This commit is contained in:
Jane
2023-12-22 12:18:52 +08:00
parent 340f82a67e
commit 812109656a
746 changed files with 84928 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
<template>
<div className="app-container">
<transition name="el-zoom-in-center">
<dc-db-config-list v-if="options.showList" @showCard="showCard" />
</transition>
</div>
</template>
<script>
import dcDbConfigList from './dcDbConfigList'
export default {
name: 'DcDbConfig',
components: { dcDbConfigList },
data() {
return {
options: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,395 @@
<template>
<el-card class="box-card" shadow="always">
<el-form ref="queryForm" :model="queryParams" :inline="true">
<el-form-item label="connectName" prop="id">
<el-input
v-model="queryParams.connectName"
placeholder="connectName"
clearable
size="small"
@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-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleCreate"
>新增</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableDataList"
border
tooltip-effect="dark"
:size="tableSize"
:height="tableHeight"
style="width: 100%;margin: 15px 0;"
>
<el-table-column type="selection" width="55" align="center" />
<template v-for="(item, index) in tableColumns">
<el-table-column
v-if="item.show"
:key="index"
:prop="item.prop"
:label="item.label"
:formatter="item.formatter"
align="center"
show-overflow-tooltip
/>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-popover
placement="left"
trigger="click"
>
<el-button
size="mini"
type="text"
icon="el-icon-edit-outline"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<el-button slot="reference">操作</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!-- 模态框添加数据-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="150px">
<el-form-item label="connectName" prop="connectName" required>
<el-input v-model="temp.connectName" placeholder="请输入connectName" style="width: 70%" />
</el-form-item>
<el-form-item label="type" prop="type" required>
<el-select v-model="temp.type" placeholder="请选择数据库类型">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="url" prop="url" required>
<el-input v-model="temp.url" placeholder="请输入url" style="width: 70%" />
</el-form-item>
<el-form-item label="userName" prop="userName" required>
<el-input v-model="temp.userName" placeholder="请输入userName" style="width: 70%" />
</el-form-item>
<el-form-item label="pwd" prop="pwd">
<el-input v-model="temp.pwd" placeholder="请输入pwd" style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script>
import { pageDataModel, delDataModel, addDataModel, updateDataModel } from '@/api/compare/dcDbConfig/datamodel'
export default {
name: 'DcDbConfigList',
data() {
return {
tableHeight: document.body.offsetHeight - 310 + 'px',
// 展示切换
showOptions: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
},
options: [{
value: 'MySQL',
label: 'MySQL'
}, {
value: 'Hive',
label: 'Hive'
}, {
value: 'Doris',
label: 'Doris'
}],
// 遮罩层
loading: true,
dialogFormVisible: false,
dialogFormVisible2: false,
textMap: {
update: '编辑数据库配置',
create: '添加数据库配置'
},
dialogStatus: '',
temp: {
connectName: '',
type: '',
url: '',
userName: '',
pwd: ''
},
rules: {
id: [{ required: true, message: '不能为空', trigger: 'blur' }],
connectName: [{ required: true, message: '不能为空', trigger: 'blur' }],
type: [{ required: true, message: '不能为空', trigger: 'blur' }],
url: [{ required: true, message: '不能为空', trigger: 'blur' }]
},
// 表格头
tableColumns: [
{ prop: 'id', label: 'id', show: true },
{ prop: 'connectName', label: 'connectName', show: true },
{ prop: 'type', label: 'type', show: true },
{
prop: 'url',
label: 'url',
show: true
}
],
// 默认选择中表格头
checkedTableColumns: [],
tableSize: 'medium',
// 状态数据字典
statusOptions: [],
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
connectName: ''
},
// 流程状态数据字典
flowStatusOptions: []
}
},
created() {
this.getDicts('sys_common_status').then(response => {
if (response.success) {
this.statusOptions = response.data
}
})
this.getDicts('sys_flow_status').then(response => {
if (response.success) {
this.flowStatusOptions = response.data
}
})
this.getList()
},
mounted() {
this.initCols()
},
methods: {
/** 查询数据源列表 */
getList() {
const _this = this
// console.log("hello world1")
_this.loading = true
pageDataModel(this.queryParams).then(response => {
console.log(response)
_this.loading = false
if (response.code ===0) {
_this.tableDataList = response.rows
_this.total = response.total
}
})
},
initCols() {
this.checkedTableColumns = this.tableColumns.map(col => col.prop)
},
handleCheckedColsChange(val) {
this.tableColumns.forEach(col => {
if (!this.checkedTableColumns.includes(col.prop)) {
col.show = false
} else {
col.show = true
}
})
},
handleCommand(command) {
this.tableSize = command
},
resetTemp() {
console.log('添加数据前,先清空之前输入的值')
this.temp = {
connectName: '',
type: '',
url: '',
userName: '',
pwd: ''
}
},
createData() {
console.log('开始添加数据')
this.$refs['dataForm'].validate((valid) => {
console.log(valid)
if (valid) {
console.log(this.temp)
addDataModel(this.temp).then((response) => {
console.log(response)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '添加成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 新增按钮操作
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
console.log('新增数据前。看是否已清空之前的数据', this.temp)
},
// 修改按钮操作
handleUpdate(row) {
// console.log(row)
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
console.log(tempData)
const tempData2 = {}
tempData2.id = tempData.id
tempData2.connectName = tempData.connectName
tempData2.type = tempData.type
tempData2.url = tempData.url
tempData2.userName = tempData.userName
tempData2.pwd = tempData.pwd
updateDataModel(tempData2).then(() => {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '修改成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
connectName: ''
}
this.handleQuery()
},
/** 刷新列表 */
handleRefresh() {
this.getList()
},
/** 详情按钮操作 */
handleDetail(row) {
this.showOptions.data.id = row.id
this.showOptions.showList = false
this.showOptions.showAdd = false
this.showOptions.showEdit = false
this.showOptions.showDetail = true
this.$emit('showCard', this.showOptions)
},
/** 删除按钮操作 */
handleDelete(row) {
console.log('进入到删除按钮操作')
console.log(row.id)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code ===0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
/** 删除全部,按钮操作 */
handleDelAll(row) {
console.log('进入到删除按钮操作')
console.log(row)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code === 0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.right-toolbar {
float: right;
}
.el-card ::v-deep .el-card__body {
height: calc(100vh - 170px);
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div className="app-container">
<transition name="el-zoom-in-center">
<dc-job-list v-if="options.showList" @showCard="showCard" />
</transition>
</div>
</template>
<script>
import dcJobList from './dcJobList'
export default {
name: 'DcJob',
components: { dcJobList },
data() {
return {
options: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,449 @@
<template>
<el-card class="box-card" shadow="always">
<el-form ref="queryForm" :model="queryParams" :inline="true">
<el-form-item label="JobId" prop="id">
<el-input
v-model="queryParams.id"
placeholder="请输入JobId"
clearable
size="small"
@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-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleCreate"
>新增</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableDataList"
border
tooltip-effect="dark"
:size="tableSize"
:height="tableHeight"
style="width: 100%;margin: 15px 0;"
>
<el-table-column type="selection" width="55" align="center" />
<template v-for="(item, index) in tableColumns">
<el-table-column
v-if="item.show"
:key="index"
:prop="item.prop"
:label="item.label"
:formatter="item.formatter"
align="center"
show-overflow-tooltip
/>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-popover
placement="left"
trigger="click"
>
<el-button
size="mini"
type="text"
icon="el-icon-edit-outline"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<el-button slot="reference">操作</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!-- 模态框添加数据-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="150px">
<el-form-item label="任务编号" prop="jobId" required>
<el-input v-model="temp.jobId" placeholder="请输入任务编号" style="width: 70%" />
</el-form-item>
<el-form-item label="任务名称" prop="jobName" required>
<el-input v-model="temp.jobName" placeholder="请输入任务名称" style="width: 70%" />
</el-form-item>
<el-form-item label="任务分组" prop="jobGroup" required>
<el-input v-model="temp.jobGroup" placeholder="请输入任务分组" style="width: 70%" />
</el-form-item>
<el-form-item label="调用目标字符串" prop="invokeTarget">
<el-input v-model="temp.invokeTarget" placeholder="请输入任务目标字符串" style="width: 70%" />
</el-form-item>
<el-form-item label="执行表达式" prop="cronExpression">
<el-input v-model="temp.cronExpression" placeholder="请输入执行表达式" style="width: 70%" />
</el-form-item>
<el-form-item label="任务状态" prop="status" required>
<el-input v-model="temp.status" placeholder="任务状态" style="width: 70%" />
</el-form-item>
<el-form-item label="创建时间" prop="createTime" required>
<el-input v-model="temp.createTime" placeholder="创建时间" style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<!-- 模态框修改数据-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible2" width="800px">
<el-form ref="dataForm2" :rules="rules" :model="temp2" label-position="left" label-width="150px">
<el-form-item label="任务编号" prop="jobId" required>
<el-input v-model="temp2.jobId" placeholder="请输入任务编号" style="width: 70%" />
</el-form-item>
<el-form-item label="任务名称" prop="jobName" required>
<el-input v-model="temp2.jobName" placeholder="请输入任务名称" style="width: 70%" />
</el-form-item>
<el-form-item label="任务分组" prop="jobGroup" required>
<el-input v-model="temp2.jobGroup" placeholder="请输入任务分组" style="width: 70%" />
</el-form-item>
<el-form-item label="调用目标字符串" prop="invokeTarget">
<el-input v-model="temp2.invokeTarget" placeholder="请输入任务目标字符串" style="width: 70%" />
</el-form-item>
<el-form-item label="执行表达式" prop="cronExpression">
<el-input v-model="temp2.cronExpression" placeholder="请输入执行表达式" style="width: 70%" />
</el-form-item>
<el-form-item label="任务状态" prop="status" required>
<el-input v-model="temp2.status" placeholder="任务状态" style="width: 70%" />
</el-form-item>
<el-form-item label="创建时间" prop="createTime" required>
<el-input v-model="temp2.createTime" placeholder="创建时间" style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script>
import { pageDataModel, delDataModel, addDataModel, updateDataModel } from '@/api/compare/dcJob/datamodel'
export default {
name: 'DcJobList',
data() {
return {
tableHeight: document.body.offsetHeight - 310 + 'px',
// 展示切换
showOptions: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
},
// 遮罩层
loading: true,
dialogFormVisible: false,
dialogFormVisible2: false,
textMap: {
update: '编辑Job配置',
create: '添加Job配置'
},
dialogStatus: '',
temp: {
jobId: '',
jobName: '',
jobGroup: '',
invokeTarget: '',
cronExpression: '',
status: '',
createTime: ''
},
temp2: {
jobId: '',
jobName: '',
jobGroup: '',
invokeTarget: '',
cronExpression: '',
status: '',
createTime: ''
},
rules: {
jobId: [{ required: true, message: '不能为空', trigger: 'blur' }],
jobName: [{ required: true, message: '不能为空', trigger: 'blur' }],
jobGroup: [{ required: true, message: '不能为空', trigger: 'blur' }],
invokeTarget: [{ required: true, message: '不能为空', trigger: 'blur' }],
cronExpression: [{ required: true, message: '不能为空', trigger: 'blur' }],
status: [{ required: true, message: '不能为空', trigger: 'blur' }],
createTime: [{ required: true, message: '不能为空', trigger: 'blur' }]
},
// 表格头
tableColumns: [
{ prop: 'jobId', label: '任务编号', show: true },
{ prop: 'jobName', label: '任务名称', show: true },
{ prop: 'jobGroup', label: '任务分组', show: true },
{
prop: 'invokeTarget',
label: '调用目标字符串',
show: true
},
{
prop: 'cronExpression',
label: '执行表达式',
show: true
},
{ prop: 'status', label: '任务状态', show: true },
{ prop: 'createTime', label: '创建时间', show: true }
],
// 默认选择中表格头
checkedTableColumns: [],
tableSize: 'medium',
// 状态数据字典
statusOptions: [],
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
id: ''
},
// 流程状态数据字典
flowStatusOptions: []
}
},
created() {
this.getDicts('sys_common_status').then(response => {
if (response.success) {
this.statusOptions = response.data
}
})
this.getDicts('sys_flow_status').then(response => {
if (response.success) {
this.flowStatusOptions = response.data
}
})
this.getList()
},
mounted() {
this.initCols()
},
methods: {
/** 查询数据源列表 */
getList() {
const _this = this
// console.log("hello world1")
_this.loading = true
pageDataModel(this.queryParams).then(response => {
_this.loading = false
if (response.code ===0) {
_this.tableDataList = response.rows
_this.total = response.total
}
})
},
initCols() {
this.checkedTableColumns = this.tableColumns.map(col => col.prop)
},
handleCheckedColsChange(val) {
this.tableColumns.forEach(col => {
if (!this.checkedTableColumns.includes(col.prop)) {
col.show = false
} else {
col.show = true
}
})
},
handleCommand(command) {
this.tableSize = command
},
resetTemp() {
console.log('添加数据前,先清空之前输入的值')
this.temp = {
jobId: '',
jobName: '',
jobGroup: '',
invokeTarget: '',
cronExpression: '',
status: '',
createTime: ''
}
},
createData() {
console.log('开始添加数据')
this.$refs['dataForm'].validate((valid) => {
console.log(valid)
if (valid) {
console.log(this.temp)
addDataModel(this.temp).then((response) => {
console.log(response)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '添加成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 新增按钮操作
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
console.log('新增数据前。看是否已清空之前的数据', this.temp)
},
// 修改按钮操作
handleUpdate(row) {
// console.log(row)
this.temp2 = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible2 = true
},
updateData() {
this.$refs['dataForm2'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp2)
console.log(tempData)
const tempData2 = {}
tempData2.id = tempData.id
tempData2.originTableName = tempData.originTableName
tempData2.originTablePrimary = tempData.originTablePrimary
tempData2.originTableFields = tempData.originTableFields
tempData2.originTableFilter = tempData.originTableFilter
tempData2.originTableGroup = tempData.originTableGroup
tempData2.toTableName = tempData.toTableName
tempData2.toTablePrimary = tempData.toTablePrimary
tempData2.toTableFields = tempData.toTableFields
tempData2.toTableFilter = tempData.toTableFilter
tempData2.toTableGroup = tempData.toTableGroup
updateDataModel(tempData2).then(() => {
this.dialogFormVisible2 = false
this.$notify({
title: '成功',
message: '修改成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
id: ''
}
this.handleQuery()
},
/** 刷新列表 */
handleRefresh() {
this.getList()
},
/** 详情按钮操作 */
handleDetail(row) {
this.showOptions.data.id = row.id
this.showOptions.showList = false
this.showOptions.showAdd = false
this.showOptions.showEdit = false
this.showOptions.showDetail = true
this.$emit('showCard', this.showOptions)
},
/** 删除按钮操作 */
handleDelete(row) {
console.log('进入到删除按钮操作')
console.log(row.id)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code ===0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
/** 删除全部,按钮操作 */
handleDelAll(row) {
console.log('进入到删除按钮操作')
console.log(row)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code === 0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.right-toolbar {
float: right;
}
.el-card ::v-deep .el-card__body {
height: calc(100vh - 170px);
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div className="app-container">
<transition name="el-zoom-in-center">
<dc-job-config-list v-if="options.showList" @showCard="showCard"/>
</transition>
</div>
</template>
<script>
import dcJobConfigList from './dcJobConfigList'
export default {
name: 'dcJobConfig',
components: {dcJobConfigList},
data() {
return {
options: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,526 @@
<template>
<el-card class="box-card" shadow="always">
<el-form ref="queryForm" :model="queryParams" :inline="true">
<el-form-item label="JobId" prop="id">
<el-input
v-model="queryParams.id"
placeholder="请输入JobId"
clearable
size="small"
@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-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleCreate"
>新增</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableDataList"
border
tooltip-effect="dark"
:size="tableSize"
:height="tableHeight"
style="width: 100%;margin: 15px 0;"
>
<el-table-column type="selection" width="55" align="center" />
<template v-for="(item, index) in tableColumns">
<el-table-column
v-if="item.show"
:key="index"
:prop="item.prop"
:label="item.label"
:formatter="item.formatter"
align="center"
show-overflow-tooltip
/>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-popover
placement="left"
trigger="click"
>
<el-button
size="mini"
type="text"
icon="el-icon-edit-outline"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-video-play"
@click="handleRun(scope.row)"
>运行</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<el-button slot="reference">操作</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!-- 模态框添加数据-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="150px">
<el-form-item label="connectName" prop="dbConfigId" required>
<el-select v-model="temp.dbConfigId" placeholder="请选择数据配置">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="源表" prop="originTableName" required>
<el-input v-model="temp.originTableName" placeholder="请填写库名和表名(例如:[db].[table])" style="width: 70%" />
</el-form-item>
<el-form-item label="源表主键" prop="originTablePrimary" required>
<el-input v-model="temp.originTablePrimary" placeholder="源表主键" style="width: 70%" />
</el-form-item>
<el-form-item label="源表字段" prop="originTableFields" required>
<el-input v-model="temp.originTableFields" placeholder="请填写字段名,用逗号分隔" style="width: 70%" />
</el-form-item>
<el-form-item label="源表过滤条件" prop="originTableFilter">
<el-input v-model="temp.originTableFilter" placeholder="源表过滤条件" style="width: 70%" />
</el-form-item>
<el-form-item label="源表分组条件" prop="originTableGroup">
<el-input v-model="temp.originTableGroup" placeholder="源表分组条件" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表" prop="toTableName" required>
<el-input v-model="temp.toTableName" placeholder="目标表" disabled style="width: 70%" />
</el-form-item>
<el-form-item label="目标表主键" prop="toTablePrimary" required>
<el-input v-model="temp.toTablePrimary" placeholder="目标表主键" disabled style="width: 70%" />
</el-form-item>
<el-form-item label="目标表字段" prop="toTableFields" required>
<el-input v-model="temp.toTableFields" placeholder="目标表字段" disabled style="width: 70%" />
</el-form-item>
<el-form-item label="目标表过滤条件" prop="toTableFilter">
<el-input v-model="temp.toTableFilter" placeholder="目标表过滤条件" disabled style="width: 70%" />
</el-form-item>
<el-form-item label="目标表分组条件" prop="toTableGroup">
<el-input v-model="temp.toTableGroup" placeholder="目标表分组条件" disabled style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<!-- 模态框修改数据-->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible2" width="800px">
<el-form ref="dataForm2" :rules="rules" :model="temp2" label-position="left" label-width="150px">
<el-form-item label="源表" prop="originTableName" required>
<el-input v-model="temp2.originTableName" placeholder="请填写库名和表名(例如:[db].[table])" style="width: 70%" />
</el-form-item>
<el-form-item label="源表主键" prop="originTablePrimary" required>
<el-input v-model="temp2.originTablePrimary" placeholder="源表主键" style="width: 70%" />
</el-form-item>
<el-form-item label="源表字段" prop="originTableFields" required>
<el-input v-model="temp2.originTableFields" placeholder="请填写字段名,用逗号分隔" style="width: 70%" />
</el-form-item>
<el-form-item label="源表过滤条件" prop="originTableFilter">
<el-input v-model="temp2.originTableFilter" placeholder="源表过滤条件" style="width: 70%" />
</el-form-item>
<el-form-item label="源表分组条件" prop="originTableGroup">
<el-input v-model="temp2.originTableGroup" placeholder="源表分组条件" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表" prop="toTableName" required>
<el-input v-model="temp2.toTableName" placeholder="目标表" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表主键" prop="toTablePrimary" required>
<el-input v-model="temp2.toTablePrimary" placeholder="目标表主键" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表字段" prop="toTableFields" required>
<el-input v-model="temp2.toTableFields" placeholder="目标表字段" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表过滤条件" prop="toTableFilter">
<el-input v-model="temp2.toTableFilter" placeholder="目标表过滤条件" style="width: 70%" />
</el-form-item>
<el-form-item label="目标表分组条件" prop="toTableGroup">
<el-input v-model="temp2.toTableGroup" placeholder="目标表分组条件" style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script>
import { pageDataModel, delDataModel, addDataModel, updateDataModel, getDbConfig, dcJobConfigRun } from '@/api/compare/dcJobConfig/datamodel'
export default {
name: 'DcJobConfigList',
data() {
return {
options: [],
tableHeight: document.body.offsetHeight - 310 + 'px',
// 展示切换
showOptions: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
},
// 遮罩层
loading: true,
dialogFormVisible: false,
dialogFormVisible2: false,
textMap: {
update: '编辑Job配置',
create: '添加Job配置'
},
dialogStatus: '',
temp: {
dbConfigId: '',
originTableName: '',
originTablePrimary: '',
originTableFields: '',
originTableFilter: '',
originTableGroup: '',
toTableName: '',
toTablePrimary: '',
toTableFields: '',
toTableFilter: '',
toTableGroup: ''
},
temp2: {
dbConfigId: '',
originTableName: '',
originTablePrimary: '',
originTableFields: '',
originTableFilter: '',
originTableGroup: '',
toTableName: '',
toTablePrimary: '',
toTableFields: '',
toTableFilter: '',
toTableGroup: ''
},
rules: {
name: [{ required: true, message: '不能为空', trigger: 'blur' }],
description: [{ required: true, message: '不能为空', trigger: 'blur' }]
},
// 表格头
tableColumns: [
{ prop: 'id', label: 'jobconfigId', show: true },
{ prop: 'originTableName', label: '源表', show: true },
{ prop: 'originTablePrimary', label: '源表主键', show: true },
{
prop: 'originTableFields',
label: '对比字段',
show: true
},
{
prop: 'toTableName',
label: '目标表',
show: true
},
{ prop: 'toTablePrimary', label: '目标主键', show: true },
{ prop: 'toTableFields', label: '目标对比字段', show: true },
{ prop: 'schduleStatus', label: '是否启动调度', show: true,
formatter: function (value, row, index) {
console.log(value)
if (value.schduleStatus =='1') {
return '是'
} else {
return '否'
}
} },
{ prop: 'createTime', label: '创建时间', show: true }
],
// 默认选择中表格头
checkedTableColumns: [],
tableSize: 'medium',
// 状态数据字典
statusOptions: [],
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
id: ''
},
// 流程状态数据字典
flowStatusOptions: []
}
},
watch: {
temp: {
handler(newName, oldName) {
this.temp.dbConfigId = newName.dbConfigId
this.temp.toTableName = newName.originTableName
this.temp.toTablePrimary = newName.originTablePrimary
this.temp.toTableFields = newName.originTableFields
this.temp.toTableFilter = newName.originTableFilter
this.temp.toTableGroup = newName.originTableGroup
},
immediate: true,
deep: true
}
},
created() {
this.getList()
this.getDbConfig()
},
mounted() {
this.initCols()
},
methods: {
/** 查询数据源列表 */
getList() {
const _this = this
// console.log("hello world1")
_this.loading = true
pageDataModel(this.queryParams).then(response => {
// console.log("hello world2")
// console.log(response.total)
// console.log(response.code)
_this.loading = false
if (response.code ===0) {
_this.tableDataList = response.rows
_this.total = response.total
// console.log("hello world3")
// console.log(_this.tableDataList)
// console.log(_this.total)
}
})
},
initCols() {
this.checkedTableColumns = this.tableColumns.map(col => col.prop)
},
// 获取DbconfigId
getDbConfig() {
let _this =this
console.log('添加数据时获取DbconfigId')
getDbConfig().then((response) => {
// console.log("response",response)
// console.log("total",response.total)
for(let i = 0; i < response.total; i++){
// console.log(response.rows[i].id)
let obj={ value :'', label : '' }
console.log(response.rows[i].connectName)
obj.value = response.rows[i].id
obj.label = response.rows[i].connectName
console.log(i)
console.log(obj)
_this.options.push(obj)
}
})
},
// 添加数据前,清空数据
resetTemp() {
console.log('添加数据前,先清空之前输入的值')
this.temp = {
dbConfigId: '',
originTableName: '',
originTablePrimary: '',
originTableFields: '',
originTableFilter: '',
originTableGroup: '',
toTableName: '',
toTablePrimary: '',
toTableFields: '',
toTableFilter: '',
toTableGroup: ''
}
},
// 开始添加数据
createData() {
console.log('开始添加数据')
this.$refs['dataForm'].validate((valid) => {
console.log(valid)
if (valid) {
console.log(this.temp)
addDataModel(this.temp).then((response) => {
console.log(response)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '添加成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 新增按钮操作
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
console.log('新增数据前。看是否已清空之前的数据', this.temp)
},
// 修改按钮操作
handleUpdate(row) {
// console.log(row)
this.temp2 = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible2 = true
},
updateData() {
this.$refs['dataForm2'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp2)
console.log(tempData)
const tempData2 = {}
tempData2.id = tempData.id
tempData2.originTableName = tempData.originTableName
tempData2.originTablePrimary = tempData.originTablePrimary
tempData2.originTableFields = tempData.originTableFields
tempData2.originTableFilter = tempData.originTableFilter
tempData2.originTableGroup = tempData.originTableGroup
tempData2.toTableName = tempData.toTableName
tempData2.toTablePrimary = tempData.toTablePrimary
tempData2.toTableFields = tempData.toTableFields
tempData2.toTableFilter = tempData.toTableFilter
tempData2.toTableGroup = tempData.toTableGroup
updateDataModel(tempData2).then(() => {
this.dialogFormVisible2 = false
this.$notify({
title: '成功',
message: '修改成功',
type: 'success',
duration: 2000
})
this.getList()
})
}
})
},
// 运行按钮操作
handleRun(data){
console.log(data.id)
dcJobConfigRun(data.id).then((response) => {
console.log("response",response)
if (response.code ===0) {
this.$message.success('运行成功')
this.getList()
}else{
this.$message.success('运行失败')
this.getList()
}
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
id: ''
}
this.handleQuery()
},
/** 刷新列表 */
handleRefresh() {
this.getList()
},
/** 删除按钮操作 */
handleDelete(row) {
console.log('进入到删除按钮操作')
console.log(row.id)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code ===0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
/** 删除全部,按钮操作 */
handleDelAll(row) {
console.log('进入到删除按钮操作')
console.log(row)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.id).then(response => {
console.log(response)
if (response.code === 0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.right-toolbar {
float: right;
}
.el-card ::v-deep .el-card__body {
height: calc(100vh - 170px);
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div className="app-container">
<transition name="el-zoom-in-center">
<dc-job-instance-list v-if="options.showList" @showCard="showCard" />
</transition>
</div>
</template>
<script>
import dcJobInstanceList from './dcJobInstanceList'
export default {
name: 'DcJobInstance',
components: { dcJobInstanceList },
data() {
return {
options: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,447 @@
<template>
<el-card class="box-card" shadow="always">
<el-form ref="queryForm" :model="queryParams" :inline="true">
<el-form-item label="jobconfigId" prop="id">
<el-input
v-model="queryParams.jobconfigId"
placeholder="请输入JobconfigId"
clearable
size="small"
@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-table
v-loading="loading"
:data="tableDataList"
border
tooltip-effect="dark"
:size="tableSize"
:height="tableHeight"
style="width: 100%;margin: 15px 0;"
>
<el-table-column type="selection" width="55" align="center" />
<template v-for="(item, index) in tableColumns">
<el-table-column
v-if="item.show"
:key="index"
:prop="item.prop"
:label="item.label"
:formatter="item.formatter"
align="center"
show-overflow-tooltip
/>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="medium"
type="text"
icon="el-icon-document"
@click="handleDetail(scope.row)"
>详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 模态框查看详情-->
<el-dialog title="Job实例列表详细" :visible.sync="dialogFormVisible" width="1000px">
<div style="text-align: center;font-size: 18pt;margin-bottom: 20px">量级对比</div>
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="180px">
<div style="display: flex;align-items: center;justify-content: center">
<el-form-item style="width: 70%;" label="源表" prop="originTableName" required>
</el-form-item>
<el-form-item style="width: 70%" label="目标表" prop="originTablePrimary" required>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 70%" label="originTablePv" prop="originTablePv" required>
<el-input v-model="temp.originTablePv" style="width: 85%" disabled/>
</el-form-item>
<el-form-item style="width: 70%" label="toTablePv" prop="toTablePv" required>
<el-input v-model="temp.toTablePv" style="width: 85%" disabled/>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 70%" label="originTableUv" prop="originTableUv" required>
<el-input v-model="temp.originTableUv" style="width: 85%" disabled/>
</el-form-item>
<el-form-item style="width: 70%" label="toTableUv" prop="toTableUv" required>
<el-input v-model="temp.toTableUv" style="width: 85%" disabled/>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 70%" label="pvDiff" prop="pvDiff" required>
<el-input v-model="temp.pvDiff" style="width: 85%" disabled/>
</el-form-item>
<el-form-item style="width: 70%" label="uvDiff" prop="uvDiff" required>
<el-input v-model="temp.uvDiff" style="width: 85%" disabled/>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 95%" label="magnitudeSql" prop="magnitudeSql" required>
<el-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 8}"
disabled
height="150"
v-model="temp.magnitudeSql">
</el-input>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 95%" label="量级是否通过" prop="magnitudeSql" required>
<el-input
disabled
height="150"
style="color:red"
:value="temp.pvDiff==0 &&temp.uvDiff==0 ? 'true':'false'"
>
</el-input>
</el-form-item>
</div>
<!--一致性对比-->
<div style="text-align: center;font-size: 18pt;margin-bottom: 20px">一致性对比</div>
<div style="display: flex">
<el-form-item style="width: 70%" label="originTableCount" prop="originTableCount" required>
<el-input v-model="temp.originTableCount" style="width: 85%" disabled/>
</el-form-item>
<el-form-item style="width: 70%" label="uvDiff" prop="uvDiff" required>
<el-input v-model="temp.uvDiff" style="width: 85%" disabled/>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 50%" label="countDiff" prop="countDiff" required>
<el-input v-model="temp.countDiff" style="width: 85%" disabled/>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 95%" label="magnitudeSql" prop="magnitudeSql" required>
<el-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 8}"
disabled
height="150"
v-model="temp.consistencySql">
</el-input>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item style="width: 95%" label="一致性是否通过" prop="magnitudeSql" required>
<el-input
disabled
height="150"
style="color:red"
:value="temp.countDiff == temp.originTableCount && temp.countDiff == temp.toTableCount ? 'true':'false'"
>
</el-input>
</el-form-item>
</div>
</el-form>
<div style="text-align: center;margin: 10px 0">
<el-button style="font-size: 16pt;background-color: #13ce66;border-radius: 5px" @click="getDiffDetail(temp.id)">
查看差异
</el-button>
</div>
</el-dialog>
<!-- 模态框查看差异-->
<el-dialog title="差异case" :visible.sync="dialogFormVisible2" width="1000px">
<el-table
:data="tableData"
style="width: 100%;"
height="250"
stripe
>
<el-table-column
prop="base_dict_code"
label="base_dict_code"
width="140">
</el-table-column>
<el-table-column
prop="base_dict_label"
label="base_dict_label"
width="140">
</el-table-column>
<el-table-column
prop="verify_dict_label"
label="verify_dict_label"
width="140">
</el-table-column>
<el-table-column
prop="dict_label_is_pass"
label="dict_label_is_pass"
width="140">
</el-table-column>
<el-table-column
prop="base_dict_value"
label="base_dict_value"
width="140">
</el-table-column>
<el-table-column
prop="verify_dict_value"
label="verify_dict_value"
width="140">
</el-table-column>
<el-table-column
prop="dict_value_is_pass"
label="dict_value_is_pass"
width="140">
</el-table-column>
</el-table>
</el-dialog>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script>
import { pageDataModel, getDiffDetail } from '@/api/compare/dcJobInstance/datamodel'
export default {
name: 'DcJobInstanceList',
data() {
return {
tableHeight: document.body.offsetHeight - 310 + 'px',
// 展示切换
showOptions: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
},
// 遮罩层
loading: true,
dialogFormVisible: false,
dialogFormVisible2: false,
textMap: {
update: '编辑Job配置',
detail: 'Jobconfig详情'
},
dialogStatus: '',
// 表单提交内容
temp: {
id: '',
originTablePv: '',
toTablePv: '',
originTableUv: '',
toTableUv: '',
pvDiff: '',
uvDiff: '',
magnitudeSql: '',
countDiff: '',
originTableCount: '',
consistencySql: ''
},
// 规则
rules: {
name: [{ required: true, message: '不能为空', trigger: 'blur' }],
description: [{ required: true, message: '不能为空', trigger: 'blur' }]
},
// 表格头
tableColumns: [
{ prop: 'id', label: 'id', show: true },
{ prop: 'jobconfigId', label: 'jobconfigId', show: true },
{ prop: 'originTableName', label: 'originTableName', show: true },
{
prop: 'toTableName',
label: 'toTableName',
show: true
},
{ prop: 'countDiff', label: '量级校验通过', show: true,
formatter: function (value, row, index) {
if (value.pvDiff == '0' && value.uvDiff == '0') {
return '是'
} else {
return '否'
}
}
},
{ prop: 'countDiff', label: '一致性校验通过', show: true,
formatter: function (value, row, index) {
if (value.countDiff == value.originTableCount && value.countDiff == value.toTableCount) {
return '是'
} else {
return '否'
}
}
},
{ prop: 'createTime', label: '创建时间', show: true }
],
// 默认选择中表格头
checkedTableColumns: [],
tableSize: 'medium',
// 状态数据字典
statusOptions: [],
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
jobconfigId: ''
},
// 流程状态数据字典
flowStatusOptions: [],
// 查看差异数组
tableData: []
}
},
created() {
this.getDicts('sys_common_status').then(response => {
if (response.success) {
this.statusOptions = response.data
}
})
this.getDicts('sys_flow_status').then(response => {
if (response.success) {
this.flowStatusOptions = response.data
}
})
this.getList()
},
mounted() {
this.initCols()
},
methods: {
/** 查询数据源列表 */
getList() {
const _this = this
// console.log("hello world1")
_this.loading = true
pageDataModel(this.queryParams).then(response => {
_this.loading = false
if (response.code ===0) {
_this.tableDataList = response.rows
_this.total = response.total
}
})
},
initCols() {
this.checkedTableColumns = this.tableColumns.map(col => col.prop)
},
handleCheckedColsChange(val) {
this.tableColumns.forEach(col => {
if (!this.checkedTableColumns.includes(col.prop)) {
col.show = false
} else {
col.show = true
}
})
},
handleCommand(command) {
this.tableSize = command
},
// 查看差异
getDiffDetail(id) {
console.log('查看差异', id)
const _this = this
// console.log("hello world1")
_this.loading = true
getDiffDetail(id).then(response => {
_this.loading = false
console.log('response', response)
if (response.code ===0) {
for (let i = 0; i < response.total; i++) {
let array = {
base_dict_code: '--',
base_dict_label: '--',
verify_dict_label: '--',
dict_label_is_pass: '--',
base_dict_value: '--',
verify_dict_value: '--',
dict_value_is_pass: '--'
}
array.base_dict_code = response.rows[i].base_dict_code || '--'
array.base_dict_label = response.rows[i].base_dict_label || '--'
array.verify_dict_label = response.rows[i].verify_dict_label || '--'
array.dict_label_is_pass = response.rows[i].dict_label_is_pass || '--'
array.base_dict_value = response.rows[i].base_dict_value || '--'
array.verify_dict_value = response.rows[i].verify_dict_value || '--'
array.dict_value_is_pass = response.rows[i].dict_value_is_pass || '--'
console.log('array', array)
_this.tableData.push(array)
}
this.dialogFormVisible2 = true
}
})
},
/** 详情按钮操作 */
handleDetail(row) {
console.log(row)
console.log('详情展开')
this.temp = Object.assign({}, row)
this.dialogStatus = 'detail'
this.dialogFormVisible = true
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
jobconfigId: ''
}
this.handleQuery()
},
/** 刷新列表 */
handleRefresh() {
this.getList()
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.right-toolbar {
float: right;
}
.el-card ::v-deep .el-card__body {
height: calc(100vh - 170px);
}
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div className="app-container">
<transition name="el-zoom-in-center">
<dc-job-log-list v-if="options.showList" @showCard="showCard" />
</transition>
</div>
</template>
<script>
import dcJobLogList from './dcJobLogList'
export default {
name: 'DcJobLog',
components: { dcJobLogList },
data() {
return {
options: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
}
}
},
methods: {
showCard(data) {
Object.assign(this.options, data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,301 @@
<template>
<el-card class="box-card" shadow="always">
<el-form ref="queryForm" :model="queryParams" :inline="true">
<el-form-item label="jobLogId" prop="jobLogId">
<el-input
v-model="queryParams.jobLogId"
placeholder="jobLogId"
clearable
size="small"
@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-table
v-loading="loading"
:data="tableDataList"
border
tooltip-effect="dark"
:size="tableSize"
:height="tableHeight"
style="width: 100%;margin: 15px 0;"
>
<el-table-column type="selection" width="55" align="center" />
<template v-for="(item, index) in tableColumns">
<el-table-column
v-if="item.show"
:key="index"
:prop="item.prop"
:label="item.label"
:formatter="item.formatter"
align="center"
show-overflow-tooltip
/>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-popover
placement="left"
trigger="click"
>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleDetail(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<el-button slot="reference">操作</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!-- 模态框添加数据-->
<el-dialog title="调度日志详情" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="150px">
<el-form-item label="日志编号" prop="jobLogId" required>
<el-input v-model="temp.jobLogId" style="width: 70%" />
</el-form-item>
<el-form-item label="任务名称" prop="jobName" required>
<el-input v-model="temp.jobName" style="width: 70%" />
</el-form-item>
<el-form-item label="源表主键" prop="jobGroup" required>
<el-input v-model="temp.jobGroup" style="width: 70%" />
</el-form-item>
<el-form-item label="调用目标字符串" prop="invokeTarget">
<el-input v-model="temp.invokeTarget" style="width: 70%" />
</el-form-item>
<el-form-item label="日志信息" prop="jobMessage">
<el-input v-model="temp.jobMessage" style="width: 70%" />
</el-form-item>
<el-form-item label="状态" prop="status" required>
<el-input v-model="temp.status" placeholder="目标表" disabled style="width: 70%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
确认
</el-button>
</div>
</el-dialog>
<el-pagination
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="queryParams.pageNum"
:page-size.sync="queryParams.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script>
import { pageDataModel, delDataModel } from '@/api/compare/dcJobLog/datamodel'
export default {
name: 'DcJobLogList',
data() {
return {
tableHeight: document.body.offsetHeight - 310 + 'px',
// 展示切换
showOptions: {
data: {},
showList: true,
showAdd: false,
showEdit: false,
showDetail: false
},
// 遮罩层
loading: true,
dialogFormVisible: false,
dialogFormVisible2: false,
dialogStatus: '',
temp: {
jobLogId: '',
jobName: '',
jobGroup: '',
invokeTarget: '',
jobMessage: '',
status: ''
},
rules: {
name: [{ required: true, message: '不能为空', trigger: 'blur' }],
description: [{ required: true, message: '不能为空', trigger: 'blur' }]
},
// 表格头
tableColumns: [
{ prop: 'jobLogId', label: '日志编号', show: true },
{ prop: 'jobName', label: '任务名称', show: true },
{ prop: 'jobGroup', label: '源表主键', show: true },
{
prop: 'invokeTarget',
label: '调用目标字符串',
show: true
},
{
prop: 'jobMessage',
label: '日志信息',
show: true
},
{ prop: 'status', label: '状态', show: true,
formatter: function (value, row, index) {
if (value.status==0) {
return '成功'
} else {
return '失败'
}
}}
],
// 默认选择中表格头
checkedTableColumns: [],
tableSize: 'medium',
// 状态数据字典
statusOptions: [],
// 表格数据
tableDataList: [],
// 总数据条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
jobLogId: ''
},
// 流程状态数据字典
flowStatusOptions: []
}
},
created() {
this.getDicts('sys_common_status').then(response => {
if (response.success) {
this.statusOptions = response.data
}
})
this.getDicts('sys_flow_status').then(response => {
if (response.success) {
this.flowStatusOptions = response.data
}
})
this.getList()
},
mounted() {
this.initCols()
},
methods: {
/** 查询数据源列表 */
getList() {
const _this = this
// console.log("hello world1")
_this.loading = true
pageDataModel(this.queryParams).then(response => {
// console.log("hello world2")
// console.log(response.total)
// console.log(response.code)
_this.loading = false
if (response.code ===0) {
_this.tableDataList = response.rows
_this.total = response.total
// console.log("hello world3")
// console.log(_this.tableDataList)
// console.log(_this.total)
}
})
},
initCols() {
this.checkedTableColumns = this.tableColumns.map(col => col.prop)
},
handleCheckedColsChange(val) {
this.tableColumns.forEach(col => {
if (!this.checkedTableColumns.includes(col.prop)) {
col.show = false
} else {
col.show = true
}
})
},
handleCommand(command) {
this.tableSize = command
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 20,
jobLogId: ''
}
this.handleQuery()
},
/** 刷新列表 */
handleRefresh() {
this.getList()
},
/** 详情按钮操作 */
handleDetail(row) {
this.temp = Object.assign({}, row) // copy obj
console.log(row)
if (row.status == 0) {
this.temp.status = '成功'
} else {
this.temp.status = '失败'
}
this.dialogStatus = 'detail'
this.dialogFormVisible = true
},
/** 删除按钮操作 */
handleDelete(row) {
console.log('进入到删除按钮操作')
console.log(row)
this.$confirm('选中数据将被永久删除, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delDataModel(row.jobLogId).then(response => {
console.log(response)
if (response.code ===0) {
this.$message.success('删除成功')
this.getList()
}
})
}).catch(() => {
})
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.queryParams.pageNum = 1
this.queryParams.pageSize = val
this.getList()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.queryParams.pageNum = val
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.right-toolbar {
float: right;
}
.el-card ::v-deep .el-card__body {
height: calc(100vh - 170px);
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<heat-map />
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<radar-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<sunburst />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<gauge />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<rich />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<theme-river />
</div>
</el-col>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="12">
<div class="chart-wrapper">
<graph />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="12">
<div class="chart-wrapper">
<sankey />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<scatter />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<point />
</div>
</el-col>
</el-row>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<div class="chart-wrapper">
<category />
</div>
</el-row>
</div>
</div>
</template>
<script>
import RadarChart from '@/components/Echarts/RadarChart'
import HeatMap from '@/components/Echarts/HeatMap'
import Gauge from '@/components/Echarts/Gauge'
import Rich from '@/components/Echarts/Rich'
import ThemeRiver from '@/components/Echarts/ThemeRiver'
import Sunburst from '@/components/Echarts/Sunburst'
import Graph from '@/components/Echarts/Graph'
import Sankey from '@/components/Echarts/Sankey'
import Scatter from '@/components/Echarts/Scatter'
import Category from '@/components/Echarts/Category'
import Point from '@/components/Echarts/Point'
export default {
name: 'Echarts',
components: {
Point,
Category,
Graph,
HeatMap,
RadarChart,
Sunburst,
Gauge,
Rich,
ThemeRiver,
Sankey,
Scatter
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 18px 22px 22px 22px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div class="app-container">
<p class="warn-content">
富文本基于
<el-link type="primary" href="https://www.kancloud.cn/wangfupeng/wangeditor3/332599" target="_blank">wangEditor</el-link>
</p>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="15" :lg="15" :xl="15">
<div ref="editor" class="text" />
</el-col>
<el-col :xs="24" :sm="24" :md="9" :lg="9" :xl="9">
<div v-html="editorContent" />
</el-col>
</el-row>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { upload } from '@/utils/upload'
import E from 'wangeditor'
export default {
name: 'Editor',
data() {
return {
editorContent:
`
<ul>
<li>更多帮助请查看官方文档:<a style="color: #42b983" target="_blank" href="https://www.wangeditor.com/doc/">wangEditor</a></li>
</ul>
`
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
const _this = this
var editor = new E(this.$refs.editor)
// 自定义菜单配置
editor.config.zIndex = 5
// 文件上传
editor.config.customUploadImg = function(files, insert) {
// files 是 input 中选中的文件列表
// insert 是获取图片 url 后,插入到编辑器的方法
files.forEach(image => {
upload(_this.imagesUploadApi, image).then(res => {
const data = res.data
const url = _this.baseApi + '/file/' + data.type + '/' + data.realName
insert(url)
})
})
}
editor.config.onchange = (html) => {
this.editorContent = html
}
editor.create()
// 初始化数据
editor.txt.html(this.editorContent)
}
}
</script>
<style scoped>
.text {
text-align:left;
}
::v-deep .w-e-text-container {
height: 420px !important;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="app-container">
<p class="warn-content">
Markdown 基于
<el-link type="primary" href="https://github.com/hinesboy/mavonEditor" target="_blank">MavonEditor</el-link>
</p>
<mavon-editor ref="md" :style="'height:' + height" @imgAdd="imgAdd" />
</div>
</template>
<script>
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
export default {
name: 'Markdown',
components: {
mavonEditor
},
data() {
return {
height: document.documentElement.clientHeight - 200 + 'px'
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 200 + 'px'
}
},
methods: {
imgAdd(pos, $file) {
upload(this.imagesUploadApi, $file).then(res => {
const data = res.data
const url = this.baseApi + '/file/' + data.type + '/' + data.realName
this.$refs.md.$img2Url(pos, url)
})
}
}
}
</script>
<style scoped>
.v-note-wrapper.shadow {
z-index: 5;
}
</style>

View File

@@ -0,0 +1,207 @@
<template>
<div class="app-container">
<p class="warn-content">
Yaml编辑器 基于
<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>
主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>
</p>
<Yaml :value="value" :height="height" />
</div>
</template>
<script>
import Yaml from '@/components/YamlEdit/index'
export default {
name: 'YamlEdit',
components: { Yaml },
data() {
return {
height: document.documentElement.clientHeight - 210 + 'px',
value: '# 展示数据如需更换主题请在src/components/YamlEdit 目录中搜索原主题名称进行替换\n' +
'\n' +
'# ===================================================================\n' +
'# Spring Boot configuration.\n' +
'#\n' +
'# This configuration will be overridden by the Spring profile you use,\n' +
'# for example application-dev.yml if you use the "dev" profile.\n' +
'#\n' +
'# More information on profiles: https://www.jhipster.tech/profiles/\n' +
'# More information on configuration properties: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# ===================================================================\n' +
'# Standard Spring Boot properties.\n' +
'# Full reference is available at:\n' +
'# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html\n' +
'# ===================================================================\n' +
'\n' +
'eureka:\n' +
' client:\n' +
' enabled: true\n' +
' healthcheck:\n' +
' enabled: true\n' +
' fetch-registry: true\n' +
' register-with-eureka: true\n' +
' instance-info-replication-interval-seconds: 10\n' +
' registry-fetch-interval-seconds: 10\n' +
' instance:\n' +
' appname: product\n' +
' instanceId: product:${spring.application.instance-id:${random.value}}\n' +
' #instanceId: 127.0.0.1:9080\n' +
' lease-renewal-interval-in-seconds: 5\n' +
' lease-expiration-duration-in-seconds: 10\n' +
' status-page-url-path: ${management.endpoints.web.base-path}/info\n' +
' health-check-url-path: ${management.endpoints.web.base-path}/health\n' +
' metadata-map:\n' +
' zone: primary # This is needed for the load balancer\n' +
' profile: ${spring.profiles.active}\n' +
' version: ${info.project.version:}\n' +
' git-version: ${git.commit.id.describe:}\n' +
' git-commit: ${git.commit.id.abbrev:}\n' +
' git-branch: ${git.branch:}\n' +
'ribbon:\n' +
' ReadTimeout: 120000\n' +
' ConnectTimeout: 300000\n' +
' eureka:\n' +
' enabled: true\n' +
'zuul:\n' +
' host:\n' +
' connect-timeout-millis: 5000\n' +
' max-per-route-connections: 10000\n' +
' max-total-connections: 5000\n' +
' socket-timeout-millis: 60000\n' +
' semaphore:\n' +
' max-semaphores: 500\n' +
'\n' +
'feign:\n' +
' hystrix:\n' +
' enabled: true\n' +
' client:\n' +
' config:\n' +
' default:\n' +
' connectTimeout: 500000\n' +
' readTimeout: 500000\n' +
'\n' +
'# See https://github.com/Netflix/Hystrix/wiki/Configuration\n' +
'hystrix:\n' +
' command:\n' +
' default:\n' +
' circuitBreaker:\n' +
' sleepWindowInMilliseconds: 100000\n' +
' forceClosed: true\n' +
' execution:\n' +
' isolation:\n' +
'# strategy: SEMAPHORE\n' +
'# See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330\n' +
' thread:\n' +
' timeoutInMilliseconds: 60000\n' +
' shareSecurityContext: true\n' +
'\n' +
'management:\n' +
' endpoints:\n' +
' web:\n' +
' base-path: /management\n' +
' exposure:\n' +
' include: ["configprops", "env", "health", "info", "threaddump"]\n' +
' endpoint:\n' +
' health:\n' +
' show-details: when_authorized\n' +
' info:\n' +
' git:\n' +
' mode: full\n' +
' health:\n' +
' mail:\n' +
' enabled: false # When using the MailService, configure an SMTP server and set this to true\n' +
' metrics:\n' +
' enabled: false # http://micrometer.io/ is disabled by default, as we use http://metrics.dropwizard.io/ instead\n' +
'\n' +
'spring:\n' +
' application:\n' +
' name: product\n' +
' jpa:\n' +
' open-in-view: false\n' +
' hibernate:\n' +
' ddl-auto: update\n' +
' naming:\n' +
' physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy\n' +
' implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy\n' +
' messages:\n' +
' basename: i18n/messages\n' +
' mvc:\n' +
' favicon:\n' +
' enabled: false\n' +
' thymeleaf:\n' +
' mode: HTML\n' +
'security:\n' +
' oauth2:\n' +
' resource:\n' +
' filter-order: 3\n' +
'\n' +
'server:\n' +
' servlet:\n' +
' session:\n' +
' cookie:\n' +
' http-only: true\n' +
'\n' +
'# Properties to be exposed on the /info management endpoint\n' +
'info:\n' +
' # Comma separated list of profiles that will trigger the ribbon to show\n' +
' display-ribbon-on-profiles: "dev"\n' +
'\n' +
'# ===================================================================\n' +
'# JHipster specific properties\n' +
'#\n' +
'# Full reference is available at: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'jhipster:\n' +
' async:\n' +
' core-pool-size: 2\n' +
' max-pool-size: 50\n' +
' queue-capacity: 10000\n' +
' # By default CORS is disabled. Uncomment to enable.\n' +
' #cors:\n' +
' #allowed-origins: "*"\n' +
' #allowed-methods: "*"\n' +
' #allowed-headers: "*"\n' +
' #exposed-headers: "Authorization,Link,X-Total-Count"\n' +
' #allow-credentials: true\n' +
' #max-age: 1800\n' +
' mail:\n' +
' from: product@localhost\n' +
' swagger:\n' +
' default-include-pattern: /api/.*\n' +
' title: product API\n' +
' description: product API documentation\n' +
' version: 0.0.1\n' +
' terms-of-service-url:\n' +
' contact-name:\n' +
' contact-url:\n' +
' contact-email:\n' +
' license:\n' +
' license-url:\n' +
'\n' +
'# ===================================================================\n' +
'# Application specific properties\n' +
'# Add your own application properties here, see the ApplicationProperties class\n' +
'# to have type-safe configuration, like in the JHipsterProperties above\n' +
'#\n' +
'# More documentation is available at:\n' +
'# https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# application:\n'
}
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 210 + 'px'
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
<el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
<el-table-column v-for="item of tableHeader" :key="item" :prop="item" :label="item" />
</el-table>
</div>
</template>
<script>
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
export default {
name: 'UploadExcel',
components: { UploadExcelComponent },
data() {
return {
tableData: [],
tableHeader: []
}
},
methods: {
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: '请不要上传大于1m的文件.',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) {
this.tableData = results
this.tableHeader = header
}
}
}
</script>

View File

@@ -0,0 +1,74 @@
const elementIcons = [
'info',
'error',
'success',
'warning',
'question',
'back',
'arrow-left',
'arrow-down',
'arrow-right',
'arrow-up',
'caret-left',
'caret-bottom',
'caret-top',
'caret-right',
'd-arrow-left',
'd-arrow-right',
'minus',
'plus',
'remove',
'circle-plus',
'remove-outline',
'circle-plus-outline',
'close',
'check',
'circle-close',
'circle-check',
'circle-close-outline',
'circle-check-outline',
'zoom-out',
'zoom-in',
'd-caret',
'sort',
'sort-down',
'sort-up',
'tickets',
'document',
'goods',
'sold-out',
'news',
'message',
'date',
'printer',
'time',
'bell',
'mobile-phone',
'service',
'view',
'menu',
'more',
'more-outline',
'star-on',
'star-off',
'location',
'location-outline',
'phone',
'phone-outline',
'picture',
'picture-outline',
'delete',
'search',
'edit',
'edit-outline',
'rank',
'refresh',
'share',
'setting',
'upload',
'upload2',
'download',
'loading'
]
export default elementIcons

View File

@@ -0,0 +1,97 @@
<template>
<div class="icons-container">
<aside>
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
</a>
</aside>
<el-tabs type="border-card">
<el-tab-pane label="Icons">
<div class="grid">
<div v-for="item of svgIcons" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateIconCode(item) }}
</div>
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="Element-UI Icons">
<div class="grid">
<div v-for="item of elementIcons" :key="item" @click="handleClipboard(generateElementIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateElementIconCode(item) }}
</div>
<div class="icon-item">
<i :class="'el-icon-' + item" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import clipboard from '@/utils/clipboard'
import svgIcons from './svg-icons'
import elementIcons from './element-icons'
export default {
name: 'Icons',
data() {
return {
svgIcons,
elementIcons
}
},
methods: {
generateIconCode(symbol) {
return `<svg-icon icon-class="${symbol}" />`
},
generateElementIconCode(symbol) {
return `<i class="el-icon-${symbol}" />`
},
handleClipboard(text, event) {
clipboard(text, event)
}
}
}
</script>
<style lang="scss" scoped>
.icons-container {
margin: 10px 20px 0;
overflow: hidden;
.grid {
position: relative;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.icon-item {
margin: 20px;
height: 85px;
text-align: center;
width: 100px;
float: left;
font-size: 30px;
color: #24292e;
cursor: pointer;
}
span {
display: block;
font-size: 16px;
margin-top: 10px;
}
.disabled {
pointer-events: none;
}
}
</style>

View File

@@ -0,0 +1,10 @@
const req = require.context('../../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const svgIcons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default svgIcons

View File

@@ -0,0 +1,135 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
autoResize: {
type: Boolean,
default: true
},
chartData: {
type: Object,
required: true
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
},
setOptions({ expectedData, actualData } = {}) {
this.chart.setOption({
xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 10,
right: 10,
bottom: 20,
top: 30,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['expected', 'actual']
},
series: [{
name: 'expected', itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: 'actual',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: actualData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,181 @@
<template>
<el-row :gutter="40" class="panel-group">
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('newVisitis')">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
New Visits
</div>
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('messages')">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Messages
</div>
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('purchases')">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Purchases
</div>
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('shoppings')">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">
Shoppings
</div>
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
</div>
</div>
</el-col>
</el-row>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
components: {
CountTo
},
methods: {
handleSetLineChartData(type) {
this.$emit('handleSetLineChartData', type)
}
}
}
</script>
<style lang="scss" scoped>
.panel-group {
margin-top: 18px;
.card-panel-col {
margin-bottom: 32px;
}
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-people {
background: #40c9c6;
}
.icon-message {
background: #36a3f7;
}
.icon-money {
background: #f4516c;
}
.icon-shopping {
background: #34bfa3
}
}
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
@media (max-width:550px) {
.card-panel-description {
display: none;
}
.card-panel-icon-wrapper {
float: none !important;
width: 100%;
height: 100%;
margin: 0 !important;
.svg-icon {
display: block;
margin: 14px auto !important;
float: none !important;
}
}
}
</style>

View File

@@ -0,0 +1,55 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.$_resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
beforeDestroy() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
// to fixed bug when cached by keep-alive
// https://github.com/PanJiaChen/vue-element-admin/issues/2116
activated() {
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
deactivated() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_initResizeEvent() {
window.addEventListener('resize', this.$_resizeHandler)
},
$_destroyResizeEvent() {
window.removeEventListener('resize', this.$_resizeHandler)
},
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
$_initSidebarResizeEvent() {
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
$_destroySidebarResizeEvent() {
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
}
}
}

View File

@@ -0,0 +1,228 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column label="排序" width="50" align="center">
<template slot-scope="scope">{{ scope.row.order }}</template>
</el-table-column>
<el-table-column label="名称" width="120" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.title }}</template>
</el-table-column>
<el-table-column label="分组标识" width="200" align="center">
<template slot-scope="scope">{{ scope.row.appName }}</template>
</el-table-column>
<el-table-column label="注册方式" width="110" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope"> {{ addressTypes.find(t => t.value === scope.row.addressType).label }}</template>
</el-table-column>
<el-table-column label="在线机器" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.addressList }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="80px" style="width: 400px; margin-left:50px;">
<el-form-item label="AppName" prop="appName">
<el-input v-model="temp.appName" placeholder="AppName" />
</el-form-item>
<el-form-item label="名称" prop="title">
<el-input v-model="temp.title" placeholder="请输入执行器名称" />
</el-form-item>
<el-form-item label="排序" prop="prop">
<el-input v-model="temp.order" placeholder="执行器序号" />
</el-form-item>
<el-form-item label="注册方式" prop="addressType">
<el-radio-group v-model="temp.addressType">
<el-radio :label="0">自动注册</el-radio>
<el-radio :label="1">手动录入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="机器地址" prop="addressList">
<el-input v-model="temp.addressList" :disabled="dialogStatus!=='create'" placeholder="多个以逗号分隔" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'Executor',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
name: undefined,
jobGroup: undefined
},
editJsonVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
appName: [{ required: true, message: 'appName is required', trigger: 'blur' }],
title: [{ required: true, message: 'title is required', trigger: 'blur' }],
order: [{ required: true, message: 'title is required', trigger: 'blur' }],
addressType: [{ required: true, message: 'title is required', trigger: 'change' }]
},
temp: {
id: undefined,
appName: undefined,
title: undefined,
order: undefined,
addressType: undefined,
addressList: undefined
},
addressTypes: [
{ value: 0, label: '自动注册' },
{ value: 1, label: '手动录入' }
]
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
executor.getList().then(response => {
const { content } = response
this.list = content
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
appName: undefined,
title: undefined,
order: undefined,
addressType: undefined,
addressList: undefined
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
executor.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
tempData.configJson = this.configJson
executor.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
executor.deleted(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
handleFetchPv(id) {
executor.fetch(id).then(response => {
this.pluginData = response
this.dialogPvVisible = true
})
}
}
}
</script>

View File

@@ -0,0 +1,426 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.datasourceName"
clearable
placeholder="数据源名称"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<!-- <el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index }}</template>
</el-table-column> -->
<el-table-column label="数据源" width="120" align="center">
<template slot-scope="scope">{{ scope.row.datasource }}</template>
</el-table-column>
<el-table-column label="数据源名称" width="150" align="center">
<template slot-scope="scope">{{ scope.row.datasourceName }}</template>
</el-table-column>
<el-table-column label="数据源分组" width="120" align="center">
<template slot-scope="scope">{{ scope.row.datasourceGroup }}
</template>
</el-table-column>
<!--<el-table-column label="用户名" width="150" align="center">
<template slot-scope="scope">{{ scope.row.jdbcUsername ? scope.row.jdbcUsername:'-' }}</template>
</el-table-column>-->
<el-table-column label="jdbc连接串" width="300" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.jdbcUrl ? scope.row.jdbcUrl:'-' }}</template>
</el-table-column>
<!-- <el-table-column label="jdbc驱动类" width="200" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.jdbcDriverClass ? scope.row.jdbcDriverClass:'-' }}</template>
</el-table-column>-->
<el-table-column label="ZK地址" width="200" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.zkAdress ? scope.row.zkAdress:'-' }}</template>
</el-table-column>
<el-table-column label="数据库名" width="200" align="center" :show-overflow-tooltip="true">-->
<template slot-scope="scope">{{ scope.row.databaseName ? scope.row.databaseName:'-' }}</template>-->
</el-table-column>
<!-- <el-table-column label="备注" width="150" align="center">-->
<!-- <template slot-scope="scope">{{ scope.row.comments }}</template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px">
<el-form-item label="数据源" prop="datasource">
<el-select
v-model="temp.datasource"
placeholder="数据源"
style="width: 200px"
@change="selectDataSource(temp.datasource)"
>
<el-option v-for="item in dataSources" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据源名称" prop="datasourceName">
<el-input v-model="temp.datasourceName" placeholder="数据源名称" style="width: 40%" />
</el-form-item>
<el-form-item label="数据源分组" prop="datasourceGroup">
<el-input v-model="temp.datasourceGroup" placeholder="数据源分组" style="width: 40%" />
</el-form-item>
<el-form-item v-if="jdbc" label="用户名">
<el-input v-model="temp.jdbcUsername" placeholder="用户名" style="width: 40%" />
</el-form-item>
<el-form-item v-if="visible" v-show="jdbc" label="密码">
<el-input v-model="temp.jdbcPassword" type="password" placeholder="密码" style="width: 40%">
<i slot="suffix" title="显示密码" style="cursor:pointer" class="el-icon-view" @click="changePass('show')" />
</el-input>
</el-form-item>
<el-form-item v-show="jdbc" v-else label="密码">
<el-input v-model="temp.jdbcPassword" type="text" placeholder="密码" style="width: 40%">
<i slot="suffix" title="隐藏密码" style="cursor:pointer" class="el-icon-check" @click="changePass('hide')" />
</el-input>
</el-form-item>
<el-form-item v-if="jdbc" label="jdbc url" prop="jdbcUrl">
<el-input
v-model="temp.jdbcUrl"
:autosize="{ minRows: 3, maxRows: 6}"
type="textarea"
placeholder="jdbc url"
style="width: 60%"
/>
</el-form-item>
<el-form-item v-if="mongodb" label="地址" prop="jdbcUrl">
<el-input
v-model="temp.jdbcUrl"
:autosize="{ minRows: 3, maxRows: 6}"
type="textarea"
placeholder="localhost:27017"
style="width: 60%"
/>
</el-form-item>
<el-form-item v-if="jdbc" label="jdbc驱动类" prop="jdbcDriverClass">
<el-input v-model="temp.jdbcDriverClass" placeholder="jdbc驱动类" style="width: 60%" />
</el-form-item>
<el-form-item v-if="hbase" label="ZK地址" prop="zkAdress">
<el-input v-model="temp.zkAdress" placeholder="localhost:2181" style="width: 60%" />
</el-form-item>
<el-form-item v-if="mongodb" label="数据库名称" prop="databaseName">
<el-input v-model="temp.databaseName" placeholder="数据库名称" style="width: 60%" />
</el-form-item>
<el-form-item label="注释">
<el-input
v-model="temp.comments"
:autosize="{ minRows: 2, maxRows: 4}"
type="textarea"
placeholder="Please input"
style="width: 60%"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
<el-button type="primary" @click="testDataSource()">
测试连接
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPluginVisible" title="Reading statistics">
<el-table :data="pluginData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel" />
<el-table-column prop="pv" label="Pv" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import waves from '@/directive/waves' // waves directive
import { parseTime } from '@/utils'
import Pagination from '@/components/Pagination'
export default {
name: 'JdbcDatasource',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10
},
pluginTypeOptions: ['reader', 'writer'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
datasourceName: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcUsername: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcPassword: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcUrl: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcDriverClass: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasource: [{ required: true, message: 'this is required', trigger: 'change' }],
zkAdress: [{ required: true, message: 'this is required', trigger: 'blur' }],
databaseName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
temp: {
id: undefined,
datasourceName: '',
datasourceGroup: 'Default',
jdbcUsername: '',
jdbcPassword: '',
jdbcUrl: '',
jdbcDriverClass: '',
comments: '',
datasource: '',
zkAdress: '',
databaseName: ''
},
visible: true,
dataSources: [
{ value: 'mysql', label: 'mysql' },
{ value: 'oracle', label: 'oracle' },
{ value: 'hana', label: 'hana' },
{ value: 'postgresql', label: 'postgresql' },
{ value: 'sqlserver', label: 'sqlserver' },
{ value: 'hive', label: 'hive' },
{ value: 'hbase', label: 'hbase' },
{ value: 'mongodb', label: 'mongodb' },
{ value: 'clickhouse', label: 'clickhouse' }
],
jdbc: true,
hbase: false,
mongodb: false
}
},
created() {
this.fetchData()
},
methods: {
selectDataSource(datasource) {
if (datasource === 'mysql') {
this.temp.jdbcUrl = 'jdbc:mysql://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'com.mysql.jdbc.Driver'
} else if (datasource === 'hana') {
this.temp.jdbcUrl = 'jdbc:sap://{host}:{port}/{instanceNumber}'
this.temp.jdbcDriverClass = 'com.sap.db.jdbc.Driver'
} else if (datasource === 'oracle') {
this.temp.jdbcUrl = 'jdbc:oracle:thin:@//{host}:{port}/{serviceName}'
this.temp.jdbcDriverClass = 'oracle.jdbc.OracleDriver'
} else if (datasource === 'postgresql') {
this.temp.jdbcUrl = 'jdbc:postgresql://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'org.postgresql.Driver'
} else if (datasource === 'sqlserver') {
this.temp.jdbcUrl = 'jdbc:jtds:sqlserver://{host}:{port};DatabaseName={database}'
this.temp.jdbcDriverClass = 'net.sourceforge.jtds.jdbc.Driver'
} else if (datasource === 'clickhouse') {
this.temp.jdbcUrl = 'jdbc:clickhouse://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'ru.yandex.clickhouse.ClickHouseDriver'
} else if (datasource === 'hive') {
this.temp.jdbcUrl = 'jdbc:hive2://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'org.apache.hive.jdbc.HiveDriver'
this.hbase = this.mongodb = false
this.jdbc = true
}
this.getShowStrategy(datasource)
},
fetchData() {
this.listLoading = true
datasourceApi.list(this.listQuery).then(response => {
const { data } = response
const { total } = data.total
this.total = total
this.list = data.records
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
datasourceName: '',
datasourceGroup: 'Default',
jdbcUsername: '',
jdbcPassword: '',
jdbcUrl: '',
jdbcDriverClass: '',
comments: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
datasourceApi.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
testDataSource() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
datasourceApi.test(this.temp).then(response => {
if (response.data === false) {
this.$notify({
title: 'Fail',
message: response.data.msg,
type: 'fail',
duration: 2000
})
} else {
this.$notify({
title: 'Success',
message: 'Tested Successfully',
type: 'success',
duration: 2000
})
}
})
}
})
},
handleUpdate(row) {
this.getShowStrategy(row.datasource)
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
datasourceApi.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
getShowStrategy(datasource) {
if (datasource === 'hbase') {
this.jdbc = this.mongodb = false
this.hbase = true
} else if (datasource === 'mongodb') {
this.jdbc = this.hbase = false
this.mongodb = true
this.temp.jdbcUrl = 'mongodb://[username:password@]host1[:port1][,...hostN[:portN]]][/[database][?options]]'
} else {
this.hbase = this.mongodb = false
this.jdbc = true
}
},
handleDelete(row) {
console.log('删除')
const idList = []
idList.push(row.id)
// 拼成 idList=xx
// 多个比较麻烦,这里不处理
datasourceApi.deleted({ idList: row.id }).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
handleFetchPv(id) {
datasourceApi.fetched(id).then(response => {
this.pluginData = response
this.dialogPvVisible = true
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
},
changePass(value) {
this.visible = !(value === 'show')
}
}
}
</script>

View File

@@ -0,0 +1,758 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobDesc" placeholder="任务名称" style="width: 200px;" class="filter-item" />
<el-select v-model="projectIds" multiple placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.glueType" placeholder="任务类型" style="width: 200px" class="filter-item">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
style="width: 100%"
size="medium"
>
<el-table-column align="center" label="ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务名称" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center" width="120">
<template slot-scope="scope">
<span>{{ scope.row.jobCron }}</span>
</template>
</el-table-column>
<el-table-column label="路由策略" align="center" width="130">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
<el-table-column label="状态" align="center" width="150">
<template slot-scope="scope">
<el-switch
v-model="scope.row.triggerStatus"
active-color="#00A854"
active-text="启动"
:active-value="1"
inactive-color="#F04134"
inactive-text="停止"
:inactive-value="0"
@change="changeSwitch(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="注册节点" align="center" width="100">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="500"
@show="loadById(scope.row)"
>
<el-table :data="registerNode">
<el-table-column width="150" property="title" label="执行器名称" />
<el-table-column width="150" property="appName" label="分组标识" />
<el-table-column width="150" property="registryList" label="机器地址" />
</el-table>
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="下次触发时间" align="center" width="120">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="300"
@show="nextTriggerTime(scope.row)"
>
<h5 v-html="triggerNextTimes" />
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="执行状态" align="center" width="80">
<template slot-scope="scope"> {{ statusList.find(t => t.value === scope.row.lastHandleCode).label }}</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template slot-scope="{row}">
<!-- <el-dropdown type="primary" size="small"> -->
<!-- 操作 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
操作<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handlerExecute(row)">执行一次</el-dropdown-item>
<el-dropdown-item @click.native="handlerViewLog(row)">查询日志</el-dropdown-item>
<el-dropdown-item divided @click.native="handlerUpdate(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="handlerDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="1000px" :before-close="handleClose">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执行器" prop="jobGroup">
<el-select v-model="temp.jobGroup" placeholder="请选择执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务名称" prop="jobDesc">
<el-input v-model="temp.jobDesc" size="medium" placeholder="请输入任务描述" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="路由策略" prop="executorRouteStrategy">
<el-select v-model="temp.executorRouteStrategy" placeholder="请选择路由策略">
<el-option v-for="item in routeStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-dialog
title="提示"
:visible.sync="showCronBox"
width="60%"
append-to-body
>
<cron v-model="temp.jobCron" />
<span slot="footer" class="dialog-footer">
<el-button @click="showCronBox = false;">关闭</el-button>
<el-button type="primary" @click="showCronBox = false"> </el-button>
</span>
</el-dialog>
<el-form-item label="Cron" prop="jobCron">
<el-input v-model="temp.jobCron" auto-complete="off" placeholder="请输入Cron表达式">
<el-button v-if="!showCronBox" slot="append" icon="el-icon-turn-off" title="打开图形配置" @click="showCronBox = true" />
<el-button v-else slot="append" icon="el-icon-open" title="关闭图形配置" @click="showCronBox = false" />
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="阻塞处理" prop="executorBlockStrategy">
<el-select v-model="temp.executorBlockStrategy" placeholder="请选择阻塞处理策略">
<el-option v-for="item in blockStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报警邮件">
<el-input v-model="temp.alarmEmail" placeholder="请输入报警邮件,多个用逗号分隔" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任务类型" prop="glueType">
<el-select v-model="temp.glueType" placeholder="任务脚本类型">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失败重试次数">
<el-input-number v-model="temp.executorFailRetryCount" :min="0" :max="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属项目" prop="projectId">
<el-select v-model="temp.projectId" placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="超时时间(分钟)">
<el-input-number v-model="temp.executorTimeout" :min="0" :max="120" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="子任务">
<el-select v-model="temp.childJobId" multiple placeholder="子任务" value-key="id">
<el-option v-for="item in jobIdList" :key="item.id" :label="item.jobDesc" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" />
</el-row>
<el-row v-if="temp.glueType==='BEAN'" :gutter="20">
<el-col :span="12">
<el-form-item label="辅助参数" prop="incrementType">
<el-select v-model="temp.incrementType" placeholder="请选择参数类型" value="">
<el-option v-for="item in incrementTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 1" :gutter="20">
<el-col :span="12">
<el-form-item label="增量主键开始ID" prop="incStartId">
<el-input v-model="temp.incStartId" placeholder="首次增量使用" style="width: 56%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="ID增量参数" prop="replaceParam">
<el-input v-model="temp.replaceParam" placeholder="-DstartId='%s' -DendId='%s'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="reader数据源" prop="datasourceId">
<el-select v-model="temp.datasourceId" placeholder="reader数据源" class="filter-item">
<el-option v-for="item in dataSourceList" :key="item.id" :label="item.datasourceName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="reader表" prop="readerTable">
<el-input v-model="temp.readerTable" placeholder="读表的表名" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="主键" label-width="40px" prop="primaryKey">
<el-input v-model="temp.primaryKey" placeholder="请填写主键字段名" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 2" :gutter="20">
<el-col :span="12">
<el-form-item label="增量开始时间" prop="incStartTime">
<el-date-picker
v-model="temp.incStartTime"
type="datetime"
placeholder="首次增量使用"
format="yyyy-MM-dd HH:mm:ss"
style="width: 57%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="增量时间字段" prop="replaceParam">
<el-input v-model="temp.replaceParam" placeholder="-DlastTime='%s' -DcurrentTime='%s'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="增量时间格式" prop="replaceParamType">
<el-select v-model="temp.replaceParamType" placeholder="增量时间格式" @change="incStartTimeFormat">
<el-option v-for="item in replaceFormatTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 3" :gutter="20">
<el-col :span="12">
<el-form-item label="分区字段" prop="partitionField">
<el-input v-model="partitionField" placeholder="请输入分区字段" style="width: 56%" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="分区时间">
<el-select v-model="timeFormatType" placeholder="分区时间格式">
<el-option v-for="item in timeFormatTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-input-number v-model="timeOffset" :min="-20" :max="0" style="width: 65%" />
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN'" :gutter="20">
<el-col :span="24">
<el-form-item label="JVM启动参数">
<el-input v-model="temp.jvmParam" placeholder="-Xms1024m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<json-editor v-if="temp.glueType==='BEAN'" ref="jsonEditor" v-model="jobJson" />
<shell-editor v-if="temp.glueType==='GLUE_SHELL'" ref="shellEditor" v-model="glueSource" />
<python-editor v-if="temp.glueType==='GLUE_PYTHON'" ref="pythonEditor" v-model="glueSource" />
<powershell-editor v-if="temp.glueType==='GLUE_POWERSHELL'" ref="powershellEditor" v-model="glueSource" />
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import * as job from '@/api/dts/datax-job-info'
import waves from '@/directive/waves' // waves directive
import Cron from '@/components/Cron'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import JsonEditor from '@/components/JsonEditor'
import ShellEditor from '@/components/ShellEditor'
import PythonEditor from '@/components/PythonEditor'
import PowershellEditor from '@/components/PowershellEditor'
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import * as jobProjectApi from '@/api/dts/datax-job-project'
import { isJSON } from '@/utils/validate'
export default {
name: 'JobInfo',
components: { Pagination, JsonEditor, ShellEditor, PythonEditor, PowershellEditor, Cron },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
const validateIncParam = (rule, value, callback) => {
if (!value) {
callback(new Error('Increment parameters is required'))
}
callback()
}
const validatePartitionParam = (rule, value, callback) => {
if (!this.partitionField) {
callback(new Error('Partition parameters is required'))
}
callback()
}
return {
projectIds: '',
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
projectIds: '',
triggerStatus: -1,
jobDesc: '',
glueType: ''
},
showCronBox: false,
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
jobGroup: [{ required: true, message: 'jobGroup is required', trigger: 'change' }],
executorRouteStrategy: [{ required: true, message: 'executorRouteStrategy is required', trigger: 'change' }],
executorBlockStrategy: [{ required: true, message: 'executorBlockStrategy is required', trigger: 'change' }],
glueType: [{ required: true, message: 'jobType is required', trigger: 'change' }],
projectId: [{ required: true, message: 'projectId is required', trigger: 'change' }],
jobDesc: [{ required: true, message: 'jobDesc is required', trigger: 'blur' }],
jobProject: [{ required: true, message: 'jobProject is required', trigger: 'blur' }],
jobCron: [{ required: true, message: 'jobCron is required', trigger: 'blur' }],
incStartId: [{ trigger: 'blur', validator: validateIncParam }],
replaceParam: [{ trigger: 'blur', validator: validateIncParam }],
primaryKey: [{ trigger: 'blur', validator: validateIncParam }],
incStartTime: [{ trigger: 'change', validator: validateIncParam }],
replaceParamType: [{ trigger: 'change', validator: validateIncParam }],
partitionField: [{ trigger: 'blur', validator: validatePartitionParam }],
datasourceId: [{ trigger: 'change', validator: validateIncParam }],
readerTable: [{ trigger: 'blur', validator: validateIncParam }]
},
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: '',
glueType: '',
glueSource: '',
jobJson: '',
executorParam: '',
replaceParam: '',
replaceParamType: 'Timestamp',
jvmParam: '',
incStartTime: '',
partitionInfo: '',
incrementType: 0,
incStartId: '',
primaryKey: '',
projectId: '',
datasourceId: '',
readerTable: ''
},
resetTemp() {
// this.temp = this.$options.data().temp
this.jobJson = ''
this.glueSource = ''
this.timeOffset = 0
this.timeFormatType = 'yyyy-MM-dd'
this.partitionField = ''
},
executorList: '',
jobIdList: '',
jobProjectList: '',
dataSourceList: '',
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
glueTypes: [
{ value: 'BEAN', label: 'FlinkX任务' },
{ value: 'GLUE_SHELL', label: 'Shell任务' },
{ value: 'GLUE_PYTHON', label: 'Python任务' },
{ value: 'GLUE_POWERSHELL', label: 'PowerShell任务' }
],
incrementTypes: [
{ value: 0, label: '无' },
{ value: 1, label: '主键自增' },
{ value: 2, label: '时间自增' },
{ value: 3, label: 'HIVE分区' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
glueSource: '',
timeOffset: 0,
timeFormatType: 'yyyy-MM-dd',
partitionField: '',
timeFormatTypes: [
{ value: 'yyyy-MM-dd', label: 'yyyy-MM-dd' },
{ value: 'yyyyMMdd', label: 'yyyyMMdd' },
{ value: 'yyyy/MM/dd', label: 'yyyy/MM/dd' }
],
replaceFormatTypes: [
{ value: 'yyyy/MM/dd', label: 'yyyy/MM/dd' },
{ value: 'yyyy-MM-dd', label: 'yyyy-MM-dd' },
{ value: 'HH:mm:ss', label: 'HH:mm:ss' },
{ value: 'yyyy/MM/dd HH:mm:ss', label: 'yyyy/MM/dd HH:mm:ss' },
{ value: 'yyyy-MM-dd HH:mm:ss', label: 'yyyy-MM-dd HH:mm:ss' },
{ value: 'Timestamp', label: '时间戳' }
],
statusList: [
{ value: 500, label: '失败' },
{ value: 502, label: '失败(超时)' },
{ value: 200, label: '成功' },
{ value: 0, label: '无' }
]
}
},
created() {
this.fetchData()
this.getExecutor()
this.getJobIdList()
this.getJobProject()
this.getDataSourceList()
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done()
})
.catch(_ => {})
},
getExecutor() {
job.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
getJobIdList() {
job.getJobIdList().then(response => {
const { content } = response
this.jobIdList = content
})
},
getJobProject() {
jobProjectApi.getJobProjectList().then(response => {
this.jobProjectList = response.data
})
},
getDataSourceList() {
datasourceApi.getDataSourceList().then(response => {
this.dataSourceList = response
})
},
fetchData() {
this.listLoading = true
if (this.projectIds) {
this.listQuery.projectIds = this.projectIds.toString()
}
job.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
incStartTimeFormat(vData) {
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
if (this.temp.glueType === 'BEAN' && !isJSON(this.jobJson)) {
this.$notify({
title: 'Fail',
message: 'json格式错误',
type: 'error',
duration: 2000
})
return
}
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const auth = []
for (const i in this.temp.childJobId) {
auth.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = auth.toString()
}
this.temp.jobJson = this.jobJson
this.temp.glueSource = this.glueSource
this.temp.executorHandler = this.temp.glueType === 'BEAN' ? 'executorJobHandler' : ''
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
job.createJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerUpdate(row) {
// this.resetTemp()
this.temp = Object.assign({}, row) // copy obj
if (this.temp.jobJson) this.jobJson = JSON.parse(this.temp.jobJson)
this.glueSource = this.temp.glueSource
const arrchildSet = []
const arrJobIdList = []
if (this.jobIdList) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id !== this.temp.id) {
arrJobIdList.push(this.jobIdList[n])
}
}
this.JobIdList = arrJobIdList
}
if (this.temp.childJobId) {
const arrString = this.temp.childJobId.split(',')
for (const i in arrString) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id === parseInt(arrString[i])) {
arrchildSet.push(this.jobIdList[n])
}
}
}
this.temp.childJobId = arrchildSet
}
if (this.temp.partitionInfo) {
const partition = this.temp.partitionInfo.split(',')
this.partitionField = partition[0]
this.timeOffset = partition[1]
this.timeFormatType = partition[2]
}
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.temp.jobJson = typeof (this.jobJson) !== 'string' ? JSON.stringify(this.jobJson) : this.jobJson
if (this.temp.glueType === 'BEAN' && !isJSON(this.temp.jobJson)) {
this.$notify({
title: 'Fail',
message: 'json格式错误',
type: 'error',
duration: 2000
})
return
}
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const auth = []
for (const i in this.temp.childJobId) {
auth.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = auth.toString()
}
this.temp.executorHandler = this.temp.glueType === 'BEAN' ? 'executorJobHandler' : ''
this.temp.glueSource = this.glueSource
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
job.updateJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerDelete(row) {
this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
job.removeJob(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
})
// const index = this.list.indexOf(row)
},
handlerExecute(row) {
this.$confirm('确定执行吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const param = {}
param.jobId = row.id
param.executorParam = row.executorParam
job.triggerJob(param).then(response => {
this.$notify({
title: 'Success',
message: 'Execute Successfully',
type: 'success',
duration: 2000
})
})
})
},
// 查看日志
handlerViewLog(row) {
this.$router.push({ path: '/data/log', query: { jobId: row.id }})
},
handlerStart(row) {
job.startJob(row.id).then(response => {
this.$notify({
title: 'Success',
message: 'Start Successfully',
type: 'success',
duration: 2000
})
})
},
handlerStop(row) {
job.stopJob(row.id).then(response => {
this.$notify({
title: 'Success',
message: 'Start Successfully',
type: 'success',
duration: 2000
})
})
},
changeSwitch(row) {
row.triggerStatus === 1 ? this.handlerStart(row) : this.handlerStop(row)
},
nextTriggerTime(row) {
job.nextTriggerTime(row.jobCron).then(response => {
const { content } = response
this.triggerNextTimes = content.join('<br>')
})
},
loadById(row) {
executor.loadById(row.jobGroup).then(response => {
this.registerNode = []
const { content } = response
this.registerNode.push(content)
})
}
}
}
</script>
<style>
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-dropdown + .el-dropdown {
margin-left: 15px;
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobId" placeholder="全部" style="width: 200px" />
<el-select v-model="listQuery.jobGroup" placeholder="执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
<el-select v-model="listQuery.logStatus" placeholder="类型" style="width: 200px">
<el-option v-for="item in logStatusList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button
class="filter-item"
style="margin-left: 10px;"
type="primary"
icon="el-icon-edit"
@click="handlerDelete"
>
清除
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.jobId }}</template>
</el-table-column>
<el-table-column align="center" label="任务描述">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="调度时间" align="center">
<template slot-scope="scope">{{ scope.row.triggerTime }}</template>
</el-table-column>
<el-table-column label="执行结果" align="center">
<template slot-scope="scope"> <span :style="`color:${scope.row.handleCode==500?'red':''}`">{{ statusList.find(t => t.value === scope.row.handleCode).label }}</span></template>
</el-table-column>
<el-table-column label="操作" align="center" width="300">
<template slot-scope="{row}">
<el-button type="primary" @click="handleViewJobLog(row)">日志查看</el-button>
<el-button type="primary" @click="killRunningJob(row)">
终止任务
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="600px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="center" label-width="100px">
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="执行器">
<el-input size="medium" value="全部" :disabled="true" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="任务">
<el-input size="medium" value="全部" :disabled="true" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="执行器">
<el-select v-model="temp.deleteType" placeholder="请选择执行器" style="width: 230px">
<el-option v-for="item in deleteTypeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="deleteLog">
确定
</el-button>
</div>
</el-dialog>
<el-dialog title="日志查看" :visible.sync="dialogVisible" width="95%">
<div class="log-container">
<pre :loading="logLoading" v-text="logContent" />
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">
关闭
</el-button>
<el-button type="primary" @click="loadLog">
刷新日志
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as log from '@/api/dts/datax-job-log'
import * as job from '@/api/dts/datax-job-info'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'JobLog',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
dialogVisible: false,
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
jobId: '',
logStatus: -1,
filterTime: ''
},
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
executorList: '',
textMap: {
create: 'Clear'
},
rules: {},
temp: {
deleteType: 1,
jobGroup: 0,
jobId: 0
},
statusList: [
{ value: 500, label: '失败' },
{ value: 502, label: '失败(超时)' },
{ value: 200, label: '成功' },
{ value: 0, label: '无' }
],
deleteTypeList: [
{ value: 1, label: '清理一个月之前日志数据' },
{ value: 2, label: '清理三个月之前日志数据' },
{ value: 3, label: '清理六个月之前日志数据' },
{ value: 4, label: '清理一年之前日志数据' },
{ value: 5, label: '清理一千条以前日志数据' },
{ value: 6, label: '清理一万条以前日志数据' },
{ value: 7, label: '清理三万条以前日志数据' },
{ value: 8, label: '清理十万条以前日志数据' },
{ value: 9, label: '清理所有日志数据' }
],
logStatusList: [
{ value: -1, label: '全部' },
{ value: 1, label: '成功' },
{ value: 2, label: '失败' },
{ value: 3, label: '进行中' }
],
// 日志查询参数
jobLogQuery: {
executorAddress: '',
triggerTime: '',
id: '',
fromLineNum: 1
},
// 日志内容
logContent: '',
// 显示日志
logShow: false,
// 日志显示加载中效果
logLoading: false
}
},
created() {
this.fetchData()
this.getExecutor()
},
methods: {
fetchData() {
this.listLoading = true
const param = Object.assign({}, this.listQuery)
const urlJobId = this.$route.query.jobId
if (urlJobId > 0 && !param.jobId) {
param.jobId = urlJobId
} else if (!urlJobId && !param.jobId) {
param.jobId = 0
}
log.getList(param).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
getExecutor() {
job.getExecutorList().then(response => {
const { content } = response
this.executorList = content
const defaultParam = { id: 0, title: '全部' }
this.executorList.unshift(defaultParam)
this.listQuery.jobGroup = this.executorList[0].id
})
},
handlerDelete() {
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
deleteLog() {
log.clearLog(this.temp.jobGroup, this.temp.jobId, this.temp.deleteType).then(response => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
// 查看日志
handleViewJobLog(row) {
// const str = location.href.split('#')[0]
// window.open(`${str}#/ router的name `)
this.dialogVisible = true
this.jobLogQuery.executorAddress = row.executorAddress
this.jobLogQuery.id = row.id
this.jobLogQuery.triggerTime = Date.parse(row.triggerTime)
if (this.logShow === false) {
this.logShow = true
}
// window.open(`#/data/log?executorAddress=${this.jobLogQuery.executorAddress}&triggerTime=${this.jobLogQuery.triggerTime}&id=${this.jobLogQuery.id}&fromLineNum=${this.jobLogQuery.fromLineNum}`)
this.loadLog()
},
// 获取日志
loadLog() {
this.logLoading = true
log.viewJobLog(this.jobLogQuery.executorAddress, this.jobLogQuery.triggerTime, this.jobLogQuery.id,
this.jobLogQuery.fromLineNum).then(response => {
// 判断是否是 '\n',如果是表示显示完成,不重新加载
if (response.content.logContent === '\n') {
// this.jobLogQuery.fromLineNum = response.toLineNum - 20;
// 重新加载
// setTimeout(() => {
// this.loadLog()
// }, 2000);
} else {
this.logContent = response.content.logContent
}
this.logLoading = false
})
},
killRunningJob(row) {
log.killJob(row).then(response => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Kill Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>
<style lang="scss" scoped>
.log-container {
margin-bottom: 20px;
background: #f5f5f5;
width: 100%;
height: 400px;
overflow: scroll;
pre {
display: block;
padding: 10px;
margin: 0 0 10.5px;
word-break: break-all;
word-wrap: break-word;
color: #334851;
background-color: #f5f5f5;
// border: 1px solid #ccd1d3;
border-radius: 1px;
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div>
<div style="background-color: #304156; padding:10px 0; text-align:right;"><el-button type="primary" style="margin-right:20px;" @click="loadLog">刷新日志</el-button></div>
<div class="log-container">
<pre :loading="logLoading" v-text="logContent" />
</div>
</div>
</template>
<script>
import * as log from '@/api/dts/datax-job-log'
export default {
data() {
return {
logContent: '',
logLoading: false
}
},
created() {
this.loadLog()
},
methods: {
loadLog() {
this.logLoading = true
log.viewJobLog(this.$route.query.executorAddress, this.$route.query.triggerTime, this.$route.query.id, this.$route.query.fromLineNum).then(response => {
// 判断是否是 '\n',如果是表示显示完成,不重新加载
if (response.content.logContent === '\n') {
// this.jobLogQuery.fromLineNum = response.toLineNum - 20;
// 重新加载
// setTimeout(() => {
// this.loadLog()
// }, 2000);
} else {
this.logContent = response.content.logContent
}
this.logLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.log-container {
background: #f5f5f5;
height: 500px;
overflow: scroll;
margin:20px;
border:1px solid #ddd;
pre {
display: block;
padding: 10px;
margin: 0 0 10.5px;
word-break: break-all;
word-wrap: break-word;
color: #334851;
background-color: #f5f5f5;
// border: 1px solid #ccd1d3;
border-radius: 1px;
}
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.searchVal"
clearable
placeholder="项目名称"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index+1 }}</template>
</el-table-column>
<el-table-column label="项目名称" align="center">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="项目描述" align="center">
<template slot-scope="scope">{{ scope.row.description }}</template>
</el-table-column>
<el-table-column label="所属用户" width="200" align="center">
<template slot-scope="scope">{{ scope.row.userName }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="200" align="center">
<template slot-scope="scope">{{ scope.row.createTime }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!=='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.pageNo"
:limit.sync="listQuery.pageSize"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px">
<el-form-item label="项目名称" prop="name">
<el-input v-model="temp.name" placeholder="项目名称" style="width: 40%" />
</el-form-item>
<el-form-item label="项目描述" prop="description">
<el-input v-model="temp.description" placeholder="项目描述" style="width: 40%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPluginVisible" title="Reading statistics">
<el-table :data="pluginData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel" />
<el-table-column prop="pv" label="Pv" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as jobProjectApi from '@/api/dts/datax-job-project'
import waves from '@/directive/waves'
import Pagination from '@/components/Pagination'
export default {
name: 'JobProject',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
pageNo: 1,
pageSize: 10,
searchVal: ''
},
pluginTypeOptions: ['reader', 'writer'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
name: [{ required: true, message: 'this is required', trigger: 'blur' }],
description: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
temp: {
id: undefined,
name: '',
description: ''
},
visible: true
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
jobProjectApi.list(this.listQuery).then(response => {
const { records, total } = response.data
this.total = parseInt(total || 0)
this.list = records
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
name: '',
description: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
jobProjectApi.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
jobProjectApi.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
console.log('删除')
const idList = []
idList.push(row.id)
jobProjectApi.deleted({ idList: row.id }).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>

View File

@@ -0,0 +1,529 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobDesc" placeholder="任务描述" style="width: 200px;" class="filter-item" />
<el-select v-model="projectIds" multiple placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope">
<span>{{ scope.row.jobCron }}</span>
</template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
<el-table-column label="修改用户" align="center" width="80">
<template slot-scope="scope">{{ scope.row.userName }}</template>
</el-table-column>
<el-table-column label="注册节点" align="center">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="500"
@show="loadById(scope.row)"
>
<el-table :data="registerNode">
<el-table-column width="150" property="title" label="执行器名称" />
<el-table-column width="150" property="appName" label="appName" />
<el-table-column width="150" property="registryList" label="机器地址" />
</el-table>
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="下次触发时间" align="center">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="300"
@show="nextTriggerTime(scope.row)"
>
<h5 v-html="triggerNextTimes" />
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="{row}">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
操作<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item divided @click.native="handlerUpdate(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="handlerDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="1000px" :before-close="handleClose">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执行器" prop="jobGroup">
<el-select v-model="temp.jobGroup" placeholder="请选择执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务描述" prop="jobDesc">
<el-input v-model="temp.jobDesc" size="medium" placeholder="请输入任务描述" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="路由策略" prop="executorRouteStrategy">
<el-select v-model="temp.executorRouteStrategy" placeholder="请选择路由策略">
<el-option v-for="item in routeStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-dialog
title="提示"
:visible.sync="showCronBox"
width="60%"
append-to-body
>
<cron v-model="temp.jobCron" />
<span slot="footer" class="dialog-footer">
<el-button @click="showCronBox = false;">关闭</el-button>
<el-button type="primary" @click="showCronBox = false"> </el-button>
</span>
</el-dialog>
<el-form-item label="Cron" prop="jobCron">
<el-input v-model="temp.jobCron" auto-complete="off" placeholder="请输入Cron表达式">
<el-button v-if="!showCronBox" slot="append" icon="el-icon-turn-off" title="打开图形配置" @click="showCronBox = true" />
<el-button v-else slot="append" icon="el-icon-open" title="关闭图形配置" @click="showCronBox = false" />
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="阻塞处理" prop="executorBlockStrategy">
<el-select v-model="temp.executorBlockStrategy" placeholder="请选择阻塞处理策略">
<el-option v-for="item in blockStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报警邮件">
<el-input v-model="temp.alarmEmail" placeholder="请输入报警邮件,多个用逗号分隔" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任务类型" prop="glueType">
<el-select v-model="temp.glueType" placeholder="任务脚本类型">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失败重试次数">
<el-input-number v-model="temp.executorFailRetryCount" :min="0" :max="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属项目" prop="projectId">
<el-select v-model="temp.projectId" placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="超时时间(分钟)">
<el-input-number v-model="temp.executorTimeout" :min="0" :max="120" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="子任务">
<el-select v-model="temp.childJobId" multiple placeholder="子任务" value-key="id">
<el-option v-for="item in jobIdList" :key="item.id" :label="item.jobDesc" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" />
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="JVM启动参数">
<el-input
v-model="temp.jvmParam"
placeholder="-Xms1024m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import Cron from '@/components/Cron'
import * as jobTemp from '@/api/dts/datax-job-template'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import * as jobProjectApi from '@/api/dts/datax-job-project'
import * as job from '@/api/dts/datax-job-info'
export default {
name: 'JobTemplate',
components: { Pagination, Cron },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
const validateIncParam = (rule, value, callback) => {
if (!value) {
callback(new Error('Increment parameters is required'))
}
callback()
}
const validatePartitionParam = (rule, value, callback) => {
if (!this.partitionField) {
callback(new Error('Partition parameters is required'))
}
callback()
}
return {
projectIds: '',
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0,
projectIds: ''
},
showCronBox: false,
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
jobGroup: [{ required: true, message: 'jobGroup is required', trigger: 'change' }],
executorRouteStrategy: [{ required: true, message: 'executorRouteStrategy is required', trigger: 'change' }],
executorBlockStrategy: [{ required: true, message: 'executorBlockStrategy is required', trigger: 'change' }],
jobDesc: [{ required: true, message: 'jobDesc is required', trigger: 'blur' }],
jobProject: [{ required: true, message: 'jobProject is required', trigger: 'blur' }],
jobCron: [{ required: true, message: 'jobCron is required', trigger: 'blur' }],
incStartId: [{ trigger: 'blur', validator: validateIncParam }],
replaceParam: [{ trigger: 'blur', validator: validateIncParam }],
primaryKey: [{ trigger: 'blur', validator: validateIncParam }],
incStartTime: [{ trigger: 'change', validator: validateIncParam }],
replaceParamType: [{ trigger: 'change', validator: validateIncParam }],
partitionField: [{ trigger: 'blur', validator: validatePartitionParam }],
datasourceId: [{ trigger: 'change', validator: validateIncParam }],
readerTable: [{ trigger: 'blur', validator: validateIncParam }],
projectId: [{ required: true, message: 'projectId is required', trigger: 'change' }]
},
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: 'RANDOM',
executorBlockStrategy: 'DISCARD_LATER',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
executorParam: '',
jvmParam: '',
projectId: '',
datasourceId: 0,
readerTable: ''
},
resetTemp() {
this.temp = this.$options.data().temp
},
executorList: '',
jobIdList: '',
jobProjectList: '',
dataSourceList: '',
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
glueTypes: [
{ value: 'BEAN', label: 'FlinkX任务' }
],
triggerNextTimes: '',
registerNode: []
}
},
created() {
this.fetchData()
this.getExecutor()
this.getJobIdList()
this.getJobProject()
this.getDataSourceList()
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done()
})
.catch(_ => {})
},
getExecutor() {
jobTemp.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
getJobIdList() {
job.getJobIdList().then(response => {
const { content } = response
this.jobIdList = content
})
},
getJobProject() {
jobProjectApi.getJobProjectList().then(response => {
this.jobProjectList = response.data
})
},
getDataSourceList() {
datasourceApi.getDataSourceList().then(response => {
this.dataSourceList = response
})
},
fetchData() {
this.listLoading = true
if (this.projectIds) {
this.listQuery.projectIds = this.projectIds.toString()
}
jobTemp.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCreate() {
//this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.temp.jobGroup = this.executorList[0]['id']
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const childJobs = []
for (const i in this.temp.childJobId) {
childJobs.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = childJobs.toString()
}
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
jobTemp.createJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerUpdate(row) {
//this.resetTemp()
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
const arrchildSet = []
const arrJobIdList = []
if (this.JobIdList) {
for (const n in this.JobIdList) {
if (this.JobIdList[n].id !== this.temp.id) {
arrJobIdList.push(this.JobIdList[n])
}
}
this.JobIdList = arrJobIdList
}
if (this.temp.childJobId) {
const arrString = this.temp.childJobId.split(',')
for (const i in arrString) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id === parseInt(arrString[i])) {
arrchildSet.push(this.jobIdList[n])
}
}
}
this.temp.childJobId = arrchildSet
}
if (this.temp.partitionInfo) {
const partition = this.temp.partitionInfo.split(',')
this.partitionField = partition[0]
this.timeOffset = partition[1]
this.timeFormatType = partition[2]
}
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const childJobs = []
for (const i in this.temp.childJobId) {
childJobs.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = childJobs.toString()
}
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
jobTemp.updateJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerDelete(row) {
this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
jobTemp.removeJob(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
})
// const index = this.list.indexOf(row)
},
nextTriggerTime(row) {
jobTemp.nextTriggerTime(row.jobCron).then(response => {
const { content } = response
this.triggerNextTimes = content.join('<br>')
})
},
loadById(row) {
executor.loadById(row.jobGroup).then(response => {
this.registerNode = []
const { content } = response
this.registerNode.push(content)
})
}
}
}
</script>
<style>
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-dropdown + .el-dropdown {
margin-left: 15px;
}
</style>

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,76 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="80px" :model="readerForm">
<el-form-item label="源端表">
<el-checkbox
v-model="readerForm.lcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="lHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.ltables" @change="lHandleCheckedChange">
<el-checkbox v-for="c in fromTablesList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="目标表">
<el-checkbox
v-model="readerForm.rcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 20px 0;" />
<el-checkbox-group v-model="readerForm.rtables" @change="rHandleCheckedChange">
<el-checkbox v-for="c in toTablesList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'TableMapper',
data() {
return {
mapperJson: {},
fromTablesList: [],
toTablesList: [],
readerForm: {
ltables: [],
rtables: [],
lcheckAll: false,
rcheckAll: false,
isIndeterminate: true
}
}
},
mounted() {
},
methods: {
lHandleCheckAllChange(val) {
this.readerForm.ltables = val ? this.fromTablesList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckAllChange(val) {
this.readerForm.rtables = val ? this.toTablesList : []
this.readerForm.isIndeterminate = false
},
lHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.fromTablesList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromTablesList.length
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.toTablesList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.toTablesList.length
},
getLTables() {
return this.readerForm.ltables
},
getRTables() {
return this.readerForm.rtables
}
}
}
</script>

View File

@@ -0,0 +1,250 @@
<template>
<div class="app-container">
<div class="build-container">
<el-steps :active="active" finish-status="success">
<el-step title="步骤 1" description="构建reader">1</el-step>
<el-step title="步骤 2" description="构建writer">2</el-step>
<el-step title="步骤 3" description="表映射">3</el-step>
<el-step title="步骤 4" description="批量创建">4</el-step>
</el-steps>
<div v-show="active===1" class="step1">
<Reader ref="reader" />
</div>
<div v-show="active===2" class="step2">
<Writer ref="writer" />
</div>
<div v-show="active===3" class="step3">
<Mapper ref="mapper" />
</div>
<div v-show="active===4" class="step4">
<el-button type="primary" @click="handleJobTemplateSelectDrawer">{{ jobTemplate ? jobTemplate : "1.选择模板" }}</el-button>
<el-button type="primary" @click="createJob">2.批量创建任务</el-button>
(步骤选择模板->批量创建任务)
<el-drawer
ref="jobTemplateSelectDrawer"
title="选择模板"
:visible.sync="jobTemplateSelectDrawer"
direction="rtl"
size="50%"
>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
destroy-on-close="true"
@current-change="handleCurrentChange"
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center">
<template slot-scope="scope">{{ scope.row.jobProject }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope"><span>{{ scope.row.jobCron }}</span></template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
</el-drawer>
<div style="margin-bottom: 20px;" />
</div>
<el-button :disabled="active===1" style="margin-top: 12px;" @click="last">上一步</el-button>
<el-button v-show="active!==4" type="primary" style="margin-top: 12px;margin-bottom: 12px;" @click="next">下一步</el-button>
</div>
</div>
</template>
<script>
import * as jobTemplate from '@/api/dts/datax-job-template'
import * as job from '@/api/dts/datax-job-info'
import Pagination from '@/components/Pagination'
import Reader from './reader'
import Writer from './writer'
import clip from '@/utils/clipboard'
import Mapper from './mapper'
export default {
name: 'JsonBuild',
components: { Reader, Writer, Pagination, Mapper },
data() {
return {
configJson: '',
active: 1,
jobTemplate: '',
jobTemplateSelectDrawer: false,
list: null,
currentRow: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0
},
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
jobJson: '',
executorParam: '',
replaceParam: '',
jvmParam: '',
incStartTime: '',
templateId: 0
}
}
},
created() {
// this.getJdbcDs()
},
methods: {
next() {
const fromTableList = this.$refs.reader.getData().tables
const toTableList = this.$refs.writer.getData().tables
// const fromTableName = this.$refs.reader.getData().tableName
// 第一步 reader 判断是否已选字段
if (this.active === 1) {
// 实现第一步骤读取的表和字段直接带到第二步骤
// this.$refs.writer.sendTableNameAndColumns(fromTableName, fromColumnList)
// 取子组件的数据
// console.info(this.$refs.reader.getData())
this.active++
} else {
// 将第一步和第二步得到的字段名字发送到第三步
if (this.active === 2) {
this.$refs.mapper.sendTables(fromTableList, toTableList)
}
if (this.active !== 4) {
this.active++
}
}
},
last() {
if (this.active > 1) {
this.active--
}
},
// Create job
createJob() {
const readerData = this.$refs.reader.getData()
const writeData = this.$refs.writer.getData()
const readerTables = this.$refs.mapper.getLTables()
const writerTables = this.$refs.mapper.getRTables()
const rdbmsReader = {
readerSplitPk: readerData.splitPk
}
const rdbmsWriter = {}
const obj = {
readerDatasourceId: readerData.datasourceId,
readerTables: readerTables,
writerDatasourceId: writeData.datasourceId,
writerTables: writerTables,
rdbmsReader: rdbmsReader,
rdbmsWriter: rdbmsWriter,
templateId: this.temp.templateId
}
// 调api
job.batchAddJob(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
// 切回第一步
this.active = 1
})
},
handleCopy(text, event) {
clip(this.configJson, event)
this.$message({
message: 'copy success',
type: 'success'
})
},
handleJobTemplateSelectDrawer() {
this.jobTemplateSelectDrawer = !this.jobTemplateSelectDrawer
if (this.jobTemplateSelectDrawer) {
this.fetchData()
this.getExecutor()
}
},
getReaderData() {
return this.$refs.reader.getData()
},
getExecutor() {
jobTemplate.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
fetchData() {
this.listLoading = true
jobTemplate.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCurrentChange(val) {
this.temp = Object.assign({}, val)
this.temp.id = undefined
this.temp.jobDesc = this.getReaderData().tableName
this.$refs.jobTemplateSelectDrawer.closeDrawer()
this.jobTemplate = val.id + '(' + val.jobDesc + ')'
this.temp.templateId = val.id
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="app-container">
<TableMapper ref="mapper" />
</div>
</template>
<script>
import TableMapper from './components/tableMapper'
export default {
name: 'Mapper',
components: { TableMapper },
methods: {
sendTables(fromTablesList, toTablesList) {
this.$refs.mapper.fromTablesList = fromTablesList
this.$refs.mapper.toTablesList = toTablesList
},
getLTables() {
return this.$refs.mapper.getLTables()
},
getRTables() {
return this.$refs.mapper.getRTables()
}
}
}
</script>

View File

@@ -0,0 +1,27 @@
<template>
<div class="app-container">
<TableReader ref="tablereader" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import TableReader from './reader/tableReader'
export default {
name: 'Reader',
components: { TableReader },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
return this.$refs.tablereader.getData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
}
}
}
</script>

View File

@@ -0,0 +1,170 @@
<template>
<div class="app-container">
<el-form label-position="right" label-width="120px" :model="readerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable style="width: 300px" @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' ||dataSource==='hana'" label="Schema">
<el-select v-model="readerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="切分字段:">
<el-input v-model="readerForm.splitPk" placeholder="切分主键" style="width: 13%" />
</el-form-item>
<el-form-item label="数据库表名:">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选
</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.tables" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rTbList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'TableReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
rDsList: [],
rTbList: [],
schemaList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tables: [],
checkAll: false,
isIndeterminate: true,
splitPk: '',
tableSchema: ''
},
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('reader')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'reader') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.readerForm.datasourceId,
tableSchema: this.readerForm.tableSchema
}
} else {
obj = {
datasourceId: this.readerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
if (response) {
this.rTbList = response.data
}
})
}
},
getSchema() {
const obj = {
datasourceId: this.readerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.readerForm.tableSchema = e
// 获取可用表
this.getTables('reader')
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.tables = val ? this.rTbList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rTbList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rTbList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,38 @@
<template>
<div class="app-container">
<TableWriter ref="tablewriter" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import TableWriter from './writer/tableWriter'
export default {
name: 'Writer',
components: { TableWriter },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
return this.$refs.tablewriter.getData()
},
getTableName() {
return this.$refs.tablewriter.getTableName()
},
getReaderData() {
return this.$parent.getReaderData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
},
sendTableNameAndColumns(fromTableName, fromColumnList) {
this.$refs.hivewriter.fromTableName = fromTableName
this.$refs.hivewriter.fromColumnList = fromColumnList
}
}
}
</script>

View File

@@ -0,0 +1,181 @@
<template>
<div>
<el-form label-position="right" label-width="150px" :model="writerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
style="width: 300px;"
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' ||dataSource==='hana'" label="Schema">
<el-select v-model="writerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<div style="margin: 5px 0;" />
<el-form-item label="数据库表名:">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.tables" @change="wHandleCheckedChange">
<el-checkbox v-for="c in wTbList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'TableWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
wDsList: [],
schemaList: [],
fromTableName: '',
wTbList: [],
dataSource: '',
createTableName: '',
writerForm: {
datasourceId: undefined,
tables: [],
checkAll: false,
isIndeterminate: true,
tableSchema: ''
},
readerForm: this.getReaderData(),
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('writer')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'writer') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.writerForm.datasourceId,
tableSchema: this.writerForm.tableSchema
}
} else {
obj = {
datasourceId: this.writerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response.data
})
}
},
getSchema() {
const obj = {
datasourceId: this.writerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.writerForm.tableSchema = e
// 获取可用表
this.getTables('writer')
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
wHandleCheckAllChange(val) {
this.writerForm.tables = val ? this.wTbList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.wTbList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.wTbList.length
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.createTableName
}
dsQueryApi.createTable(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,76 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="80px" :model="readerForm">
<el-form-item label="源端字段">
<el-checkbox
v-model="readerForm.lcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="lHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.lcolumns" @change="lHandleCheckedChange">
<el-checkbox v-for="c in fromColumnsList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="目标字段">
<el-checkbox
v-model="readerForm.rcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 20px 0;" />
<el-checkbox-group v-model="readerForm.rcolumns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in toColumnsList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'FieldMapper',
data() {
return {
mapperJson: {},
fromColumnsList: [],
toColumnsList: [],
readerForm: {
lcolumns: [],
rcolumns: [],
lcheckAll: false,
rcheckAll: false,
isIndeterminate: true
}
}
},
mounted() {
},
methods: {
lHandleCheckAllChange(val) {
this.readerForm.lcolumns = val ? this.fromColumnsList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckAllChange(val) {
this.readerForm.rcolumns = val ? this.toColumnsList : []
this.readerForm.isIndeterminate = false
},
lHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.fromColumnsList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnsList.length
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.toColumnsList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.toColumnsList.length
},
getLColumns() {
return this.readerForm.lcolumns
},
getRColumns() {
return this.readerForm.rcolumns
}
}
}
</script>

View File

@@ -0,0 +1,299 @@
<template>
<div class="app-container">
<div class="build-container">
<el-steps :active="active" finish-status="success">
<el-step title="步骤 1" description="构建reader">1</el-step>
<el-step title="步骤 2" description="构建writer">2</el-step>
<el-step title="步骤 3" description="字段映射">3</el-step>
<el-step title="步骤 4" description="构建">4</el-step>
</el-steps>
<div v-show="active===1" class="step1">
<Reader ref="reader" />
</div>
<div v-show="active===2" class="step2">
<Writer ref="writer" />
</div>
<div v-show="active===3" class="step3">
<Mapper ref="mapper" />
</div>
<div v-show="active===4" class="step4">
<el-button type="primary" @click="buildJson">1.构建</el-button>
<el-button type="primary" @click="handleJobTemplateSelectDrawer">{{ jobTemplate ? jobTemplate : "2.选择模板" }}</el-button>
<el-button type="info" @click="handleCopy(inputData,$event)">复制json</el-button>
(步骤构建->选择模板->下一步)
<el-drawer
ref="jobTemplateSelectDrawer"
title="选择模板"
:visible.sync="jobTemplateSelectDrawer"
direction="rtl"
size="50%"
>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
destroy-on-close="true"
@current-change="handleCurrentChange"
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope"><span>{{ scope.row.jobCron }}</span></template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
</el-drawer>
<div style="margin-bottom: 20px;" />
<json-editor v-show="active===4" ref="jsonEditor" v-model="configJson" />
</div>
<el-button :disabled="active===1" style="margin-top: 12px;" @click="last">上一步</el-button>
<el-button type="primary" style="margin-top: 12px;margin-bottom: 12px;" @click="next">下一步</el-button>
</div>
</div>
</template>
<script>
import * as dataxJsonApi from '@/api/dts/datax-json'
import * as jobTemplate from '@/api/dts/datax-job-template'
import * as job from '@/api/dts/datax-job-info'
import Pagination from '@/components/Pagination'
import JsonEditor from '@/components/JsonEditor'
import Reader from './reader'
import Writer from './writer'
import clip from '@/utils/clipboard'
import Mapper from './mapper'
export default {
name: 'JsonBuild',
components: { Reader, Writer, Pagination, JsonEditor, Mapper },
data() {
return {
configJson: '',
active: 1,
jobTemplate: '',
jobTemplateSelectDrawer: false,
list: null,
currentRow: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0
},
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
jobJson: '',
executorParam: '',
replaceParam: '',
jvmParam: '',
incStartTime: ''
}
}
},
created() {
// this.getJdbcDs()
},
methods: {
next() {
const fromColumnList = this.$refs.reader.getData().columns
const toColumnsList = this.$refs.writer.getData().columns
// const fromTableName = this.$refs.reader.getData().tableName
// 第一步 reader 判断是否已选字段
if (this.active === 1) {
// 实现第一步骤读取的表和字段直接带到第二步骤
// this.$refs.writer.sendTableNameAndColumns(fromTableName, fromColumnList)
// 取子组件的数据
// console.info(this.$refs.reader.getData())
this.active++
} else {
// 将第一步和第二步得到的字段名字发送到第三步
if (this.active === 2) {
this.$refs.mapper.sendColumns(fromColumnList, toColumnsList)
}
if (this.active === 4) {
this.temp.jobJson = this.configJson
job.createJob(this.temp).then(() => {
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
// 切回第一步
this.active = 1
})
} else {
this.active++
}
}
},
last() {
if (this.active > 1) {
this.active--
}
},
// 构建json
buildJson() {
const readerData = this.$refs.reader.getData()
const writeData = this.$refs.writer.getData()
const readerColumns = this.$refs.mapper.getLColumns()
const writerColumns = this.$refs.mapper.getRColumns()
const hiveReader = {
readerPath: readerData.path,
readerDefaultFS: readerData.defaultFS,
readerFileType: readerData.fileType,
readerFieldDelimiter: readerData.fieldDelimiter,
readerSkipHeader: readerData.skipHeader
}
const hiveWriter = {
writerDefaultFS: writeData.defaultFS,
writerFileType: writeData.fileType,
writerPath: writeData.path,
writerFileName: writeData.fileName,
writeMode: writeData.writeMode,
writeFieldDelimiter: writeData.fieldDelimiter
}
const hbaseReader = {
readerMode: readerData.mode,
readerMaxVersion: readerData.maxVersion,
readerRange: readerData.range
}
const hbaseWriter = {
writerMode: writeData.mode,
writerRowkeyColumn: writeData.rowkeyColumn,
writerVersionColumn: writeData.versionColumn,
writeNullMode: writeData.nullMode
}
const mongoDBReader = {}
const mongoDBWriter = {
upsertInfo: writeData.upsertInfo
}
const rdbmsReader = {
readerSplitPk: readerData.splitPk,
whereParams: readerData.where,
querySql: readerData.querySql
}
const rdbmsWriter = {
preSql: writeData.preSql,
postSql: writeData.postSql
}
const obj = {
readerDatasourceId: readerData.datasourceId,
readerTables: [readerData.tableName],
readerColumns: readerColumns,
writerDatasourceId: writeData.datasourceId,
writerTables: [writeData.tableName],
writerColumns: writerColumns,
hiveReader: hiveReader,
hiveWriter: hiveWriter,
rdbmsReader: rdbmsReader,
rdbmsWriter: rdbmsWriter,
hbaseReader: hbaseReader,
hbaseWriter: hbaseWriter,
mongoDBReader: mongoDBReader,
mongoDBWriter: mongoDBWriter
}
// 调api
dataxJsonApi.buildJobJson(obj).then(response => {
this.configJson = JSON.parse(response.data)
})
},
handleCopy(text, event) {
clip(this.configJson, event)
this.$message({
message: 'copy success',
type: 'success'
})
},
handleJobTemplateSelectDrawer() {
this.jobTemplateSelectDrawer = !this.jobTemplateSelectDrawer
if (this.jobTemplateSelectDrawer) {
this.fetchData()
this.getExecutor()
}
},
getReaderData() {
return this.$refs.reader.getData()
},
getExecutor() {
jobTemplate.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
fetchData() {
this.listLoading = true
jobTemplate.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCurrentChange(val) {
this.temp = Object.assign({}, val)
this.temp.id = undefined
this.temp.jobDesc = this.getReaderData().tableName
this.$refs.jobTemplateSelectDrawer.closeDrawer()
this.jobTemplate = val.id + '(' + val.jobDesc + ')'
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="app-container">
<FieldMapper ref="mapper" />
</div>
</template>
<script>
import FieldMapper from './components/fieldMapper'
export default {
name: 'Mapper',
components: { FieldMapper },
methods: {
sendColumns(fromColumnsList, toColumnsList) {
this.$refs.mapper.fromColumnsList = fromColumnsList
this.$refs.mapper.toColumnsList = toColumnsList
},
getLColumns() {
return this.$refs.mapper.getLColumns()
},
getRColumns() {
return this.$refs.mapper.getRColumns()
}
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div class="app-container">
<RDBMSReader v-show="dataSource!=='hive' && dataSource!=='hbase' && dataSource!=='mongodb'" ref="rdbmsreader" @selectDataSource="showDataSource" />
<HiveReader v-show="dataSource==='hive'" ref="hivereader" @selectDataSource="showDataSource" />
<HBaseReader v-show="dataSource==='hbase'" ref="hbasereader" @selectDataSource="showDataSource" />
<MongoDBReader v-show="dataSource==='mongodb'" ref="mongodbreader" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import RDBMSReader from './reader/RDBMSReader'
import HiveReader from './reader/HiveReader'
import HBaseReader from './reader/HBaseReader'
import MongoDBReader from './reader/MongoDBReader'
export default {
name: 'Reader',
components: { RDBMSReader, HiveReader, HBaseReader, MongoDBReader },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
if (this.dataSource === 'hive') {
return this.$refs.hivereader.getData()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasereader.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbreader.getData()
} else {
return this.$refs.rdbmsreader.getData()
}
},
showDataSource(data) {
this.dataSource = data
this.getData()
}
}
}
</script>

View File

@@ -0,0 +1,203 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="表" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="mode" prop="mode">
<el-select v-model="readerForm.mode" placeholder="读取hbase的模式">
<el-option v-for="item in modeTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="maxVersion">
<el-input v-model="readerForm.maxVersion" placeholder="多版本模式下读取的版本数,取值只能为1或者大于1的数字" style="width: 50%" />
</el-form-item>
<el-form-item label="range">
<el-input v-model="readerForm.range.startRowkey" placeholder="startRowkey指定开始rowkey" style="width: 50%" />
</el-form-item>
<el-form-item>
<el-input v-model="readerForm.range.endRowkey" placeholder="endRowkey指定结束rowkey" style="width: 50%" />
</el-form-item>
<el-form-item>
<el-select v-model="readerForm.range.isBinaryRowkey" placeholder="转换方式">
<el-option v-for="item in binaryRowkeyTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'HBaseReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
mode: '',
maxVersion: '',
range: {
startRowkey: '',
endRowkey: '',
isBinaryRowkey: ''
}
},
modeTypes: [
{ value: 'normal', label: 'normal' },
{ value: 'multiVersionFixedColumn', label: 'multiVersionFixedColumn' }
],
binaryRowkeyTypes: [
{ value: 'true', label: '调用Bytes.toBytesBinary(rowkey)' },
{ value: 'false', label: '调用Bytes.toBytes(rowkey)' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hbaseReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hbaseReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,206 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="表" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="path" prop="path">
<el-input v-model="readerForm.path" :autosize="{ minRows: 2, maxRows: 20}" type="textarea" placeholder="要读取的文件路径,如果要读取多个文件,可以使用正则表达式'*'" style="width: 42%" />
</el-form-item>
<el-form-item label="defaultFS" prop="defaultFS">
<el-input v-model="readerForm.defaultFS" placeholder="Hadoop hdfs文件系统namenode节点地址" style="width: 42%" />
</el-form-item>
<el-form-item label="fileType" prop="fileType">
<el-select v-model="readerForm.fileType" placeholder="文件的类型">
<el-option v-for="item in fileTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="fieldDelimiter" prop="fieldDelimiter">
<el-input v-model="readerForm.fieldDelimiter" placeholder="读取的字段分隔符" style="width: 42%" />
</el-form-item>
<el-form-item label="skipHeader">
<el-select v-model="readerForm.skipHeader" placeholder="是否跳过表头">
<el-option v-for="item in skipHeaderTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'HiveReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
path: '',
defaultFS: '',
fileType: '',
fieldDelimiter: '',
skipHeader: ''
},
rules: {
path: [{ required: true, message: 'this is required', trigger: 'blur' }],
defaultFS: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileType: [{ required: true, message: 'this is required', trigger: 'change' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
fileTypes: [
{ value: 'text', label: 'text' },
{ value: 'orc', label: 'orc' },
{ value: 'rc', label: 'rc' },
{ value: 'seq', label: 'seq' },
{ value: 'csv', label: 'csv' }
],
skipHeaderTypes: [
{ value: 'true', label: '读取跳过表头' },
{ value: 'false', label: '读取包含表头' }
]
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hiveReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hiveReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,169 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="文档" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'MongoDBReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true
},
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('mongodbReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'mongodbReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,226 @@
<template>
<div class="app-container">
<el-form label-position="right" label-width="120px" :model="readerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable style="width: 300px" @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' || dataSource==='sqlserver' || dataSource==='hana'" label="Schema" prop="tableSchema">
<el-select v-model="readerForm.tableSchema" allow-create default-first-option filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="数据库表名:" prop="tableName">
<el-select v-model="readerForm.tableName" allow-create default-first-option filterable style="width: 300px" @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="SQL语句">
<el-input v-model="readerForm.querySql" :autosize="{ minRows: 3, maxRows: 20}" type="textarea" placeholder="sql查询一般用于多表关联查询时才用" style="width: 42%" />
<el-button type="primary" @click.prevent="getColumns('reader')">解析字段</el-button>
</el-form-item>
<el-form-item label="切分字段:">
<el-input v-model="readerForm.splitPk" placeholder="切分主键" style="width: 13%" />
</el-form-item>
<el-form-item label="表所有字段:">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选
</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="where条件" prop="where">
<el-input v-model="readerForm.where" placeholder="where条件不需要再加where" type="textarea" style="width: 42%" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'RDBMSReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
rDsList: [],
rTbList: [],
schemaList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
where: '',
querySql: '',
checkAll: false,
isIndeterminate: true,
splitPk: '',
tableSchema: ''
},
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }],
tableSchema: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('rdbmsReader')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'rdbmsReader') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.readerForm.datasourceId,
tableSchema: this.readerForm.tableSchema
}
} else {
obj = {
datasourceId: this.readerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
if (response) {
this.rTbList = response.data
}
})
}
},
getSchema() {
const obj = {
datasourceId: this.readerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.readerForm.tableSchema = e
// 获取可用表
this.getTables('rdbmsReader')
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response.data
this.readerForm.columns = response.data
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
if (this.readerForm.querySql !== '') {
this.getColumnsByQuerySql()
} else {
this.getTableColumns()
}
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,60 @@
<template>
<div class="app-container">
<RDBMSWriter v-show="dataSource!=='hive' && dataSource!=='hbase' && dataSource!=='mongodb'" ref="rdbmswriter" @selectDataSource="showDataSource" />
<HiveWriter v-show="dataSource==='hive'" ref="hivewriter" @selectDataSource="showDataSource" />
<HBaseWriter v-show="dataSource==='hbase'" ref="hbasewriter" @selectDataSource="showDataSource" />
<MongoDBWriter v-show="dataSource==='mongodb'" ref="mongodbwriter" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import RDBMSWriter from './writer/RDBMSWriter'
import HiveWriter from './writer/HiveWriter'
import HBaseWriter from './writer/HBaseWriter'
import MongoDBWriter from './writer/MongoDBWriter'
export default {
name: 'Writer',
components: { RDBMSWriter, HiveWriter, HBaseWriter, MongoDBWriter },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
if (this.dataSource === 'hive') {
return this.$refs.hivewriter.getData()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasewriter.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbwriter.getData()
} else {
return this.$refs.rdbmswriter.getData()
}
},
getTableName() {
if (this.dataSource === 'hive') {
return this.$refs.hivewriter.getTableName()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasewriter.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbwriter.getData()
} else {
return this.$refs.rdbmswriter.getTableName()
}
},
getReaderData() {
return this.$parent.getReaderData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
},
sendTableNameAndColumns(fromTableName, fromColumnList) {
this.$refs.hivewriter.fromTableName = fromTableName
this.$refs.hivewriter.fromColumnList = fromColumnList
}
}
}
</script>

View File

@@ -0,0 +1,223 @@
<template>
<div>
<el-form label-position="left" label-width="115px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="rowkeyColumn" prop="rowkeyColumn">
<el-input v-model="writerForm.rowkeyColumn" :autosize="{ minRows: 5, maxRows: 20}" type="textarea" style="width: 42%" />
</el-form-item>
<el-form-item label="versionColumn">
<el-input v-model="writerForm.versionColumn.index" placeholder="index指定对应reader端column的索引" style="width: 42%" />
</el-form-item>
<el-form-item>
<el-input v-model="writerForm.versionColumn.value" placeholder="value指定时间的值,long值" style="width: 42%" />
</el-form-item>
<el-form-item label="nullMode">
<el-select v-model="writerForm.nullMode" placeholder="null值转换方式">
<el-option v-for="item in nullModeTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'HBaseWriter',
data() {
const checkJson = (rule, value, callback) => {
if (!value) {
callback(new Error('不能为空'))
}
if (typeof value === 'string') {
try {
var obj = JSON.parse(value)
if (typeof obj !== 'object' || !obj) {
callback(new Error('JSON格式错误'))
}
if (!(obj instanceof Array)) {
callback(new Error('JSON必须为数组'))
}
} catch (e) {
callback(new Error('JSON格式错误'))
}
}
}
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
mode: 'normal',
rowkeyColumn: '[{\n' +
'\t"index": 0,\n' +
'\t"type": "string"\n' +
'}]',
versionColumn: {
index: '',
value: ''
},
nullMode: ''
},
nullModeTypes: [
{ value: 'skip', label: '不向hbase写这列' },
{ value: 'empty', label: '写入new byte [0]' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }],
rowkeyColumn: [{ required: true, trigger: 'blur', validator: checkJson }]
},
readerForm: this.getReaderData()
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hbaseWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hbaseWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
}
}
}
</script>

View File

@@ -0,0 +1,240 @@
<template>
<div>
<el-form label-position="left" label-width="105px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<!--<el-col :span="6">
<el-form-item>
<el-button type="primary" :disabled="!this.fromTableName" @click="createTable()">一键生成目标表</el-button>
</el-form-item>
</el-col>-->
</el-row>
<el-form-item label="path" prop="path">
<el-input v-model="writerForm.path" :autosize="{ minRows: 2, maxRows: 20}" type="textarea" placeholder="为与hive表关联请填写hive表在hdfs上的存储路径" style="width: 42%" />
</el-form-item>
<el-form-item label="defaultFS" prop="defaultFS">
<el-input v-model="writerForm.defaultFS" placeholder="Hadoop hdfs文件系统namenode节点地址" style="width: 42%" />
</el-form-item>
<el-form-item label="fileName" prop="fileName">
<el-input v-model="writerForm.fileName" placeholder="HdfsWriter写入时的文件名" style="width: 42%" />
</el-form-item>
<el-form-item label="fileType" prop="fileType">
<el-select v-model="writerForm.fileType" placeholder="文件的类型">
<el-option v-for="item in fileTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="writeMode" prop="writeMode">
<el-select v-model="writerForm.writeMode" placeholder="文件的类型">
<el-option v-for="item in writeModes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="fieldDelimiter" prop="fieldDelimiter">
<el-input v-model="writerForm.fieldDelimiter" placeholder="与创建表的分隔符一致" style="width: 13%" />
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'HiveWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
defaultFS: '',
fileType: '',
path: '',
fileName: '',
writeMode: '',
fieldDelimiter: ''
},
rules: {
path: [{ required: true, message: 'this is required', trigger: 'blur' }],
defaultFS: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileName: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileType: [{ required: true, message: 'this is required', trigger: 'change' }],
writeMode: [{ required: true, message: 'this is required', trigger: 'change' }],
fieldDelimiter: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
readerForm: this.getReaderData(),
fileTypes: [
{ value: 'text', label: 'text' },
{ value: 'orc', label: 'orc' }
],
writeModes: [
{ value: 'append', label: 'append 写入前不做任何处理' },
{ value: 'nonConflict', label: 'nonConflict 目录下有fileName前缀的文件直接报错' }
]
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hiveWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hiveWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const tableName = this.fromTableName
const datasourceId = this.writerForm.datasourceId
const columns = this.fromColumnList
const jsonString = {}
jsonString['datasourceId'] = datasourceId
jsonString['tableName'] = tableName
jsonString['columns'] = columns
console.info(jsonString)
dsQueryApi.createTable(jsonString).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,192 @@
<template>
<div>
<el-form label-position="left" label-width="105px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新信息">
<el-select v-model="writerForm.upsertInfo.isUpsert" placeholder="是否更新">
<el-option v-for="item in upsertType" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="writerForm.upsertInfo.upsertKey" placeholder="更新的业务主键" style="width: 42%" />
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'MongoDBWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
columns: [],
tableName: '',
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
upsertInfo: {
isUpsert: '',
upsertKey: ''
}
},
upsertType: [
{ value: true, label: '针对相同的upsertKey做更新' },
{ value: false, label: '不做更新' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
readerForm: this.getReaderData()
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('mongodbWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'mongodbWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
}
}
}
</script>

View File

@@ -0,0 +1,232 @@
<template>
<div>
<el-form label-position="right" label-width="150px" :model="writerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
style="width: 300px;"
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' || dataSource==='hana'" label="Schema" prop="tableSchema">
<el-select v-model="writerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="数据库表名:" prop="tableName">
<el-select
v-model="fromTableName"
allow-create
default-first-option
filterable
:disabled="writerForm.ifCreateTable"
style="width: 300px"
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-input v-show="writerForm.ifCreateTable" v-model="writerForm.tableName" style="width: 200px;" :placeholder="readerForm.tableName" />
<!--<el-input v-model="createTableName" style="width: 195px" />
<el-button type="primary" @click="createTable">新增</el-button>-->
</el-form-item>
<div style="margin: 5px 0;" />
<el-form-item label="字段:">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="前置sql语句">
<el-input v-model="writerForm.preSql" placeholder="前置sql在insert之前执行" type="textarea" style="width: 42%" />
</el-form-item>
<el-form-item label="postSql">
<el-input v-model="writerForm.postSql" placeholder="多个用;分隔" type="textarea" style="width: 42%" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'RDBMSWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
wDsList: [],
schemaList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
createTableName: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
preSql: '',
postSql: '',
ifCreateTable: false,
tableSchema: ''
},
readerForm: this.getReaderData(),
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }],
tableSchema: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('rdbmsWriter')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'rdbmsWriter') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.writerForm.datasourceId,
tableSchema: this.writerForm.tableSchema
}
} else {
obj = {
datasourceId: this.writerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response.data
})
}
},
getSchema() {
const obj = {
datasourceId: this.writerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.writerForm.tableSchema = e
// 获取可用表
this.getTables('rdbmsWriter')
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response.data
this.writerForm.columns = response.data
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.createTableName
}
dsQueryApi.createTable(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,174 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.registryKey" placeholder="执行器" style="width: 200px;" class="filter-item" />
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
Search
</el-button>
</div>
<div v-for="item in list" :key="item.id" class="container">
<p><span class="fl">执行器{{ item.registryKey }}</span><span class="fl">&nbsp;&nbsp;注册地址{{ item.registryValue }}</span><span class="fr">更新时间{{ item.updateTime }}</span></p>
<div :class="item.id + ' fl'" style="width: 30%;height: 300px" />
<div :class="item.id + ' fl'" style="width: 30%;height: 300px" />
<div :class="item.id + ' fl' + ' loadAverage'" style="width: 30%;height: 300px ">
<p class="title">平均负载</p>
<p class="number">{{ item.loadAverage >= 0 ? item.loadAverage : 0 }}</p>
</div>
</div>
</div>
</template>
<script>
import { getList } from '@/api/dts/datax-registry'
import waves from '@/directive/waves' // waves directive
export default {
name: 'Registry',
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
registryKey: undefined
},
dialogPluginVisible: false
}
},
created() {
this.fetchData()
},
mounted() {
},
methods: {
fetchData() {
this.listLoading = true
this.list = []
getList(this.listQuery).then(response => {
const { data } = response
const { total } = data.total
this.total = total
this.list = data.records
this.listLoading = false
this.$nextTick(function() {
if (this.list) {
for (let i = 0; i < this.list.length; i++) {
this.initEcharts(this.list[i])
}
}
})
})
},
initEcharts(data) {
const myChart1 = this.$echarts.init(document.getElementsByClassName(data.id)[0])
// 绘制图表
var option1 = {
title: {
text: 'cpu使用率',
subtext: '',
x: 'center'
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
},
show: false
},
series: [{
name: 'cpu使用率',
type: 'gauge',
max: 100,
radius: '70%', // 半径
startAngle: 215, // 起始位置
endAngle: -35, // 终点位置
detail: {
formatter: '{value}%'
},
data: [{
value: data.cpuUsage,
name: ''
}]
}]
}
myChart1.setOption(option1)
const myChart2 = this.$echarts.init(document.getElementsByClassName(data.id)[1])
// 绘制图表
var option2 = {
title: {
text: '内存使用率',
subtext: '',
x: 'center'
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
},
show: false
},
series: [{
name: '内存使用率',
type: 'gauge',
max: 100,
radius: '70%', // 半径
startAngle: 215, // 起始位置
endAngle: -35, // 终点位置
detail: {
formatter: '{value}%'
},
data: [{
value: data.memoryUsage,
name: ''
}]
}]
}
myChart2.setOption(option2)
}
}
}
</script>
<style lang="scss" scope>
.container{
overflow: hidden;
p{
font-size: 14px;color: #666;padding: 10px 0;
.fl{
float: left;
}
.fr{
float: right;
}
}
.loadAverage{
p{
text-align: center;
}
.title{
font-size: 18px;font-weight: bold;color: #333;padding: 5px 0;margin: 0;
}
.number{
font-size: 50px;color: #3d90d0
}
}
}
</style>

View File

@@ -0,0 +1,215 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.username" placeholder="资源名称" style="width: 200px;" class="filter-item" />
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button
class="filter-item"
style="margin-left: 10px;"
type="primary"
icon="el-icon-edit"
@click="handleCreate"
>
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index+1 }}</template>
</el-table-column>
<el-table-column label="资源名称" align="center">
<template slot-scope="scope">{{ scope.row.username }}</template>
</el-table-column>
<el-table-column label="资源地址" align="center">
<template slot-scope="scope">{{ scope.row.username }}</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!=='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form
ref="dataForm"
:rules="rules"
:model="temp"
label-position="right"
label-width="100px"
style="width: 400px; margin-left:50px;"
>
<el-form-item label="资源名称" prop="resourceName">
<el-input v-model="temp.resourceName" placeholder="资源名称" />
</el-form-item>
<el-form-item label="资源地址" prop="resourcePath">
<el-input v-model="temp.resourcePath" placeholder="资源地址" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as user from '@/api/dts/datax-user'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'User',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
username: undefined
},
roles: ['ROLE_USER', 'ROLE_ADMIN'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
role: [{ required: true, message: 'role is required', trigger: 'change' }],
username: [{ required: true, message: 'username is required', trigger: 'blur' }],
password: [{ required: false, message: 'password is required', trigger: 'blur' }]
},
temp: {
id: undefined,
role: '',
username: '',
password: '',
permission: ''
},
resetTemp() {
this.temp = this.$options.data().temp
}
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
user.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
user.createUser(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
user.updateUser(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
user.deleteUser(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>

View File

@@ -0,0 +1,89 @@
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">
Oops!
</h1>
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
<router-link to="/dashboard">
回首页
</router-link>
</li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
</div>
</template>
<script>
import errGif from '@/assets/401_images/401.gif'
export default {
name: 'Page401',
data() {
return {
errGif: errGif + '?' + +new Date()
}
},
methods: {
back() {
if (this.$route.query.noGoBack) {
this.$router.push({ path: '/dashboard' })
} else {
this.$router.go(-1)
}
}
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none!important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

225
src/views/features/404.vue Normal file
View File

@@ -0,0 +1,225 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确请点击以下按钮返回主页或者发送错误报告</div>
<a href="/" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
message() {
return '网管说这个页面你不能进......'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1,12 @@
<script>
export default {
created() {
const { params, query } = this.$route
const { path } = params
this.$router.replace({ path: '/' + path, query })
},
render: function(h) {
return h() // avoid warning message
}
}
</script>

View File

@@ -0,0 +1,325 @@
<template>
<div class="app-container">
<el-row :gutter="15">
<el-col style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">字段配置{{ tableName }}</span>
<el-button
:loading="genLoading"
icon="el-icon-s-promotion"
size="mini"
style="float: right; padding: 6px 9px;"
type="success"
@click="toGen"
>保存&生成</el-button>
<el-button
:loading="columnLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px;margin-right: 9px"
type="primary"
@click="saveColumnConfig"
>保存</el-button>
<el-tooltip class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start">
<el-button
:loading="syncLoading"
icon="el-icon-refresh"
size="mini"
style="float: right; padding: 6px 9px;"
type="info"
@click="sync"
>同步</el-button>
</el-tooltip>
</div>
<el-form size="small" label-width="90px">
<el-table v-loading="loading" :data="data" :max-height="tableHeight" size="small" style="width: 100%;margin-bottom: 15px">
<el-table-column prop="columnName" label="字段名称" />
<el-table-column prop="columnType" label="字段类型" />
<el-table-column prop="remark" label="字段描述">
<template slot-scope="scope">
<el-input v-model="data[scope.$index].remark" size="mini" class="edit-input" />
</template>
</el-table-column>
<el-table-column align="center" label="必填" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].notNull" />
</template>
</el-table-column>
<el-table-column align="center" label="列表" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].listShow" />
</template>
</el-table-column>
<el-table-column align="center" label="表单" width="70px">
<template slot-scope="scope">
<el-checkbox v-model="data[scope.$index].formShow" />
</template>
</el-table-column>
<el-table-column label="表单类型">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].formType" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option
label="文本框"
value="Input"
/>
<el-option
label="文本域"
value="Textarea"
/>
<el-option
label="单选框"
value="Radio"
/>
<el-option
label="下拉框"
value="Select"
/>
<el-option
label="日期框"
value="Date"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="查询方式">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].queryType" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<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="NotNull"
value="NotNull"
/>
<el-option
label="BetWeen"
value="BetWeen"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="日期注解">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].dateAnnotation" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option
label="自动创建时间"
value="CreationTimestamp"
/>
<el-option
label="自动更新时间"
value="UpdateTimestamp"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="关联字典">
<template slot-scope="scope">
<el-select v-model="data[scope.$index].dictName" filterable class="edit-input" clearable size="mini" placeholder="请选择">
<el-option v-for="item in dicts" :key="item.id" :label="item.remark === '' ? item.name : item.remark" :value="item.name" />
</el-select>
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
</el-col>
<el-col>
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">生成配置</span>
<el-button
:loading="configLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="doSubmit"
>保存</el-button>
</div>
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="78px">
<el-form-item label="作者名称" prop="author">
<el-input v-model="form.author" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">类上面的作者名称</span>
</el-form-item>
<el-form-item label="模块名称" prop="moduleName">
<el-input v-model="form.moduleName" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">模块的名称请选择项目中已存在的模块</span>
</el-form-item>
<el-form-item label="至于包下" prop="pack">
<el-input v-model="form.pack" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">项目包的名称生成的代码放到哪个包里面</span>
</el-form-item>
<el-form-item label="接口名称" prop="apiAlias">
<el-input v-model="form.apiAlias" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">接口的名称用于控制器与接口文档中</span>
</el-form-item>
<el-form-item label="前端路径" prop="path">
<el-input v-model="form.path" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">输入views文件夹下的目录不存在即创建</span>
</el-form-item>
<!-- <el-form-item label="接口目录">-->
<!-- <el-input v-model="form.apiPath" style="width: 40%" />-->
<!-- <span style="color: #C0C0C0;margin-left: 10px;">Api存放路径[src/api]为空则自动生成路径</span>-->
<!-- </el-form-item>-->
<el-form-item label="去表前缀" prop="prefix">
<el-input v-model="form.prefix" placeholder="默认不去除表前缀" style="width: 40%" />
<span style="color: #C0C0C0;margin-left: 10px;">默认不去除表前缀可自定义</span>
</el-form-item>
<el-form-item label="是否覆盖" prop="cover">
<el-radio-group v-model="form.cover" size="mini" style="width: 40%">
<el-radio-button label="true"></el-radio-button>
<el-radio-button label="false"></el-radio-button>
</el-radio-group>
<span style="color: #C0C0C0;margin-left: 10px;">谨防误操作请慎重选择</span>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crud from '@/mixins/crud'
import { update, get } from '@/api/generator/genConfig'
import { save, sync, generator } from '@/api/generator/generator'
import { getDicts } from '@/api/system/dict'
export default {
name: 'GeneratorConfig',
components: {},
mixins: [crud],
data() {
return {
activeName: 'first', tableName: '', tableHeight: 550, columnLoading: false, configLoading: false, dicts: [], syncLoading: false, genLoading: false,
form: { id: null, tableName: '', author: '', pack: '', path: '', moduleName: '', cover: 'false', apiPath: '', prefix: '', apiAlias: null },
rules: {
author: [
{ required: true, message: '作者不能为空', trigger: 'blur' }
],
pack: [
{ required: true, message: '包路径不能为空', trigger: 'blur' }
],
moduleName: [
{ required: true, message: '包路径不能为空', trigger: 'blur' }
],
path: [
{ required: true, message: '前端路径不能为空', trigger: 'blur' }
],
apiAlias: [
{ required: true, message: '接口名称不能为空', trigger: 'blur' }
],
cover: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
}
},
created() {
this.tableHeight = document.documentElement.clientHeight - 385
this.tableName = this.$route.params.tableName
this.$nextTick(() => {
this.init()
get(this.tableName).then(data => {
this.form = data
this.form.cover = this.form.cover.toString()
})
getDicts().then(data => {
this.dicts = data
})
})
},
methods: {
beforeInit() {
this.url = '/system/api/generator/columns'
const tableName = this.tableName
this.params = { tableName }
return true
},
saveColumnConfig() {
this.columnLoading = true
save(this.data).then(res => {
this.notify('保存成功', 'success')
this.columnLoading = false
}).catch(err => {
this.columnLoading = false
console.log(err.response.data.message)
})
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.configLoading = true
update(this.form).then(res => {
this.notify('保存成功', 'success')
this.form = res
this.form.cover = this.form.cover.toString()
this.configLoading = false
}).catch(err => {
this.configLoading = false
console.log(err.response.data.message)
})
}
})
},
sync() {
this.syncLoading = true
sync([this.tableName]).then(() => {
this.init()
this.notify('同步成功', 'success')
this.syncLoading = false
}).then(() => {
this.syncLoading = false
})
},
toGen() {
this.genLoading = true
save(this.data).then(res => {
this.notify('保存成功', 'success')
// 生成代码
generator(this.tableName, 0).then(data => {
this.genLoading = false
this.notify('生成成功', 'success')
}).catch(err => {
this.genLoading = false
console.log(err.response.data.message)
})
}).catch(err => {
this.genLoading = false
console.log(err.response.data.message)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.edit-input {
.el-input__inner {
border: 1px solid #e5e6e7;
}
}
</style>
<style scoped>
::v-deep .input-with-select .el-input-group__prepend {
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<el-input v-model="query.name" clearable size="small" placeholder="请输入表名" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation>
<el-tooltip slot="right" class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start">
<el-button
class="filter-item"
size="mini"
type="success"
icon="el-icon-refresh"
:loading="syncLoading"
:disabled="crud.selections.length === 0"
@click="sync"
>同步</el-button>
</el-tooltip>
</crudOperation>
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="tableName" label="表名" />
<el-table-column :show-overflow-tooltip="true" prop="engine" label="数据库引擎" />
<el-table-column :show-overflow-tooltip="true" prop="coding" label="字符编码集" />
<el-table-column :show-overflow-tooltip="true" prop="remark" label="备注" />
<el-table-column prop="createTime" label="创建日期" />
<el-table-column label="操作" width="160px" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" style="margin-right: 2px" type="text">
<router-link :to="'/sys-tools/generator/preview/' + scope.row.tableName">
预览
</router-link>
</el-button>
<el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text" @click="toDownload(scope.row.tableName)">下载</el-button>
<el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text">
<router-link :to="'/sys-tools/generator/config/' + scope.row.tableName">
配置
</router-link>
</el-button>
<el-button type="text" style="margin-left: -1px" size="mini" @click="toGen(scope.row.tableName)">生成</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import { generator, sync } from '@/api/generator/generator'
import { downloadFile } from '@/utils/index'
import CRUD, { presenter, header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
export default {
name: 'GeneratorIndex',
components: { pagination, crudOperation, rrOperation },
cruds() {
return CRUD({ url: '/system/api/generator/tables' })
},
mixins: [presenter(), header()],
data() {
return {
syncLoading: false
}
},
created() {
this.crud.optShow = { add: false, edit: false, del: false, download: false }
},
methods: {
toGen(tableName) {
// 生成代码
generator(tableName, 0).then(data => {
this.$notify({
title: '生成成功',
type: 'success',
duration: 2500
})
})
},
toDownload(tableName) {
// 打包下载
generator(tableName, 2).then(data => {
downloadFile(data, tableName, 'zip')
})
},
sync() {
const tables = []
this.crud.selections.forEach(val => {
tables.push(val.tableName)
})
this.syncLoading = true
sync(tables).then(() => {
this.crud.refresh()
this.crud.notify('同步成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.syncLoading = false
}).then(() => {
this.syncLoading = false
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,30 @@
<template>
<el-tabs v-model="activeName" type="card">
<el-tab-pane v-for="item in data" :key="item.name" :lazy="true" :label="item.name" :name="item.name">
<Java :value="item.content" :height="height" />
</el-tab-pane>
</el-tabs>
</template>
<script>
import Java from '@/components/JavaEdit/index'
import { generator } from '@/api/generator/generator'
export default {
name: 'Preview',
components: { Java },
data() {
return {
data: null, height: '', activeName: 'Entity'
}
},
created() {
this.height = document.documentElement.clientHeight - 180 + 'px'
const tableName = this.$route.params.tableName
generator(tableName, 1).then(data => {
this.data = data
}).catch(() => {
this.$router.go(-1)
})
}
}
</script>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div>
<div v-if="query.dictName === ''">
<div class="my-code">点击字典查看详情</div>
</div>
<div v-else>
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.label" clearable size="small" placeholder="输入字典标签查询" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<rrOperation />
</div>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典标签" prop="label">
<el-input v-model="form.label" style="width: 370px;" />
</el-form-item>
<el-form-item label="字典值" prop="value">
<el-input v-model="form.value" style="width: 370px;" />
</el-form-item>
<el-form-item label="排序" prop="dictSort">
<el-input-number v-model.number="form.dictSort" :min="0" :max="999" controls-position="right" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column label="所属字典">
{{ query.dictName }}
</el-table-column>
<el-table-column prop="label" label="字典标签" />
<el-table-column prop="value" label="字典值" />
<el-table-column prop="dictSort" label="排序" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudDictDetail from '@/api/system/dictDetail'
import CRUD, { presenter, header, form } from '@crud/crud'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, label: null, value: null, dictSort: 999 }
export default {
components: { pagination, rrOperation, udOperation },
cruds() {
return [
CRUD({ title: '字典详情', url: '/system/api/dictDetail', query: { dictName: '' }, sort: ['dictSort,asc', 'id,desc'],
crudMethod: { ...crudDictDetail },
optShow: {
add: true,
edit: true,
del: true,
reset: false
},
queryOnPresenterCreated: false
})
]
},
mixins: [
presenter(),
header(),
form(function() {
return Object.assign({ dict: { id: this.dictId }}, defaultForm)
})],
data() {
return {
dictId: null,
rules: {
label: [
{ required: true, message: '请输入字典标签', trigger: 'blur' }
],
value: [
{ required: true, message: '请输入字典值', trigger: 'blur' }
],
dictSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<div class="app-container">
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!-- 字典列表 -->
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="10" :lg="11" :xl="11" style="margin-bottom: 10px">
<el-card class="box-card">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="name" label="名称" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 字典详情列表 -->
<el-col :xs="24" :sm="24" :md="14" :lg="13" :xl="13">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>字典详情</span>
<el-button
v-if="checkPer(['admin','dict:add']) && this.$refs.dictDetail && this.$refs.dictDetail.query.dictName"
class="filter-item"
size="mini"
style="float: right;padding: 4px 10px"
type="primary"
icon="el-icon-plus"
@click="$refs.dictDetail && $refs.dictDetail.crud.toAdd()"
>新增</el-button>
</div>
<dictDetail ref="dictDetail" :permission="permission" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import dictDetail from './dictDetail'
import crudDict from '@/api/system/dict'
import CRUD, { presenter, header, form } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, name: null, description: null, dictDetails: [] }
export default {
name: 'Dict',
components: { crudOperation, pagination, rrOperation, udOperation, dictDetail },
cruds() {
return [
CRUD({ title: '字典', url: '/system/api/dict', crudMethod: { ...crudDict }})
]
},
mixins: [presenter(), header(), form(defaultForm)],
data() {
return {
queryTypeOptions: [
{ key: 'name', display_name: '字典名称' },
{ key: 'description', display_name: '描述' }
],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
},
methods: {
// 获取数据前设置好接口地址
[CRUD.HOOK.beforeRefresh]() {
if (this.$refs.dictDetail) {
this.$refs.dictDetail.query.dictName = ''
}
return true
},
// 选中字典后,设置字典详情数据
handleCurrentChange(val) {
if (val) {
this.$refs.dictDetail.query.dictName = val.name
this.$refs.dictDetail.dictId = val.id
this.$refs.dictDetail.crud.toQuery()
}
},
// 编辑前将字典明细临时清空,避免日志入库数据过长
[CRUD.HOOK.beforeToEdit](crud, form) {
// 将角色的菜单清空,避免日志入库数据过长
form.dictDetails = null
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<eHeader :dict="dict" :permission="permission" />
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="jobSort" label="排序">
<template slot-scope="scope">
{{ scope.row.jobSort }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<!-- 编辑与删除 -->
<el-table-column
v-if="checkPer(['admin','job:edit','job:del'])"
label="操作"
width="130px"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
<!--表单渲染-->
<eForm :job-status="dict.job_status" />
</div>
</template>
<script>
import crudJob from '@/api/system/job'
import eHeader from './module/header'
import eForm from './module/form'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import udOperation from '@crud/UD.operation'
export default {
name: 'Job',
components: { eHeader, eForm, crudOperation, pagination, udOperation },
cruds() {
return CRUD({
title: '岗位',
url: '/system/api/job',
sort: ['jobSort,asc', 'id,desc'],
crudMethod: { ...crudJob }
})
},
mixins: [presenter()],
// 数据字典
dicts: ['job_status'],
data() {
return {
permission: {
add: ['admin', 'job:add'],
edit: ['admin', 'job:edit'],
del: ['admin', 'job:del']
}
}
},
methods: {
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.job_status[val] + '" ' + data.name + '岗位, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// eslint-disable-next-line no-undef
crudJob.edit(data).then(() => {
// eslint-disable-next-line no-undef
this.crud.notify(this.dict.label.job_status[val] + '成功', 'success')
}).catch(err => {
data.enabled = !data.enabled
console.log(err.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<el-dialog
append-to-body
:close-on-click-modal="false"
:before-close="crud.cancelCU"
:visible="crud.status.cu > 0"
:title="crud.status.title"
width="500px"
>
<el-form
ref="form"
:model="form"
:rules="rules"
size="small"
label-width="80px"
>
<el-form-item
label="名称"
prop="name"
>
<el-input
v-model="form.name"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
label="排序"
prop="jobSort"
>
<el-input-number
v-model.number="form.jobSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
v-if="form.pid !== 0"
label="状态"
prop="enabled"
>
<el-radio
v-for="item in jobStatus"
:key="item.id"
v-model="form.enabled"
:label="item.value === 'true'"
>
{{ item.label }}
</el-radio>
</el-form-item>
</el-form>
<div
slot="footer"
class="dialog-footer"
>
<el-button
type="text"
@click="crud.cancelCU"
>
取消
</el-button>
<el-button
:loading="crud.status.cu === 2"
type="primary"
@click="crud.submitCU"
>
确认
</el-button>
</div>
</el-dialog>
</template>
<script>
import { form } from '@crud/crud'
const defaultForm = {
id: null,
name: '',
jobSort: 999,
enabled: true
}
export default {
mixins: [form(defaultForm)],
props: {
jobStatus: {
type: Array,
required: true
}
},
data() {
return {
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
jobSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div
v-if="crud.props.searchToggle"
>
<el-input v-model="query.name" clearable size="small" placeholder="输入岗位名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in dict.dict.job_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<rrOperation />
</div>
</template>
<script>
import { header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { rrOperation, DateRangePicker },
mixins: [header()],
props: {
dict: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
}
}
}
</script>

View File

@@ -0,0 +1,252 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="580px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type" size="mini" style="width: 178px">
<el-radio-button label="0">目录</el-radio-button>
<el-radio-button label="1">菜单</el-radio-button>
<el-radio-button label="2">按钮</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="450"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame">
<el-radio-group v-model="form.iFrame" size="mini">
<el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache">
<el-radio-group v-model="form.cache" size="mini">
<el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden">
<el-radio-group v-model="form.hidden" size="mini">
<el-radio-button label="false">是</el-radio-button>
<el-radio-button label="true">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title">
<el-input v-model="form.title" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" placeholder="菜单标题" />
</el-form-item>
<el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title">
<el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission">
<el-input v-model="form.permission" :disabled="form.iFrame.toString() === 'true'" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path">
<el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" />
</el-form-item>
<el-form-item label="菜单排序" prop="menuSort">
<el-input-number v-model.number="form.menuSort" :min="0" :max="999" controls-position="right" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件名称" prop="componentName">
<el-input v-model="form.componentName" style="width: 178px;" placeholder="匹配组件内Name字段" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件路径" prop="component">
<el-input v-model="form.component" style="width: 178px;" placeholder="组件路径" />
</el-form-item>
<el-form-item label="上级类目" prop="pid">
<treeselect
v-model="form.pid"
:options="menus"
:load-options="loadMenus"
style="width: 450px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getMenus"
:data="crud.data"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" label="菜单标题" width="125px" prop="title" />
<el-table-column prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" />
</template>
</el-table-column>
<el-table-column prop="menuSort" align="center" label="排序">
<template slot-scope="scope">
{{ scope.row.menuSort }}
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" />
<el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" />
<el-table-column prop="iFrame" label="外链" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.iFrame">是</span>
<span v-else>否</span>
</template>
</el-table-column>
<el-table-column prop="cache" label="缓存" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.cache">是</span>
<span v-else>否</span>
</template>
</el-table-column>
<el-table-column prop="hidden" label="可见" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.hidden">否</span>
<span v-else>是</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" width="135px" />
<el-table-column v-if="checkPer(['admin','menu:edit','menu:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudMenu from '@/api/system/menu'
import IconSelect from '@/components/IconSelect'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
// crud交由presenter持有
const defaultForm = { id: null, title: null, menuSort: 999, path: null, component: null, componentName: null, iFrame: false, roles: [], pid: 0, icon: null, cache: false, hidden: false, type: 0, permission: null }
export default {
name: 'Menu',
components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '菜单', url: '/system/api/menus', crudMethod: { ...crudMenu }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
menus: [],
permission: {
add: ['admin', 'menu:add'],
edit: ['admin', 'menu:edit'],
del: ['admin', 'menu:del']
},
rules: {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
path: [
{ required: true, message: '请输入地址', trigger: 'blur' }
]
}
}
},
methods: {
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.menus = []
if (form.id != null) {
if (form.pid === null) {
form.pid = 0
}
this.getSupDepts(form.id)
} else {
this.menus.push({ id: 0, label: '顶级类目', children: null })
}
},
getMenus(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudMenu.getMenus(params).then(res => {
resolve(res.content)
})
}, 100)
},
getSupDepts(id) {
crudMenu.getMenuSuperior(id).then(res => {
const children = res.map(function(obj) {
if (!obj.leaf && !obj.children) {
obj.children = null
}
return obj
})
this.menus = [{ id: 0, label: '顶级类目', children: children }]
})
},
loadMenus({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudMenu.getMenusTree(parentNode.id).then(res => {
parentNode.children = res.map(function(obj) {
if (!obj.leaf) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 选中图标
selected(name) {
this.form.icon = name
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<div class="el-upload">
<img :src="user.avatarName ? baseApi + '/system/avatar/' + user.avatarName : Avatar" title="点击上传头像" class="avatar" @click="toggleShow">
<myUpload
v-model="show"
:headers="headers"
:url="updateAvatarApi"
@crop-upload-success="cropUploadSuccess"
/>
</div>
</div>
<ul class="user-info">
<li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li>
<li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li>
<li><svg-icon icon-class="dept" /> 所属部门 <div class="user-right"> {{ user.dept.name }}</div></li>
<li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li>
<li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li>
<li>
<svg-icon icon-class="anq" /> 安全设置
<div class="user-right">
<a @click="$refs.pass.dialog = true">修改密码</a>
<a @click="$refs.email.dialog = true">修改邮箱</a>
</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
<!-- 用户资料 -->
<el-card class="box-card">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户资料" name="first">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" style="width: 35%;" />
<span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="">
<el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 操作日志 -->
<el-tab-pane label="操作日志" name="second">
<el-table v-loading="loading" :data="data" style="width: 100%;">
<el-table-column prop="description" label="行为" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="time" label="请求耗时" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag>
<el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag>
<el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag>
</template>
</el-table-column>
<el-table-column
align="right"
>
<template slot="header">
<div style="display:inline-block;float: right;cursor: pointer" @click="init">创建日期<i class="el-icon-refresh" style="margin-left: 40px" /></div>
</template>
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<updateEmail ref="email" :email="user.email" />
<updatePass ref="pass" />
</div>
</template>
<script>
import myUpload from 'vue-image-crop-upload'
import { mapGetters } from 'vuex'
import updatePass from './center/updatePass'
import updateEmail from './center/updateEmail'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { isvalidPhone } from '@/utils/validate'
import crud from '@/mixins/crud'
import { editUser } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png'
export default {
name: 'Center',
components: { updatePass, updateEmail, myUpload },
mixins: [crud],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
show: false,
Avatar: Avatar,
activeName: 'first',
saveLoading: false,
headers: {
'Authorization': getToken()
},
form: {},
rules: {
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user',
'updateAvatarApi',
'baseApi'
])
},
created() {
this.form = { id: this.user.id, nickName: this.user.nickName, gender: this.user.gender, phone: this.user.phone }
store.dispatch('GetInfo').then(() => {})
},
methods: {
toggleShow() {
this.show = !this.show
},
handleClick(tab, event) {
if (tab.name === 'second') {
this.init()
}
},
beforeInit() {
this.url = '/system/api/logs/user'
return true
},
cropUploadSuccess(jsonData, field) {
store.dispatch('GetInfo').then(() => {})
},
doSubmit() {
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.saveLoading = true
editUser(this.form).then(() => {
this.editSuccessNotify()
store.dispatch('GetInfo').then(() => {})
this.saveLoading = false
}).catch(() => {
this.saveLoading = false
})
}
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
}
.user-info {
padding-left: 0;
list-style: none;
li{
border-bottom: 1px solid #F0F3F4;
padding: 11px 0;
font-size: 13px;
}
.user-right {
float: right;
a{
color: #317EF3;
}
}
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div style="display: inline-block;">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="新邮箱" prop="email">
<el-input v-model="form.email" auto-complete="on" style="width: 200px;" />
<el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{{ buttonName }}</el-button>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model="form.code" style="width: 320px;" />
</el-form-item>
<el-form-item label="当前密码" prop="pass">
<el-input v-model="form.pass" type="password" style="width: 320px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { validEmail } from '@/utils/validate'
import { updateEmail } from '@/api/system/user'
import { resetEmail } from '@/api/system/code'
export default {
props: {
email: {
type: String,
required: true
}
},
data() {
const validMail = (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('新邮箱不能为空'))
} else if (value === this.email) {
callback(new Error('新邮箱不能与旧邮箱相同'))
} else if (validEmail(value)) {
callback()
} else {
callback(new Error('邮箱格式错误'))
}
}
return {
loading: false, dialog: false, title: '修改邮箱', form: { pass: '', email: '', code: '' },
user: { email: '', password: '' }, codeLoading: false,
buttonName: '获取验证码', isDisabled: false, time: 60,
rules: {
pass: [
{ required: true, message: '当前密码不能为空', trigger: 'blur' }
],
email: [
{ required: true, validator: validMail, trigger: 'blur' }
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
sendCode() {
if (this.form.email && this.form.email !== this.email) {
this.codeLoading = true
this.buttonName = '验证码发送中'
const _this = this
resetEmail(this.form.email).then(res => {
this.$message({
showClose: true,
message: '发送成功验证码有效期5分钟',
type: 'success'
})
this.codeLoading = false
this.isDisabled = true
this.buttonName = this.time-- + '秒后重新发送'
this.timer = window.setInterval(function() {
_this.buttonName = _this.time + '秒后重新发送'
--_this.time
if (_this.time < 0) {
_this.buttonName = '重新发送'
_this.time = 60
_this.isDisabled = false
window.clearInterval(_this.timer)
}
}, 1000)
}).catch(err => {
this.resetForm()
this.codeLoading = false
console.log(err.response.data.message)
})
}
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updateEmail(this.form).then(res => {
this.loading = false
this.resetForm()
this.$notify({
title: '邮箱修改成功',
type: 'success',
duration: 1500
})
store.dispatch('GetInfo').then(() => {})
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
window.clearInterval(this.timer)
this.time = 60
this.buttonName = '获取验证码'
this.isDisabled = false
this.form = { pass: '', email: '', code: '' }
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div style="display: inline-block">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="500px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="旧密码" prop="oldPass">
<el-input v-model="form.oldPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="新密码" prop="newPass">
<el-input v-model="form.newPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPass">
<el-input v-model="form.confirmPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { updatePass } from '@/api/system/user'
export default {
data() {
const confirmPass = (rule, value, callback) => {
if (value) {
if (this.form.newPass !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
} else {
callback(new Error('请再次输入密码'))
}
}
return {
loading: false, dialog: false, title: '修改密码', form: { oldPass: '', newPass: '', confirmPass: '' },
rules: {
oldPass: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }
],
newPass: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPass: [
{ required: true, validator: confirmPass, trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updatePass(this.form).then(res => {
this.resetForm()
this.$notify({
title: '密码修改成功,请重新登录',
type: 'success',
duration: 1500
})
setTimeout(() => {
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
}, 1500)
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
this.form = { oldPass: '', newPass: '', confirmPass: '' }
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,484 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!--侧边部门数据-->
<el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4">
<div class="head-container">
<el-input
v-model="deptName"
clearable
size="small"
placeholdfer="输入部门名称搜索"
prefix-icon="el-icon-search"
class="filter-item"
@input="getDeptDatas"
/>
</div>
<el-tree
:data="deptDatas"
:load="getDeptDatas"
:props="defaultProps"
:expand-on-click-node="false"
lazy
@node-click="handleNodeClick"
/>
</el-col>
<!--用户数据-->
<el-col :xs="15" :sm="18" :md="19" :lg="20" :xl="20">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input
v-model="query.blurry"
clearable
size="small"
placeholder="输入名称或者邮箱搜索"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="crud.toQuery"
/>
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select
v-model="query.enabled"
clearable
size="small"
placeholder="状态"
class="filter-item"
style="width: 90px"
@change="crud.toQuery"
>
<el-option
v-for="item in enabledTypeOptions"
:key="item.key"
:label="item.display_name"
:value="item.key"
/>
</el-select>
<rrOperation />
</div>
<crudOperation show="" :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="570px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model.number="form.phone" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="部门" prop="dept.id">
<treeselect
v-model="form.dept.id"
:options="depts"
:load-options="loadDepts"
style="width: 178px"
placeholder="选择部门"
/>
</el-form-item>
<el-form-item label="岗位" prop="jobs">
<el-select
v-model="jobDatas"
style="width: 178px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeJob"
>
<el-option
v-for="item in jobs"
:key="item.name"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="">男</el-radio>
<el-radio label="">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.enabled" :disabled="form.id === user.id">
<el-radio
v-for="item in dict.user_status"
:key="item.id"
:label="item.value"
>{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item style="margin-bottom: 0;" label="角色" prop="roles">
<el-select
v-model="roleDatas"
style="width: 437px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeRole"
>
<el-option
v-for="item in roles"
:key="item.name"
:disabled="level !== 1 && item.level <= level"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="username" label="用户名" />
<el-table-column :show-overflow-tooltip="true" prop="nickName" label="昵称" />
<el-table-column prop="gender" label="性别" />
<el-table-column :show-overflow-tooltip="true" prop="phone" width="100" label="电话" />
<el-table-column :show-overflow-tooltip="true" width="135" prop="email" label="邮箱" />
<el-table-column :show-overflow-tooltip="true" prop="dept" label="部门">
<template slot-scope="scope">
<div>{{ scope.row.dept.name }}</div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="user.id === scope.row.id"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="135" label="创建日期" />
<el-table-column
v-if="checkPer(['admin','user:edit','user:del'])"
label="操作"
width="115"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === user.id"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-col>
</el-row>
</div>
</template>
<script>
import crudUser from '@/api/system/user'
import { isvalidPhone } from '@/utils/validate'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getAll, getLevel } from '@/api/system/role'
import { getAllJob } from '@/api/system/job'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
import Treeselect from '@riophae/vue-treeselect'
import { mapGetters } from 'vuex'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
let userRoles = []
let userJobs = []
const defaultForm = { id: null, username: null, nickName: null, gender: '男', email: null, enabled: 'false', roles: [], jobs: [], dept: { id: null }, phone: null }
export default {
name: 'User',
components: { Treeselect, crudOperation, rrOperation, udOperation, pagination, DateRangePicker },
cruds() {
return CRUD({ title: '用户', url: '/system/api/users', crudMethod: { ...crudUser }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 数据字典
dicts: ['user_status'],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
height: document.documentElement.clientHeight - 180 + 'px;',
deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [],
jobDatas: [], roleDatas: [], // 多选时使用
defaultProps: { children: 'children', label: 'name', isLeaf: 'leaf' },
permission: {
add: ['admin', 'user:add'],
edit: ['admin', 'user:edit'],
del: ['admin', 'user:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '激活' },
{ key: 'false', display_name: '锁定' }
],
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user'
])
},
created() {
this.crud.msg.add = '新增成功默认密码123456'
},
mounted: function() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 180 + 'px;'
}
},
methods: {
// 禁止输入空格
keydown(e) {
if (e.keyCode === 32) {
e.returnValue = false
}
},
changeRole(value) {
userRoles = []
value.forEach(function(data, index) {
const role = { id: data }
userRoles.push(role)
})
},
changeJob(value) {
userJobs = []
value.forEach(function(data, index) {
const job = { id: data }
userJobs.push(job)
})
},
deleteTag(value) {
userRoles.forEach(function(data, index) {
if (data.id === value) {
userRoles.splice(index, value)
}
})
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.getRoles()
if (form.id == null) {
this.getDepts()
} else {
this.getSupDepts(form.dept.id)
}
this.getRoleLevel()
this.getJobs()
form.enabled = form.enabled.toString()
},
// 新增前将多选的值设置为空
[CRUD.HOOK.beforeToAdd]() {
this.jobDatas = []
this.roleDatas = []
},
// 初始化编辑时候的角色与岗位
[CRUD.HOOK.beforeToEdit](crud, form) {
this.getJobs(this.form.dept.id)
this.jobDatas = []
this.roleDatas = []
userRoles = []
userJobs = []
const _this = this
form.roles.forEach(function(role, index) {
_this.roleDatas.push(role.id)
const rol = { id: role.id }
userRoles.push(rol)
})
form.jobs.forEach(function(job, index) {
_this.jobDatas.push(job.id)
const data = { id: job.id }
userJobs.push(data)
})
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
if (!crud.form.dept.id) {
this.$message({
message: '部门不能为空',
type: 'warning'
})
return false
} else if (this.jobDatas.length === 0) {
this.$message({
message: '岗位不能为空',
type: 'warning'
})
return false
} else if (this.roleDatas.length === 0) {
this.$message({
message: '角色不能为空',
type: 'warning'
})
return false
}
crud.form.roles = userRoles
crud.form.jobs = userJobs
return true
},
// 获取左侧部门数据
getDeptDatas(node, resolve) {
const sort = 'id,desc'
const params = { sort: sort }
if (typeof node !== 'object') {
if (node) {
params['name'] = node
}
} else if (node.level !== 0) {
params['pid'] = node.data.id
}
setTimeout(() => {
getDepts(params).then(res => {
if (resolve) {
resolve(res.content)
} else {
this.deptDatas = res.content
}
})
}, 100)
},
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(deptId) {
getDeptSuperior(deptId).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 切换部门
handleNodeClick(data) {
if (data.pid === 0) {
this.query.deptId = null
} else {
this.query.deptId = data.id
}
this.crud.toQuery()
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudUser.edit(data).then(res => {
this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(() => {
data.enabled = !data.enabled
})
}).catch(() => {
data.enabled = !data.enabled
})
},
// 获取弹窗内角色数据
getRoles() {
getAll().then(res => {
this.roles = res
}).catch(() => { })
},
// 获取弹窗内岗位数据
getJobs() {
getAllJob().then(res => {
this.jobs = res.content
}).catch(() => { })
},
// 获取权限级别
getRoleLevel() {
getLevel().then(res => {
this.level = res.level
}).catch(() => { })
},
checkboxT(row, rowIndex) {
return row.id !== this.user.id
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,360 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" size="small" clearable placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!-- 表单渲染 -->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="520px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" style="width: 380px;" />
</el-form-item>
<el-form-item label="角色级别" prop="level">
<el-input-number v-model.number="form.level" :min="1" controls-position="right" style="width: 145px;" />
</el-form-item>
<el-form-item label="数据范围" prop="dataScope">
<el-select v-model="form.dataScope" style="width: 140px" placeholder="请选择数据范围" @change="changeScope">
<el-option
v-for="item in dateScopes"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item v-if="form.dataScope === '自定义'" label="数据权限" prop="depts">
<treeselect
v-model="deptDatas"
:load-options="loadDepts"
:options="depts"
multiple
style="width: 380px"
placeholder="请选择"
/>
</el-form-item>
<el-form-item label="描述信息" prop="description">
<el-input v-model="form.description" style="width: 380px;" rows="5" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<el-row :gutter="15">
<!--角色管理-->
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="17" style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">角色列表</span>
</div>
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" :data="crud.data" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="dataScope" label="数据权限" />
<el-table-column prop="level" label="角色级别" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column :show-overflow-tooltip="true" width="135px" prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','roles:edit','roles:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
v-if="scope.row.level >= level"
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 菜单授权 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="7">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<el-tooltip class="item" effect="dark" content="选择指定角色分配菜单" placement="top">
<span class="role-span">菜单分配</span>
</el-tooltip>
<el-button
v-permission="['admin','roles:edit']"
:disabled="!showButton"
:loading="menuLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="saveMenu"
>保存</el-button>
</div>
<el-tree
ref="menu"
lazy
:data="menus"
:default-checked-keys="menuIds"
:load="getMenuDatas"
:props="defaultProps"
check-strictly
accordion
show-checkbox
node-key="id"
@check="menuChange"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crudRoles from '@/api/system/role'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getMenusTree, getChild } from '@/api/system/menu'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, depts: [], description: null, dataScope: '全部', level: 3 }
export default {
name: 'Role',
components: { Treeselect, pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '角色', url: '/system/api/roles', sort: 'level,asc', crudMethod: { ...crudRoles }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
defaultProps: { children: 'children', label: 'label', isLeaf: 'leaf' },
dateScopes: ['全部', '本级', '自定义'], level: 3,
currentId: 0, menuLoading: false, showButton: false,
menus: [], menuIds: [], depts: [], deptDatas: [], // 多选时使用
permission: {
add: ['admin', 'roles:add'],
edit: ['admin', 'roles:edit'],
del: ['admin', 'roles:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
permission: [
{ required: true, message: '请输入权限', trigger: 'blur' }
]
}
}
},
created() {
crudRoles.getLevel().then(data => {
this.level = data.level
})
},
methods: {
getMenuDatas(node, resolve) {
setTimeout(() => {
getMenusTree(node.data.id ? node.data.id : 0).then(res => {
resolve(res)
})
}, 100)
},
[CRUD.HOOK.afterRefresh]() {
this.$refs.menu.setCheckedKeys([])
},
// 新增前初始化部门信息
[CRUD.HOOK.beforeToAdd](crud, form) {
this.deptDatas = []
form.menus = null
},
// 编辑前初始化自定义数据权限的部门信息
[CRUD.HOOK.beforeToEdit](crud, form) {
this.deptDatas = []
if (form.dataScope === '自定义') {
this.getSupDepts(form.depts)
}
const _this = this
form.depts.forEach(function(dept) {
_this.deptDatas.push(dept.id)
})
// 将角色的菜单清空,避免日志入库数据过长
form.menus = null
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
if (crud.form.dataScope === '自定义' && this.deptDatas.length === 0) {
this.$message({
message: '自定义数据权限不能为空',
type: 'warning'
})
return false
} else if (crud.form.dataScope === '自定义') {
const depts = []
this.deptDatas.forEach(function(data) {
const dept = { id: data }
depts.push(dept)
})
crud.form.depts = depts
} else {
crud.form.depts = []
}
return true
},
// 触发单选
handleCurrentChange(val) {
if (val) {
const _this = this
// 清空菜单的选中
this.$refs.menu.setCheckedKeys([])
// 保存当前的角色id
this.currentId = val.id
// 初始化默认选中的key
this.menuIds = []
val.menus.forEach(function(data) {
_this.menuIds.push(data.id)
})
this.showButton = true
}
},
menuChange(menu) {
// 获取该节点的所有子节点id 包含自身
getChild(menu.id).then(childIds => {
// 判断是否在 menuIds 中,如果存在则删除,否则添加
if (this.menuIds.indexOf(menu.id) !== -1) {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index !== -1) {
this.menuIds.splice(index, 1)
}
}
} else {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index === -1) {
this.menuIds.push(childIds[i])
}
}
}
this.$refs.menu.setCheckedKeys(this.menuIds)
})
},
// 保存菜单
saveMenu() {
this.menuLoading = true
const role = { id: this.currentId, menus: [] }
// 得到已选中的 key 值
this.menuIds.forEach(function(id) {
const menu = { id: id }
role.menus.push(menu)
})
crudRoles.editMenu(role).then(() => {
this.crud.notify('保存成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.menuLoading = false
this.update()
}).catch(err => {
this.menuLoading = false
console.log(err.response.data.message)
})
},
// 改变数据
update() {
// 无刷新更新 表格数据
crudRoles.get(this.currentId).then(res => {
for (let i = 0; i < this.crud.data.length; i++) {
if (res.id === this.crud.data[i].id) {
this.crud.data[i] = res
break
}
}
})
},
// 获取部门数据
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(depts) {
const ids = []
depts.forEach(dept => {
ids.push(dept.id)
})
getDeptSuperior(ids).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 如果数据权限为自定义则获取部门数据
changeScope() {
if (this.form.dataScope === '自定义') {
this.getDepts()
}
},
checkboxT(row) {
return row.level >= this.level
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.role-span {
font-weight: bold;color: #303133;
font-size: 15px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__multi-value{
margin-bottom: 0;
}
::v-deep .vue-treeselect__multi-value-item{
border: 0;
padding: 0;
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<!-- 任务日志 -->
<el-button
slot="right"
class="filter-item"
size="mini"
type="info"
icon="el-icon-tickets"
@click="doLog"
>日志</el-button>
</crudOperation>
<Log ref="log" />
</div>
<!--Form表单-->
<el-dialog :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" append-to-body width="730px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="100px">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务描述" prop="description">
<el-input v-model="form.description" style="width: 220px;" />
</el-form-item>
<el-form-item label="Bean名称" prop="beanName">
<el-input v-model="form.beanName" style="width: 220px;" />
</el-form-item>
<el-form-item label="执行方法" prop="methodName">
<el-input v-model="form.methodName" style="width: 220px;" />
</el-form-item>
<el-form-item label="Cron表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" style="width: 220px;" />
</el-form-item>
<el-form-item label="子任务ID">
<el-input v-model="form.subTask" placeholder="多个用逗号隔开按顺序执行" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务负责人" prop="personInCharge">
<el-input v-model="form.personInCharge" style="width: 220px;" />
</el-form-item>
<el-form-item label="告警邮箱" prop="email">
<el-input v-model="form.email" placeholder="多个邮箱用逗号隔开" style="width: 220px;" />
</el-form-item>
<el-form-item label="失败后暂停">
<el-radio-group v-model="form.pauseAfterFailure" style="width: 220px">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="任务状态">
<el-radio-group v-model="form.isPause" style="width: 220px">
<el-radio :label="false">启用</el-radio>
<el-radio :label="true">暂停</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="参数内容">
<el-input v-model="form.params" style="width: 556px;" rows="4" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="id" label="任务ID" />
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column :show-overflow-tooltip="true" prop="isPause" width="90px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isPause ? 'warning' : 'success'">{{ scope.row.isPause ? '已暂停' : '运行中' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="description" width="150px" label="描述" />
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="136px" label="创建日期" />
<el-table-column v-if="checkPer(['admin','timing:edit','timing:del'])" label="操作" width="170px" align="center" fixed="right">
<template slot-scope="scope">
<el-button v-permission="['admin','timing:edit']" size="mini" style="margin-right: 3px;" type="text" @click="crud.toEdit(scope.row)">编辑</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: -2px" type="text" size="mini" @click="execute(scope.row.id)">执行</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: 3px" type="text" size="mini" @click="updateStatus(scope.row.id,scope.row.isPause ? '恢复' : '暂停')">
{{ scope.row.isPause ? '恢复' : '暂停' }}
</el-button>
<el-popover
:ref="scope.row.id"
v-permission="['admin','timing:del']"
placement="top"
width="200"
>
<p>确定停止并删除该任务吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="text" size="mini">删除</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudJob from '@/api/system/timing'
import Log from './log'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, jobName: null, subTask: null, beanName: null, methodName: null, params: null, cronExpression: null, pauseAfterFailure: true, isPause: false, personInCharge: null, email: null, description: null }
export default {
name: 'Timing',
components: { Log, pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '定时任务', url: '/system/api/jobs', crudMethod: { ...crudJob }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
delLoading: false,
permission: {
add: ['admin', 'timing:add'],
edit: ['admin', 'timing:edit'],
del: ['admin', 'timing:del']
},
rules: {
jobName: [
{ required: true, message: '请输入任务名称', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入任务描述', trigger: 'blur' }
],
beanName: [
{ required: true, message: '请输入Bean名称', trigger: 'blur' }
],
methodName: [
{ required: true, message: '请输入方法名称', trigger: 'blur' }
],
cronExpression: [
{ required: true, message: '请输入Cron表达式', trigger: 'blur' }
],
personInCharge: [
{ required: true, message: '请输入负责人名称', trigger: 'blur' }
]
}
}
},
methods: {
// 执行
execute(id) {
crudJob.execution(id).then(res => {
this.crud.notify('执行成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
// 改变状态
updateStatus(id, status) {
if (status === '恢复') {
this.updateParams(id)
}
crudJob.updateIsPause(id).then(res => {
this.crud.toQuery()
this.crud.notify(status + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
updateParams(id) {
console.log(id)
},
delMethod(id) {
this.delLoading = true
crudJob.del([id]).then(() => {
this.delLoading = false
this.$refs[id].doClose()
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(() => {
this.delLoading = false
this.$refs[id].doClose()
})
},
// 显示日志
doLog() {
this.$refs.log.dialog = true
this.$refs.log.doInit()
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>

View File

@@ -0,0 +1,104 @@
<template>
<el-dialog :visible.sync="dialog" append-to-body title="执行日志" width="88%">
<!-- 搜索 -->
<div class="head-container">
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.isSuccess" placeholder="日志状态" clearable size="small" class="filter-item" style="width: 110px" @change="toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
<!-- 导出 -->
<div style="display: inline-block;">
<el-button
:loading="downloadLoading"
size="mini"
class="filter-item"
type="warning"
icon="el-icon-download"
@click="downloadMethod"
>导出</el-button>
</div>
</div>
<!--表格渲染-->
<el-table v-loading="loading" :data="data" style="width: 100%;margin-top: -10px;">
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" width="120px" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column prop="createTime" label="异常详情" width="110px">
<template slot-scope="scope">
<el-button v-show="scope.row.exceptionDetail" size="mini" type="text" @click="info(scope.row.exceptionDetail)">查看详情</el-button>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" prop="time" width="100px" label="耗时(毫秒)" />
<el-table-column align="center" prop="isSuccess" width="80px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isSuccess ? 'success' : 'danger'">{{ scope.row.isSuccess ? '成功' : '失败' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" label="创建日期" />
</el-table>
<el-dialog :visible.sync="errorDialog" append-to-body title="异常详情" width="85%">
<pre>{{ errorInfo }}</pre>
</el-dialog>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
:page-size="6"
style="margin-top:8px;"
layout="total, prev, pager, next"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-dialog>
</template>
<script>
import crud from '@/mixins/crud'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { DateRangePicker },
mixins: [crud],
data() {
return {
title: '任务日志',
errorInfo: '', errorDialog: false,
enabledTypeOptions: [
{ key: 'true', display_name: '成功' },
{ key: 'false', display_name: '失败' }
]
}
},
methods: {
doInit() {
this.$nextTick(() => {
this.init()
})
},
// 获取数据前设置好接口地址
beforeInit() {
this.url = '/system/api/jobs/logs'
this.size = 6
return true
},
// 异常详情
info(errorInfo) {
this.errorInfo = errorInfo
this.errorDialog = true
}
}
}
</script>
<style scoped>
.java.hljs{
color: #444;
background: #ffffff !important;
}
::v-deep .el-dialog__body{
padding: 0 20px 10px 20px !important;
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<div class="el-upload">
<img :src="user.avatarName ? baseApi + '/system/avatar/' + user.avatarName : Avatar" title="点击上传头像" class="avatar" @click="toggleShow">
<myUpload
v-model="show"
:headers="headers"
:url="updateAvatarApi"
@crop-upload-success="cropUploadSuccess"
/>
</div>
</div>
<ul class="user-info">
<li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li>
<li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li>
<li><svg-icon icon-class="dept" /> 所属部门 <div class="user-right"> {{ user.dept.name }}</div></li>
<li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li>
<li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li>
<li>
<svg-icon icon-class="anq" /> 安全设置
<div class="user-right">
<a @click="$refs.pass.dialog = true">修改密码</a>
<a @click="$refs.email.dialog = true">修改邮箱</a>
</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
<!-- 用户资料 -->
<el-card class="box-card">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户资料" name="first">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" style="width: 35%;" />
<span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="">
<el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 操作日志 -->
<el-tab-pane label="操作日志" name="second">
<el-table v-loading="loading" :data="data" style="width: 100%;">
<el-table-column prop="description" label="行为" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="time" label="请求耗时" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag>
<el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag>
<el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag>
</template>
</el-table-column>
<el-table-column
align="right"
>
<template slot="header">
<div style="display:inline-block;float: right;cursor: pointer" @click="init">创建日期<i class="el-icon-refresh" style="margin-left: 40px" /></div>
</template>
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<updateEmail ref="email" :email="user.email" />
<updatePass ref="pass" />
</div>
</template>
<script>
import myUpload from 'vue-image-crop-upload'
import { mapGetters } from 'vuex'
import updatePass from './center/updatePass'
import updateEmail from './center/updateEmail'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { isvalidPhone } from '@/utils/validate'
import crud from '@/mixins/crud'
import { editUser } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png'
export default {
name: 'Center',
components: { updatePass, updateEmail, myUpload },
mixins: [crud],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
show: false,
Avatar: Avatar,
activeName: 'first',
saveLoading: false,
headers: {
'Authorization': getToken()
},
form: {},
rules: {
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user',
'updateAvatarApi',
'baseApi'
])
},
created() {
this.form = { id: this.user.id, nickName: this.user.nickName, gender: this.user.gender, phone: this.user.phone }
store.dispatch('GetInfo').then(() => {})
},
methods: {
toggleShow() {
this.show = !this.show
},
handleClick(tab, event) {
if (tab.name === 'second') {
this.init()
}
},
beforeInit() {
this.url = '/system/api/logs/user'
return true
},
cropUploadSuccess(jsonData, field) {
store.dispatch('GetInfo').then(() => {})
},
doSubmit() {
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.saveLoading = true
editUser(this.form).then(() => {
this.editSuccessNotify()
store.dispatch('GetInfo').then(() => {})
this.saveLoading = false
}).catch(() => {
this.saveLoading = false
})
}
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
}
.user-info {
padding-left: 0;
list-style: none;
li{
border-bottom: 1px solid #F0F3F4;
padding: 11px 0;
font-size: 13px;
}
.user-right {
float: right;
a{
color: #317EF3;
}
}
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div style="display: inline-block;">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="新邮箱" prop="email">
<el-input v-model="form.email" auto-complete="on" style="width: 200px;" />
<el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{{ buttonName }}</el-button>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model="form.code" style="width: 320px;" />
</el-form-item>
<el-form-item label="当前密码" prop="pass">
<el-input v-model="form.pass" type="password" style="width: 320px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { validEmail } from '@/utils/validate'
import { updateEmail } from '@/api/system/user'
import { resetEmail } from '@/api/system/code'
export default {
props: {
email: {
type: String,
required: true
}
},
data() {
const validMail = (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('新邮箱不能为空'))
} else if (value === this.email) {
callback(new Error('新邮箱不能与旧邮箱相同'))
} else if (validEmail(value)) {
callback()
} else {
callback(new Error('邮箱格式错误'))
}
}
return {
loading: false, dialog: false, title: '修改邮箱', form: { pass: '', email: '', code: '' },
user: { email: '', password: '' }, codeLoading: false,
buttonName: '获取验证码', isDisabled: false, time: 60,
rules: {
pass: [
{ required: true, message: '当前密码不能为空', trigger: 'blur' }
],
email: [
{ required: true, validator: validMail, trigger: 'blur' }
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
sendCode() {
if (this.form.email && this.form.email !== this.email) {
this.codeLoading = true
this.buttonName = '验证码发送中'
const _this = this
resetEmail(this.form.email).then(res => {
this.$message({
showClose: true,
message: '发送成功验证码有效期5分钟',
type: 'success'
})
this.codeLoading = false
this.isDisabled = true
this.buttonName = this.time-- + '秒后重新发送'
this.timer = window.setInterval(function() {
_this.buttonName = _this.time + '秒后重新发送'
--_this.time
if (_this.time < 0) {
_this.buttonName = '重新发送'
_this.time = 60
_this.isDisabled = false
window.clearInterval(_this.timer)
}
}, 1000)
}).catch(err => {
this.resetForm()
this.codeLoading = false
console.log(err.response.data.message)
})
}
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updateEmail(this.form).then(res => {
this.loading = false
this.resetForm()
this.$notify({
title: '邮箱修改成功',
type: 'success',
duration: 1500
})
store.dispatch('GetInfo').then(() => {})
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
window.clearInterval(this.timer)
this.time = 60
this.buttonName = '获取验证码'
this.isDisabled = false
this.form = { pass: '', email: '', code: '' }
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div style="display: inline-block">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="500px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="旧密码" prop="oldPass">
<el-input v-model="form.oldPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="新密码" prop="newPass">
<el-input v-model="form.newPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPass">
<el-input v-model="form.confirmPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { updatePass } from '@/api/system/user'
export default {
data() {
const confirmPass = (rule, value, callback) => {
if (value) {
if (this.form.newPass !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
} else {
callback(new Error('请再次输入密码'))
}
}
return {
loading: false, dialog: false, title: '修改密码', form: { oldPass: '', newPass: '', confirmPass: '' },
rules: {
oldPass: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }
],
newPass: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPass: [
{ required: true, validator: confirmPass, trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updatePass(this.form).then(res => {
this.resetForm()
this.$notify({
title: '密码修改成功,请重新登录',
type: 'success',
duration: 1500
})
setTimeout(() => {
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
}, 1500)
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
this.form = { oldPass: '', newPass: '', confirmPass: '' }
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,484 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!--侧边部门数据-->
<el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4">
<div class="head-container">
<el-input
v-model="deptName"
clearable
size="small"
placeholdfer="输入部门名称搜索"
prefix-icon="el-icon-search"
class="filter-item"
@input="getDeptDatas"
/>
</div>
<el-tree
:data="deptDatas"
:load="getDeptDatas"
:props="defaultProps"
:expand-on-click-node="false"
lazy
@node-click="handleNodeClick"
/>
</el-col>
<!--用户数据-->
<el-col :xs="15" :sm="18" :md="19" :lg="20" :xl="20">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input
v-model="query.blurry"
clearable
size="small"
placeholder="输入名称或者邮箱搜索"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="crud.toQuery"
/>
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select
v-model="query.enabled"
clearable
size="small"
placeholder="状态"
class="filter-item"
style="width: 90px"
@change="crud.toQuery"
>
<el-option
v-for="item in enabledTypeOptions"
:key="item.key"
:label="item.display_name"
:value="item.key"
/>
</el-select>
<rrOperation />
</div>
<crudOperation show="" :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="570px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model.number="form.phone" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" @keydown.native="keydown($event)" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="部门" prop="dept.id">
<treeselect
v-model="form.dept.id"
:options="depts"
:load-options="loadDepts"
style="width: 178px"
placeholder="选择部门"
/>
</el-form-item>
<el-form-item label="岗位" prop="jobs">
<el-select
v-model="jobDatas"
style="width: 178px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeJob"
>
<el-option
v-for="item in jobs"
:key="item.name"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="">男</el-radio>
<el-radio label="">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.enabled" :disabled="form.id === user.id">
<el-radio
v-for="item in dict.user_status"
:key="item.id"
:label="item.value"
>{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item style="margin-bottom: 0;" label="角色" prop="roles">
<el-select
v-model="roleDatas"
style="width: 437px"
multiple
placeholder="请选择"
@remove-tag="deleteTag"
@change="changeRole"
>
<el-option
v-for="item in roles"
:key="item.name"
:disabled="level !== 1 && item.level <= level"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="username" label="用户名" />
<el-table-column :show-overflow-tooltip="true" prop="nickName" label="昵称" />
<el-table-column prop="gender" label="性别" />
<el-table-column :show-overflow-tooltip="true" prop="phone" width="100" label="电话" />
<el-table-column :show-overflow-tooltip="true" width="135" prop="email" label="邮箱" />
<el-table-column :show-overflow-tooltip="true" prop="dept" label="部门">
<template slot-scope="scope">
<div>{{ scope.row.dept.name }}</div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="user.id === scope.row.id"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="135" label="创建日期" />
<el-table-column
v-if="checkPer(['admin','user:edit','user:del'])"
label="操作"
width="115"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === user.id"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-col>
</el-row>
</div>
</template>
<script>
import crudUser from '@/api/system/user'
import { isvalidPhone } from '@/utils/validate'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getAll, getLevel } from '@/api/system/role'
import { getAllJob } from '@/api/system/job'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
import Treeselect from '@riophae/vue-treeselect'
import { mapGetters } from 'vuex'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
let userRoles = []
let userJobs = []
const defaultForm = { id: null, username: null, nickName: null, gender: '男', email: null, enabled: 'false', roles: [], jobs: [], dept: { id: null }, phone: null }
export default {
name: 'User',
components: { Treeselect, crudOperation, rrOperation, udOperation, pagination, DateRangePicker },
cruds() {
return CRUD({ title: '用户', url: '/system/api/users', crudMethod: { ...crudUser }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 数据字典
dicts: ['user_status'],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
height: document.documentElement.clientHeight - 180 + 'px;',
deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [],
jobDatas: [], roleDatas: [], // 多选时使用
defaultProps: { children: 'children', label: 'name', isLeaf: 'leaf' },
permission: {
add: ['admin', 'user:add'],
edit: ['admin', 'user:edit'],
del: ['admin', 'user:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '激活' },
{ key: 'false', display_name: '锁定' }
],
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user'
])
},
created() {
this.crud.msg.add = '新增成功默认密码123456'
},
mounted: function() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 180 + 'px;'
}
},
methods: {
// 禁止输入空格
keydown(e) {
if (e.keyCode === 32) {
e.returnValue = false
}
},
changeRole(value) {
userRoles = []
value.forEach(function(data, index) {
const role = { id: data }
userRoles.push(role)
})
},
changeJob(value) {
userJobs = []
value.forEach(function(data, index) {
const job = { id: data }
userJobs.push(job)
})
},
deleteTag(value) {
userRoles.forEach(function(data, index) {
if (data.id === value) {
userRoles.splice(index, value)
}
})
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.getRoles()
if (form.id == null) {
this.getDepts()
} else {
this.getSupDepts(form.dept.id)
}
this.getRoleLevel()
this.getJobs()
form.enabled = form.enabled.toString()
},
// 新增前将多选的值设置为空
[CRUD.HOOK.beforeToAdd]() {
this.jobDatas = []
this.roleDatas = []
},
// 初始化编辑时候的角色与岗位
[CRUD.HOOK.beforeToEdit](crud, form) {
this.getJobs(this.form.dept.id)
this.jobDatas = []
this.roleDatas = []
userRoles = []
userJobs = []
const _this = this
form.roles.forEach(function(role, index) {
_this.roleDatas.push(role.id)
const rol = { id: role.id }
userRoles.push(rol)
})
form.jobs.forEach(function(job, index) {
_this.jobDatas.push(job.id)
const data = { id: job.id }
userJobs.push(data)
})
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
if (!crud.form.dept.id) {
this.$message({
message: '部门不能为空',
type: 'warning'
})
return false
} else if (this.jobDatas.length === 0) {
this.$message({
message: '岗位不能为空',
type: 'warning'
})
return false
} else if (this.roleDatas.length === 0) {
this.$message({
message: '角色不能为空',
type: 'warning'
})
return false
}
crud.form.roles = userRoles
crud.form.jobs = userJobs
return true
},
// 获取左侧部门数据
getDeptDatas(node, resolve) {
const sort = 'id,desc'
const params = { sort: sort }
if (typeof node !== 'object') {
if (node) {
params['name'] = node
}
} else if (node.level !== 0) {
params['pid'] = node.data.id
}
setTimeout(() => {
getDepts(params).then(res => {
if (resolve) {
resolve(res.content)
} else {
this.deptDatas = res.content
}
})
}, 100)
},
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(deptId) {
getDeptSuperior(deptId).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 切换部门
handleNodeClick(data) {
if (data.pid === 0) {
this.query.deptId = null
} else {
this.query.deptId = data.id
}
this.crud.toQuery()
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudUser.edit(data).then(res => {
this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(() => {
data.enabled = !data.enabled
})
}).catch(() => {
data.enabled = !data.enabled
})
},
// 获取弹窗内角色数据
getRoles() {
getAll().then(res => {
this.roles = res
}).catch(() => { })
},
// 获取弹窗内岗位数据
getJobs() {
getAllJob().then(res => {
this.jobs = res.content
}).catch(() => { })
},
// 获取权限级别
getRoleLevel() {
getLevel().then(res => {
this.level = res.level
}).catch(() => { })
},
checkboxT(row, rowIndex) {
return row.id !== this.user.id
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>

107
src/views/home.vue Normal file
View File

@@ -0,0 +1,107 @@
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<github-corner class="github-corner" />
<panel-group @handleSetLineChartData="handleSetLineChartData" />
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData" />
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<radar-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<pie-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<bar-chart />
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import GithubCorner from '@/components/GithubCorner'
import PanelGroup from './dashboard/PanelGroup'
import LineChart from './dashboard/LineChart'
import RadarChart from '@/components/Echarts/RadarChart'
import PieChart from '@/components/Echarts/PieChart'
import BarChart from '@/components/Echarts/BarChart'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
}
export default {
name: 'Dashboard',
components: {
GithubCorner,
PanelGroup,
LineChart,
RadarChart,
PieChart,
BarChart
},
data() {
return {
lineChartData: lineChartData.newVisitis
}
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
background-color: rgb(240, 242, 245);
position: relative;
.github-corner {
position: absolute;
top: 0;
border: 0;
right: 0;
}
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
@media (max-width:1024px) {
.chart-wrapper {
padding: 8px;
}
}
</style>

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="部门排序" prop="deptSort">
<el-input-number
v-model.number="form.deptSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item label="顶级部门">
<el-radio-group v-model="form.isTop" style="width: 140px">
<el-radio label="1">是</el-radio>
<el-radio label="0">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect
v-model="form.pid"
:load-options="loadDepts"
:options="depts"
style="width: 370px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getDeptDatas"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:data="crud.data"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="排序" prop="deptSort" />
<el-table-column label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '部门', url: '/system/api/dept', crudMethod: { ...crudDept }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
// 设置数据字典
dicts: ['dept_status'],
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
deptSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 'true', display_name: '正常' },
{ key: 'false', display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
crudDept.getDeptSuperior(id).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 提交前的验证
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div>
<div v-if="query.dictName === ''">
<div class="my-code">点击字典查看详情</div>
</div>
<div v-else>
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.label" clearable size="small" placeholder="输入字典标签查询" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<rrOperation />
</div>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典标签" prop="label">
<el-input v-model="form.label" style="width: 370px;" />
</el-form-item>
<el-form-item label="字典值" prop="value">
<el-input v-model="form.value" style="width: 370px;" />
</el-form-item>
<el-form-item label="排序" prop="dictSort">
<el-input-number v-model.number="form.dictSort" :min="0" :max="999" controls-position="right" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column label="所属字典">
{{ query.dictName }}
</el-table-column>
<el-table-column prop="label" label="字典标签" />
<el-table-column prop="value" label="字典值" />
<el-table-column prop="dictSort" label="排序" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</div>
</template>
<script>
import crudDictDetail from '@/api/system/dictDetail'
import CRUD, { presenter, header, form } from '@crud/crud'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, label: null, value: null, dictSort: 999 }
export default {
components: { pagination, rrOperation, udOperation },
cruds() {
return [
CRUD({ title: '字典详情', url: '/system/api/dictDetail', query: { dictName: '' }, sort: ['dictSort,asc', 'id,desc'],
crudMethod: { ...crudDictDetail },
optShow: {
add: true,
edit: true,
del: true,
reset: false
},
queryOnPresenterCreated: false
})
]
},
mixins: [
presenter(),
header(),
form(function() {
return Object.assign({ dict: { id: this.dictId }}, defaultForm)
})],
data() {
return {
dictId: null,
rules: {
label: [
{ required: true, message: '请输入字典标签', trigger: 'blur' }
],
value: [
{ required: true, message: '请输入字典值', trigger: 'blur' }
],
dictSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<div class="app-container">
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!-- 字典列表 -->
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="10" :lg="11" :xl="11" style="margin-bottom: 10px">
<el-card class="box-card">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="name" label="名称" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 字典详情列表 -->
<el-col :xs="24" :sm="24" :md="14" :lg="13" :xl="13">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>字典详情</span>
<el-button
v-if="checkPer(['admin','dict:add']) && this.$refs.dictDetail && this.$refs.dictDetail.query.dictName"
class="filter-item"
size="mini"
style="float: right;padding: 4px 10px"
type="primary"
icon="el-icon-plus"
@click="$refs.dictDetail && $refs.dictDetail.crud.toAdd()"
>新增</el-button>
</div>
<dictDetail ref="dictDetail" :permission="permission" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import dictDetail from './dictDetail'
import crudDict from '@/api/system/dict'
import CRUD, { presenter, header, form } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import rrOperation from '@crud/RR.operation'
import udOperation from '@crud/UD.operation'
const defaultForm = { id: null, name: null, description: null, dictDetails: [] }
export default {
name: 'Dict',
components: { crudOperation, pagination, rrOperation, udOperation, dictDetail },
cruds() {
return [
CRUD({ title: '字典', url: '/system/api/dict', crudMethod: { ...crudDict }})
]
},
mixins: [presenter(), header(), form(defaultForm)],
data() {
return {
queryTypeOptions: [
{ key: 'name', display_name: '字典名称' },
{ key: 'description', display_name: '描述' }
],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
},
permission: {
add: ['admin', 'dict:add'],
edit: ['admin', 'dict:edit'],
del: ['admin', 'dict:del']
}
}
},
methods: {
// 获取数据前设置好接口地址
[CRUD.HOOK.beforeRefresh]() {
if (this.$refs.dictDetail) {
this.$refs.dictDetail.query.dictName = ''
}
return true
},
// 选中字典后,设置字典详情数据
handleCurrentChange(val) {
if (val) {
this.$refs.dictDetail.query.dictName = val.name
this.$refs.dictDetail.dictId = val.id
this.$refs.dictDetail.crud.toQuery()
}
},
// 编辑前将字典明细临时清空,避免日志入库数据过长
[CRUD.HOOK.beforeToEdit](crud, form) {
// 将角色的菜单清空,避免日志入库数据过长
form.dictDetails = null
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<eHeader :dict="dict" :permission="permission" />
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="jobSort" label="排序">
<template slot-scope="scope">
{{ scope.row.jobSort }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
active-color="#409EFF"
inactive-color="#F56C6C"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" />
<!-- 编辑与删除 -->
<el-table-column
v-if="checkPer(['admin','job:edit','job:del'])"
label="操作"
width="130px"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
<!--表单渲染-->
<eForm :job-status="dict.job_status" />
</div>
</template>
<script>
import crudJob from '@/api/system/job'
import eHeader from './module/header'
import eForm from './module/form'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import udOperation from '@crud/UD.operation'
export default {
name: 'Job',
components: { eHeader, eForm, crudOperation, pagination, udOperation },
cruds() {
return CRUD({
title: '岗位',
url: '/system/api/job',
sort: ['jobSort,asc', 'id,desc'],
crudMethod: { ...crudJob }
})
},
mixins: [presenter()],
// 数据字典
dicts: ['job_status'],
data() {
return {
permission: {
add: ['admin', 'job:add'],
edit: ['admin', 'job:edit'],
del: ['admin', 'job:del']
}
}
},
methods: {
// 改变状态
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.job_status[val] + '" ' + data.name + '岗位, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// eslint-disable-next-line no-undef
crudJob.edit(data).then(() => {
// eslint-disable-next-line no-undef
this.crud.notify(this.dict.label.job_status[val] + '成功', 'success')
}).catch(err => {
data.enabled = !data.enabled
console.log(err.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<el-dialog
append-to-body
:close-on-click-modal="false"
:before-close="crud.cancelCU"
:visible="crud.status.cu > 0"
:title="crud.status.title"
width="500px"
>
<el-form
ref="form"
:model="form"
:rules="rules"
size="small"
label-width="80px"
>
<el-form-item
label="名称"
prop="name"
>
<el-input
v-model="form.name"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
label="排序"
prop="jobSort"
>
<el-input-number
v-model.number="form.jobSort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
v-if="form.pid !== 0"
label="状态"
prop="enabled"
>
<el-radio
v-for="item in jobStatus"
:key="item.id"
v-model="form.enabled"
:label="item.value === 'true'"
>
{{ item.label }}
</el-radio>
</el-form-item>
</el-form>
<div
slot="footer"
class="dialog-footer"
>
<el-button
type="text"
@click="crud.cancelCU"
>
取消
</el-button>
<el-button
:loading="crud.status.cu === 2"
type="primary"
@click="crud.submitCU"
>
确认
</el-button>
</div>
</el-dialog>
</template>
<script>
import { form } from '@crud/crud'
const defaultForm = {
id: null,
name: '',
jobSort: 999,
enabled: true
}
export default {
mixins: [form(defaultForm)],
props: {
jobStatus: {
type: Array,
required: true
}
},
data() {
return {
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
jobSort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div
v-if="crud.props.searchToggle"
>
<el-input v-model="query.name" clearable size="small" placeholder="输入岗位名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in dict.dict.job_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<rrOperation />
</div>
</template>
<script>
import { header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { rrOperation, DateRangePicker },
mixins: [header()],
props: {
dict: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
}
}
}
</script>

View File

@@ -0,0 +1,252 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="580px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type" size="mini" style="width: 178px">
<el-radio-button label="0">目录</el-radio-button>
<el-radio-button label="1">菜单</el-radio-button>
<el-radio-button label="2">按钮</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="450"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame">
<el-radio-group v-model="form.iFrame" size="mini">
<el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache">
<el-radio-group v-model="form.cache" size="mini">
<el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden">
<el-radio-group v-model="form.hidden" size="mini">
<el-radio-button label="false">是</el-radio-button>
<el-radio-button label="true">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title">
<el-input v-model="form.title" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" placeholder="菜单标题" />
</el-form-item>
<el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title">
<el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission">
<el-input v-model="form.permission" :disabled="form.iFrame.toString() === 'true'" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path">
<el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" />
</el-form-item>
<el-form-item label="菜单排序" prop="menuSort">
<el-input-number v-model.number="form.menuSort" :min="0" :max="999" controls-position="right" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件名称" prop="componentName">
<el-input v-model="form.componentName" style="width: 178px;" placeholder="匹配组件内Name字段" />
</el-form-item>
<el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件路径" prop="component">
<el-input v-model="form.component" style="width: 178px;" placeholder="组件路径" />
</el-form-item>
<el-form-item label="上级类目" prop="pid">
<treeselect
v-model="form.pid"
:options="menus"
:load-options="loadMenus"
style="width: 450px;"
placeholder="选择上级类目"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table
ref="table"
v-loading="crud.loading"
lazy
:load="getMenus"
:data="crud.data"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id"
@select="crud.selectChange"
@select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" label="菜单标题" width="125px" prop="title" />
<el-table-column prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" />
</template>
</el-table-column>
<el-table-column prop="menuSort" align="center" label="排序">
<template slot-scope="scope">
{{ scope.row.menuSort }}
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" />
<el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" />
<el-table-column prop="iFrame" label="外链" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.iFrame">是</span>
<span v-else>否</span>
</template>
</el-table-column>
<el-table-column prop="cache" label="缓存" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.cache">是</span>
<span v-else>否</span>
</template>
</el-table-column>
<el-table-column prop="hidden" label="可见" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.hidden">否</span>
<span v-else>是</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" width="135px" />
<el-table-column v-if="checkPer(['admin','menu:edit','menu:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
msg="确定删除吗,如果存在下级节点则一并删除此操作不能撤销"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudMenu from '@/api/system/menu'
import IconSelect from '@/components/IconSelect'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker'
// crud交由presenter持有
const defaultForm = { id: null, title: null, menuSort: 999, path: null, component: null, componentName: null, iFrame: false, roles: [], pid: 0, icon: null, cache: false, hidden: false, type: 0, permission: null }
export default {
name: 'Menu',
components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '菜单', url: '/system/api/menus', crudMethod: { ...crudMenu }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
menus: [],
permission: {
add: ['admin', 'menu:add'],
edit: ['admin', 'menu:edit'],
del: ['admin', 'menu:del']
},
rules: {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
path: [
{ required: true, message: '请输入地址', trigger: 'blur' }
]
}
}
},
methods: {
// 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) {
this.menus = []
if (form.id != null) {
if (form.pid === null) {
form.pid = 0
}
this.getSupDepts(form.id)
} else {
this.menus.push({ id: 0, label: '顶级类目', children: null })
}
},
getMenus(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudMenu.getMenus(params).then(res => {
resolve(res.content)
})
}, 100)
},
getSupDepts(id) {
crudMenu.getMenuSuperior(id).then(res => {
const children = res.map(function(obj) {
if (!obj.leaf && !obj.children) {
obj.children = null
}
return obj
})
this.menus = [{ id: 0, label: '顶级类目', children: children }]
})
},
loadMenus({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudMenu.getMenusTree(parentNode.id).then(res => {
parentNode.children = res.map(function(obj) {
if (!obj.leaf) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
// 选中图标
selected(name) {
this.form.icon = name
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>

View File

@@ -0,0 +1,360 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" size="small" clearable placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission" />
</div>
<!-- 表单渲染 -->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="520px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" style="width: 380px;" />
</el-form-item>
<el-form-item label="角色级别" prop="level">
<el-input-number v-model.number="form.level" :min="1" controls-position="right" style="width: 145px;" />
</el-form-item>
<el-form-item label="数据范围" prop="dataScope">
<el-select v-model="form.dataScope" style="width: 140px" placeholder="请选择数据范围" @change="changeScope">
<el-option
v-for="item in dateScopes"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item v-if="form.dataScope === '自定义'" label="数据权限" prop="depts">
<treeselect
v-model="deptDatas"
:load-options="loadDepts"
:options="depts"
multiple
style="width: 380px"
placeholder="请选择"
/>
</el-form-item>
<el-form-item label="描述信息" prop="description">
<el-input v-model="form.description" style="width: 380px;" rows="5" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<el-row :gutter="15">
<!--角色管理-->
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="17" style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">角色列表</span>
</div>
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" :data="crud.data" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="dataScope" label="数据权限" />
<el-table-column prop="level" label="角色级别" />
<el-table-column :show-overflow-tooltip="true" prop="description" label="描述" />
<el-table-column :show-overflow-tooltip="true" width="135px" prop="createTime" label="创建日期" />
<el-table-column v-if="checkPer(['admin','roles:edit','roles:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
v-if="scope.row.level >= level"
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 菜单授权 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="7">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<el-tooltip class="item" effect="dark" content="选择指定角色分配菜单" placement="top">
<span class="role-span">菜单分配</span>
</el-tooltip>
<el-button
v-permission="['admin','roles:edit']"
:disabled="!showButton"
:loading="menuLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="saveMenu"
>保存</el-button>
</div>
<el-tree
ref="menu"
lazy
:data="menus"
:default-checked-keys="menuIds"
:load="getMenuDatas"
:props="defaultProps"
check-strictly
accordion
show-checkbox
node-key="id"
@check="menuChange"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crudRoles from '@/api/system/role'
import { getDepts, getDeptSuperior } from '@/api/system/dept'
import { getMenusTree, getChild } from '@/api/system/menu'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, name: null, depts: [], description: null, dataScope: '全部', level: 3 }
export default {
name: 'Role',
components: { Treeselect, pagination, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() {
return CRUD({ title: '角色', url: '/system/api/roles', sort: 'level,asc', crudMethod: { ...crudRoles }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
defaultProps: { children: 'children', label: 'label', isLeaf: 'leaf' },
dateScopes: ['全部', '本级', '自定义'], level: 3,
currentId: 0, menuLoading: false, showButton: false,
menus: [], menuIds: [], depts: [], deptDatas: [], // 多选时使用
permission: {
add: ['admin', 'roles:add'],
edit: ['admin', 'roles:edit'],
del: ['admin', 'roles:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
permission: [
{ required: true, message: '请输入权限', trigger: 'blur' }
]
}
}
},
created() {
crudRoles.getLevel().then(data => {
this.level = data.level
})
},
methods: {
getMenuDatas(node, resolve) {
setTimeout(() => {
getMenusTree(node.data.id ? node.data.id : 0).then(res => {
resolve(res)
})
}, 100)
},
[CRUD.HOOK.afterRefresh]() {
this.$refs.menu.setCheckedKeys([])
},
// 新增前初始化部门信息
[CRUD.HOOK.beforeToAdd](crud, form) {
this.deptDatas = []
form.menus = null
},
// 编辑前初始化自定义数据权限的部门信息
[CRUD.HOOK.beforeToEdit](crud, form) {
this.deptDatas = []
if (form.dataScope === '自定义') {
this.getSupDepts(form.depts)
}
const _this = this
form.depts.forEach(function(dept) {
_this.deptDatas.push(dept.id)
})
// 将角色的菜单清空,避免日志入库数据过长
form.menus = null
},
// 提交前做的操作
[CRUD.HOOK.afterValidateCU](crud) {
if (crud.form.dataScope === '自定义' && this.deptDatas.length === 0) {
this.$message({
message: '自定义数据权限不能为空',
type: 'warning'
})
return false
} else if (crud.form.dataScope === '自定义') {
const depts = []
this.deptDatas.forEach(function(data) {
const dept = { id: data }
depts.push(dept)
})
crud.form.depts = depts
} else {
crud.form.depts = []
}
return true
},
// 触发单选
handleCurrentChange(val) {
if (val) {
const _this = this
// 清空菜单的选中
this.$refs.menu.setCheckedKeys([])
// 保存当前的角色id
this.currentId = val.id
// 初始化默认选中的key
this.menuIds = []
val.menus.forEach(function(data) {
_this.menuIds.push(data.id)
})
this.showButton = true
}
},
menuChange(menu) {
// 获取该节点的所有子节点id 包含自身
getChild(menu.id).then(childIds => {
// 判断是否在 menuIds 中,如果存在则删除,否则添加
if (this.menuIds.indexOf(menu.id) !== -1) {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index !== -1) {
this.menuIds.splice(index, 1)
}
}
} else {
for (let i = 0; i < childIds.length; i++) {
const index = this.menuIds.indexOf(childIds[i])
if (index === -1) {
this.menuIds.push(childIds[i])
}
}
}
this.$refs.menu.setCheckedKeys(this.menuIds)
})
},
// 保存菜单
saveMenu() {
this.menuLoading = true
const role = { id: this.currentId, menus: [] }
// 得到已选中的 key 值
this.menuIds.forEach(function(id) {
const menu = { id: id }
role.menus.push(menu)
})
crudRoles.editMenu(role).then(() => {
this.crud.notify('保存成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.menuLoading = false
this.update()
}).catch(err => {
this.menuLoading = false
console.log(err.response.data.message)
})
},
// 改变数据
update() {
// 无刷新更新 表格数据
crudRoles.get(this.currentId).then(res => {
for (let i = 0; i < this.crud.data.length; i++) {
if (res.id === this.crud.data[i].id) {
this.crud.data[i] = res
break
}
}
})
},
// 获取部门数据
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
getSupDepts(depts) {
const ids = []
depts.forEach(dept => {
ids.push(dept.id)
})
getDeptSuperior(ids).then(res => {
const date = res.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
depts.forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
// 获取弹窗内部门数据
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 200)
})
}
},
// 如果数据权限为自定义则获取部门数据
changeScope() {
if (this.form.dataScope === '自定义') {
this.getDepts()
}
},
checkboxT(row) {
return row.level >= this.level
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.role-span {
font-weight: bold;color: #303133;
font-size: 15px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
::v-deep .vue-treeselect__multi-value{
margin-bottom: 0;
}
::v-deep .vue-treeselect__multi-value-item{
border: 0;
padding: 0;
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<!-- 任务日志 -->
<el-button
slot="right"
class="filter-item"
size="mini"
type="info"
icon="el-icon-tickets"
@click="doLog"
>日志</el-button>
</crudOperation>
<Log ref="log" />
</div>
<!--Form表单-->
<el-dialog :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" append-to-body width="730px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="100px">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务描述" prop="description">
<el-input v-model="form.description" style="width: 220px;" />
</el-form-item>
<el-form-item label="Bean名称" prop="beanName">
<el-input v-model="form.beanName" style="width: 220px;" />
</el-form-item>
<el-form-item label="执行方法" prop="methodName">
<el-input v-model="form.methodName" style="width: 220px;" />
</el-form-item>
<el-form-item label="Cron表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" style="width: 220px;" />
</el-form-item>
<el-form-item label="子任务ID">
<el-input v-model="form.subTask" placeholder="多个用逗号隔开按顺序执行" style="width: 220px;" />
</el-form-item>
<el-form-item label="任务负责人" prop="personInCharge">
<el-input v-model="form.personInCharge" style="width: 220px;" />
</el-form-item>
<el-form-item label="告警邮箱" prop="email">
<el-input v-model="form.email" placeholder="多个邮箱用逗号隔开" style="width: 220px;" />
</el-form-item>
<el-form-item label="失败后暂停">
<el-radio-group v-model="form.pauseAfterFailure" style="width: 220px">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="任务状态">
<el-radio-group v-model="form.isPause" style="width: 220px">
<el-radio :label="false">启用</el-radio>
<el-radio :label="true">暂停</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="参数内容">
<el-input v-model="form.params" style="width: 556px;" rows="4" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" prop="id" label="任务ID" />
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column :show-overflow-tooltip="true" prop="isPause" width="90px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isPause ? 'warning' : 'success'">{{ scope.row.isPause ? '已暂停' : '运行中' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="description" width="150px" label="描述" />
<el-table-column :show-overflow-tooltip="true" prop="createTime" width="136px" label="创建日期" />
<el-table-column v-if="checkPer(['admin','timing:edit','timing:del'])" label="操作" width="170px" align="center" fixed="right">
<template slot-scope="scope">
<el-button v-permission="['admin','timing:edit']" size="mini" style="margin-right: 3px;" type="text" @click="crud.toEdit(scope.row)">编辑</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: -2px" type="text" size="mini" @click="execute(scope.row.id)">执行</el-button>
<el-button v-permission="['admin','timing:edit']" style="margin-left: 3px" type="text" size="mini" @click="updateStatus(scope.row.id,scope.row.isPause ? '恢复' : '暂停')">
{{ scope.row.isPause ? '恢复' : '暂停' }}
</el-button>
<el-popover
:ref="scope.row.id"
v-permission="['admin','timing:del']"
placement="top"
width="200"
>
<p>确定停止并删除该任务吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="text" size="mini">删除</el-button>
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</div>
</template>
<script>
import crudJob from '@/api/system/timing'
import Log from './log'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
const defaultForm = { id: null, jobName: null, subTask: null, beanName: null, methodName: null, params: null, cronExpression: null, pauseAfterFailure: true, isPause: false, personInCharge: null, email: null, description: null }
export default {
name: 'Timing',
components: { Log, pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '定时任务', url: '/system/api/jobs', crudMethod: { ...crudJob }})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
delLoading: false,
permission: {
add: ['admin', 'timing:add'],
edit: ['admin', 'timing:edit'],
del: ['admin', 'timing:del']
},
rules: {
jobName: [
{ required: true, message: '请输入任务名称', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入任务描述', trigger: 'blur' }
],
beanName: [
{ required: true, message: '请输入Bean名称', trigger: 'blur' }
],
methodName: [
{ required: true, message: '请输入方法名称', trigger: 'blur' }
],
cronExpression: [
{ required: true, message: '请输入Cron表达式', trigger: 'blur' }
],
personInCharge: [
{ required: true, message: '请输入负责人名称', trigger: 'blur' }
]
}
}
},
methods: {
// 执行
execute(id) {
crudJob.execution(id).then(res => {
this.crud.notify('执行成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
// 改变状态
updateStatus(id, status) {
if (status === '恢复') {
this.updateParams(id)
}
crudJob.updateIsPause(id).then(res => {
this.crud.toQuery()
this.crud.notify(status + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
console.log(err.response.data.message)
})
},
updateParams(id) {
console.log(id)
},
delMethod(id) {
this.delLoading = true
crudJob.del([id]).then(() => {
this.delLoading = false
this.$refs[id].doClose()
this.crud.dleChangePage(1)
this.crud.delSuccessNotify()
this.crud.toQuery()
}).catch(() => {
this.delLoading = false
this.$refs[id].doClose()
})
},
// 显示日志
doLog() {
this.$refs.log.dialog = true
this.$refs.log.doInit()
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>

View File

@@ -0,0 +1,104 @@
<template>
<el-dialog :visible.sync="dialog" append-to-body title="执行日志" width="88%">
<!-- 搜索 -->
<div class="head-container">
<el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<date-range-picker v-model="query.createTime" class="date-item" />
<el-select v-model="query.isSuccess" placeholder="日志状态" clearable size="small" class="filter-item" style="width: 110px" @change="toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
<!-- 导出 -->
<div style="display: inline-block;">
<el-button
:loading="downloadLoading"
size="mini"
class="filter-item"
type="warning"
icon="el-icon-download"
@click="downloadMethod"
>导出</el-button>
</div>
</div>
<!--表格渲染-->
<el-table v-loading="loading" :data="data" style="width: 100%;margin-top: -10px;">
<el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
<el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" />
<el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" />
<el-table-column :show-overflow-tooltip="true" prop="params" width="120px" label="参数" />
<el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" />
<el-table-column prop="createTime" label="异常详情" width="110px">
<template slot-scope="scope">
<el-button v-show="scope.row.exceptionDetail" size="mini" type="text" @click="info(scope.row.exceptionDetail)">查看详情</el-button>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" prop="time" width="100px" label="耗时(毫秒)" />
<el-table-column align="center" prop="isSuccess" width="80px" label="状态">
<template slot-scope="scope">
<el-tag :type="scope.row.isSuccess ? 'success' : 'danger'">{{ scope.row.isSuccess ? '成功' : '失败' }}</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="createTime" label="创建日期" />
</el-table>
<el-dialog :visible.sync="errorDialog" append-to-body title="异常详情" width="85%">
<pre>{{ errorInfo }}</pre>
</el-dialog>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
:page-size="6"
style="margin-top:8px;"
layout="total, prev, pager, next"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-dialog>
</template>
<script>
import crud from '@/mixins/crud'
import DateRangePicker from '@/components/DateRangePicker'
export default {
components: { DateRangePicker },
mixins: [crud],
data() {
return {
title: '任务日志',
errorInfo: '', errorDialog: false,
enabledTypeOptions: [
{ key: 'true', display_name: '成功' },
{ key: 'false', display_name: '失败' }
]
}
},
methods: {
doInit() {
this.$nextTick(() => {
this.init()
})
},
// 获取数据前设置好接口地址
beforeInit() {
this.url = '/system/api/jobs/logs'
this.size = 6
return true
},
// 异常详情
info(errorInfo) {
this.errorInfo = errorInfo
this.errorDialog = true
}
}
}
</script>
<style scoped>
.java.hljs{
color: #444;
background: #ffffff !important;
}
::v-deep .el-dialog__body{
padding: 0 20px 10px 20px !important;
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<div class="el-upload">
<img :src="user.avatarName ? baseApi + '/system/avatar/' + user.avatarName : Avatar" title="点击上传头像" class="avatar" @click="toggleShow">
<myUpload
v-model="show"
:headers="headers"
:url="updateAvatarApi"
@crop-upload-success="cropUploadSuccess"
/>
</div>
</div>
<ul class="user-info">
<li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li>
<li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li>
<li><svg-icon icon-class="dept" /> 所属部门 <div class="user-right"> {{ user.dept.name }}</div></li>
<li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li>
<li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li>
<li>
<svg-icon icon-class="anq" /> 安全设置
<div class="user-right">
<a @click="$refs.pass.dialog = true">修改密码</a>
<a @click="$refs.email.dialog = true">修改邮箱</a>
</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
<!-- 用户资料 -->
<el-card class="box-card">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户资料" name="first">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" style="width: 35%;" />
<span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="">
<el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 操作日志 -->
<el-tab-pane label="操作日志" name="second">
<el-table v-loading="loading" :data="data" style="width: 100%;">
<el-table-column prop="description" label="行为" />
<el-table-column prop="requestIp" label="IP" />
<el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" />
<el-table-column prop="browser" label="浏览器" />
<el-table-column prop="time" label="请求耗时" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag>
<el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag>
<el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag>
</template>
</el-table-column>
<el-table-column
align="right"
>
<template slot="header">
<div style="display:inline-block;float: right;cursor: pointer" @click="init">创建日期<i class="el-icon-refresh" style="margin-left: 40px" /></div>
</template>
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<updateEmail ref="email" :email="user.email" />
<updatePass ref="pass" />
</div>
</template>
<script>
import myUpload from 'vue-image-crop-upload'
import { mapGetters } from 'vuex'
import updatePass from './center/updatePass'
import updateEmail from './center/updateEmail'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { isvalidPhone } from '@/utils/validate'
import crud from '@/mixins/crud'
import { editUser } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png'
export default {
name: 'Center',
components: { updatePass, updateEmail, myUpload },
mixins: [crud],
data() {
// 自定义验证
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
show: false,
Avatar: Avatar,
activeName: 'first',
saveLoading: false,
headers: {
'Authorization': getToken()
},
form: {},
rules: {
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user',
'updateAvatarApi',
'baseApi'
])
},
created() {
this.form = { id: this.user.id, nickName: this.user.nickName, gender: this.user.gender, phone: this.user.phone }
store.dispatch('GetInfo').then(() => {})
},
methods: {
toggleShow() {
this.show = !this.show
},
handleClick(tab, event) {
if (tab.name === 'second') {
this.init()
}
},
beforeInit() {
this.url = '/system/api/logs/user'
return true
},
cropUploadSuccess(jsonData, field) {
store.dispatch('GetInfo').then(() => {})
},
doSubmit() {
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.saveLoading = true
editUser(this.form).then(() => {
this.editSuccessNotify()
store.dispatch('GetInfo').then(() => {})
this.saveLoading = false
}).catch(() => {
this.saveLoading = false
})
}
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
}
.user-info {
padding-left: 0;
list-style: none;
li{
border-bottom: 1px solid #F0F3F4;
padding: 11px 0;
font-size: 13px;
}
.user-right {
float: right;
a{
color: #317EF3;
}
}
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div style="display: inline-block;">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="新邮箱" prop="email">
<el-input v-model="form.email" auto-complete="on" style="width: 200px;" />
<el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{{ buttonName }}</el-button>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model="form.code" style="width: 320px;" />
</el-form-item>
<el-form-item label="当前密码" prop="pass">
<el-input v-model="form.pass" type="password" style="width: 320px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { validEmail } from '@/utils/validate'
import { updateEmail } from '@/api/system/user'
import { resetEmail } from '@/api/system/code'
export default {
props: {
email: {
type: String,
required: true
}
},
data() {
const validMail = (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('新邮箱不能为空'))
} else if (value === this.email) {
callback(new Error('新邮箱不能与旧邮箱相同'))
} else if (validEmail(value)) {
callback()
} else {
callback(new Error('邮箱格式错误'))
}
}
return {
loading: false, dialog: false, title: '修改邮箱', form: { pass: '', email: '', code: '' },
user: { email: '', password: '' }, codeLoading: false,
buttonName: '获取验证码', isDisabled: false, time: 60,
rules: {
pass: [
{ required: true, message: '当前密码不能为空', trigger: 'blur' }
],
email: [
{ required: true, validator: validMail, trigger: 'blur' }
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
sendCode() {
if (this.form.email && this.form.email !== this.email) {
this.codeLoading = true
this.buttonName = '验证码发送中'
const _this = this
resetEmail(this.form.email).then(res => {
this.$message({
showClose: true,
message: '发送成功验证码有效期5分钟',
type: 'success'
})
this.codeLoading = false
this.isDisabled = true
this.buttonName = this.time-- + '秒后重新发送'
this.timer = window.setInterval(function() {
_this.buttonName = _this.time + '秒后重新发送'
--_this.time
if (_this.time < 0) {
_this.buttonName = '重新发送'
_this.time = 60
_this.isDisabled = false
window.clearInterval(_this.timer)
}
}, 1000)
}).catch(err => {
this.resetForm()
this.codeLoading = false
console.log(err.response.data.message)
})
}
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updateEmail(this.form).then(res => {
this.loading = false
this.resetForm()
this.$notify({
title: '邮箱修改成功',
type: 'success',
duration: 1500
})
store.dispatch('GetInfo').then(() => {})
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
window.clearInterval(this.timer)
this.time = 60
this.buttonName = '获取验证码'
this.isDisabled = false
this.form = { pass: '', email: '', code: '' }
}
}
}
</script>
<style scoped>
</style>

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