最终效果:
新增
查询页面:
<template>
<div class="app-container">
<el-row>
<el-col :span="12">
<el-card>
<el-statistic
:value="updateNum"
title="更新次数"
></el-statistic>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<el-statistic
:value="problemNum"
title="解决问题数量"
></el-statistic>
</el-card>
</el-col>
</el-row>
<el-row :gutter="10" style="margin-top: 30px;height: 100%">
<el-col :span="1.5">
<el-button
type="primary"
plain
:icon="IconEnum.ADD"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
</el-row>
<el-card style="margin-top: 30px;height: 100%">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="版本号">
<el-input v-model="queryParams.versionNo"
placeholder="请输入版本号"
clearable
@keyup.enter.native="searchQuery"></el-input>
</el-form-item>
<el-form-item label="发布时间">
<el-date-picker
v-model="queryParams.updateDateList"
style="width: 240px"
format="yyyy-MM-dd"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="IconEnum.SEARCH" size="mini" @click="searchQuery">搜索</el-button>
<el-button :icon="IconEnum.RESET" size="mini" @click="resetQuery('queryForm')">重置</el-button>
</el-form-item>
</el-form>
<div style="overflow:auto">
<el-timeline>
<el-timeline-item v-for="(item, index) in dataSource" :key="index" :timestamp="item.updateDate" placement="top">
<div :class="isExpanded[index]?'extendCard':'hiddenCard'">
<div>
<el-button plain :icon="IconEnum.EDIT" type="warning" size="mini"
@click="handleEdit(item.id)">编辑</el-button>
<el-button type="danger" plain :icon="IconEnum.DELETE" size="mini" @click="handleDelete(item)"
>删除</el-button>
<el-button style="float: right" size="mini" v-if="!isExpanded[index]" @click="expandCard(index)">展开</el-button>
<el-button style="float: right" size="mini" v-else @click="collapseCard(index)">收起</el-button>
</div>
<span style="display: inline;font-weight: bold;font-size: large;line-height: 40px;">版本号:{{item.versionNo}}</span>
<div v-for="content in item.sysVersionItemDtoList">
<span style="font-weight:bold">【{{content.modelName}}】</span>
<!-- <div v-for="problem in content.problemList">-->
<div v-html="content.problem.replace(/\n/g, '<br>')"></div>
<!-- </div>-->
</div>
</div>
</el-timeline-item>
</el-timeline>
<el-pagination
:current-page.sync="queryParams.page"
:page-sizes="[5, 10, 20, 30]"
:page-size.sync="queryParams.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</div>
</el-card>
<!-- 组件区域 -->
<verson-modal ref="versionModal" @ok="handleOk"></verson-modal>
</div>
</template>
<script>
/**一些引入*/
export default {
name: "index",
components: {VersonModal},
data(){
return{
updateNum:0,
problemNum:0,
url: {
list: '/system/version/list',
delete: '/system/version/batch/',
},
queryParams:{
page: 1,
pageSize: 10,
versionNo:undefined,
updateDateList:undefined,
},
dataSource:[],
isExpanded:[],
}
},
created() {
this.loadData(1)//加载第一页
},
methods:{
loadData(arg) {
//加载数据逻辑,记得写分页
},
searchQuery() {
this.isExpanded=[]
this.loadData(1);
// 点击查询清空列表选中行
this.ids = []
},
handleSizeChange(pageSize) {
this.isExpanded=[]
this.queryParams.pageSize = pageSize
// 请求列表
this.loadData(1)
},
handleCurrentChange(page) {
this.isExpanded=[]
this.queryParams.page = page
// 请求列表
this.loadData()
},
resetQuery(queryForm) {
this.resetForm(queryForm)
this.queryParams = {}
this.loadData(1);
},
expandCard(index) {
this.isExpanded[index] = true;
this.$forceUpdate()
},
collapseCard(index) {
this.isExpanded[index] = false;
this.$forceUpdate()
},
handleAdd(){
this.$refs.versionModal.handleAdd()
},
handleEdit(id){
this.$refs.versionModal.handleEdit(id)
},
handleOk(){
this.loadData(1)
}
}
}
</script>
<style scoped>
.demo-infinite-container {
overflow: auto;
padding: 8px 24px;
height: 100vh;
}
.hiddenCard{
height:200px;
overflow: hidden;
border:1px solid rgba(196, 196, 196, 0.5);
border-radius: 5px;
padding: 30px 20px;
}
.extendCard{
height: 100%;
border:1px solid rgba(196, 196, 196, 0.5);
border-radius: 5px;
padding: 30px 20px;
}
.hiddenCard::before {
content: "";
position: absolute;
right:auto;
bottom: 1px;
left: 10px;
width: 90%;
height: 40px;
background: linear-gradient(rgba(255,255,255,0),rgba(255,255,255,1));
pointer-events:none;
}
</style>
新增窗口:
<template>
<div class="app-container">
<el-dialog :title="title" :visible.sync="open" v-if="open" width="75%" :before-close="cancel" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="版本号" prop="versionNo">
<el-input v-model="form.versionNo" placeholder="请输入版本号" style="width: 80%" :disabled="readOnly"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发布日期" prop="updateDate">
<el-date-picker
v-model="form.updateDate"
value-format="yyyy-MM-dd"
type="date"
placeholder="选择日期">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="submitLoading" @click="submitForm" :disabled="readOnly">保存</el-button>
<el-button @click="cancel">关闭</el-button>
</div>
<el-alert
title="更新内容请按1./2./3.标明序号,使用回车符间隔每条内容"
type="warning"
:closable="false">
</el-alert>
<EditTable :config="config" ref="modelForm" />
</el-dialog>
</div>
</template>
<script>
import EditTable from "@/components/EditTable";
import {addVersionApi, editVersionApi, getVersionApi} from "@/api/system/system/version";
export default {
name: "versonModal",
components: {
EditTable,
},
data(){
return {
title: "",//标题
open: false,//是否弹出
readOnly: false,//表单可编辑
submitLoading: false,
form:{
versionNo:undefined,
updateDate:undefined,
},
modelList:[],
config: {
columns :[
{ prop: 'id', label: 'id', attr: {width: '180'}, hidden:true},
{ prop: 'modelName', label: '模块名',edit: true,rules: [{required: true, message: '请输入'}]},
{ prop: 'problem', label: '更新内容', edit: true,type:'textArea', rules: [{required: true, message: '请输入'}]},
{ prop: 'opt', label: '操作', edit: true, align: 'center' }
],
isAdd: !this.readOnly,
isDetail:this.readOnly,
isRowLimit: false,
isSubmit:false,
data:this.modelList
},
rules: {//表单校验
versionNo: [
{required: true, message: "请输入版本号", trigger: "change"}
],
updateDate: [
{required: true, message: "请选择发布日期", trigger: "change"}
],
}
}
},
methods:{
/** 新增操作 */
handleAdd() {
this.open = true;
this.readOnly = false
this.title = "新增";
},
/** 编辑操作 */
handleEdit(id) {
if(id) {
this.readOnly = false
getVersionApi(id).then(res => {
this.form = res.data
this.modelList = res.data.sysVersionItemDtoList
this.open = true;
this.title = "编辑";
this.$nextTick(() => {
this.$refs.modelForm.setData(this.modelList)
})
})
}
},
reset(){
this.form={}
this.$refs.modelForm.reset()
},
setData(data){
this.$refs.form.setData(data)
},
cancel(){
this.reset()
this.readOnly=true
this.open=false
},
getValue(){
let list = this.$refs.modelForm.getTableData()
this.form.sysVersionItemDtoList = list
},
submitForm(){
//子表表单验证
let contentTableCheck = this.$refs.modelForm.validateTable()
// 触发表单验证
let formCheck = this.$refs.form.validate()
Promise.all([formCheck,contentTableCheck]).then((valid)=>{
if(valid){
if(!this.$refs.modelForm.getTableData().length>0){
this.$message.error("请填写模块和问题")
// return
}
this.getValue()
if(this.form.id){
editVersionApi(this.form).then(res=>{
this.$modal.msgSuccess("修改成功");
this.cancel()
this.$emit('ok')
})
}else{
addVersionApi(this.form).then(res=>{
this.$modal.msgSuccess("新增成功");
this.cancel()
this.$emit('ok')
})
}
}
})
},
}
}
</script>
<style scoped>
</style>
可编辑表格:
<template>
<el-form :model="form" ref="form" size="small" :rules="rules">
<el-form-item v-if="isAdd">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="add" plain>新增</el-button>
</el-form-item>
<el-table :data="form.list" border>
<el-table-column type="index" label="#" align="center"></el-table-column>
<el-table-column v-if="!x.hidden" v-for="x in columns" :key="x.prop" :label="x.label" :prop="x.prop" v-bind="x.attr" :align="x.align" >
<template slot-scope="{row, $index}">
<t-text v-if="(!x.edit) && (cellIndex === undefined)" :row="{x, row}" />
<template v-else>
<t-text v-if="isDetail" :row="{x, row}" />
<template v-else>
<t-text v-if="cellIndex !== $index && isRowLimit" :row="{x, row}" />
<template v-else>
<t-input v-if="x.prop !== 'opt'" v-model="row[`${x.prop}`]" v-bind="componentAttrs(x, row, $index)" class="width100" />
<template v-else>
<!-- <el-link type="primary" :underline="false" @click="save(row, $index)">保存</el-link>-->
<el-link type="primary" :underline="false" @click="del($index)">删除</el-link>
<el-link type="primary" :underline="false" @click="resetField($index)">重置</el-link>
</template>
</template>
</template>
</template>
</template>
</el-table-column>
</el-table>
<el-form-item >
<template v-if="isSubmit">
<el-button type="primary" @click="submit">提交</el-button>
<el-button @click="reset">重置</el-button>
</template>
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
config: Object
},
components: {
TInput: {
functional: true,
props: ['prop', 'rules', 'type', 'options', 'row', 'cb'],
render: (h, {props: { prop, rules, type = 'default', options = [], row, cb = () => {} }, data, listeners: {input = () => {}}}) => {
const children = {
checkbox: h => h('el-checkbox-group', {props: {...data.attrs}, on: {input(v) {input(v)}}}, options.map(o => h('el-checkbox', {props: {...o, label: o.value, key: o.value}}, [o.label]))),
select: h => h('el-select', {class: 'width100', props: {...data.attrs}, on: {change(v){input(v)}}}, options.map(o => h('el-option', {props: {...o, key: o.value}}))),
date: h => h('el-date-picker', {props: {type: 'date', valueFormat: 'yyyy-MM-dd'}, ...data}),
switch: h => h('el-switch', {props: {activeColor: '#13ce66'}, ...data}),
mixInput: h => h('el-input', data, [h('el-button', {slot: 'append', props: {icon: 'el-icon-search'}, on: {click(){cb(row)}}})]),
opt: () => '-',
textArea:h=>h('el-input',{props: {type: 'textarea'},...data}),
default: h => h('el-input', data),
}
return h('el-form-item', {props: {prop, rules}}, [children[type](h)])
}
},
TText: {
functional: true,
props: ['row'],
render: (h, {props: { row: { x, row } }}) => {
if(!row[`${x.prop}`]) return h('span', '-')
else if(x.format && typeof x.format == 'function') return h('span', x.format(row))
else if(x.type === 'select') return h('dict-tag', {props: {options: x.options, value: row[`${x.prop}`]}})
else return h('span', row[`${x.prop}`])
}
},
},
data(){
const { columns = [], isAdd = false, isDetail = false, isSubmit = false,data = [],isRowLimit = false } = this.config || {}
//[columns.reduce((r, c) => ({...r, [c.prop]: c.type == 'checkbox' ? [] : (c.type == 'switch' ? false : '')}), {isAdd: true})]
return {
cellIndex: undefined,
form: {
list: data
},
columns,
rules: columns.reduce((r, c) => ({...r, [c.prop]: c.rules ? c.rules : { required: c.required == false ? false : true, message: c.label + '必填'}}), {}),
isAdd,
isDetail,
isRowLimit,
isSubmit,
}
},
methods: {
componentAttrs(item, row, idx){
const {type, label} = item, attrs = Object.fromEntries(Object.entries(item).filter(n => !/^(prop|edit|label|attr|format)/.test(n[0]))),
placeholder = (/^(select|el-date-picker)/.test(type) ? '请选择' : '请输入') + label
Object.assign(attrs, {prop: `list.${idx}.${item.prop}`, rules: this.rules[item.prop]})
return {...attrs, row, placeholder}
},
add(){
const { columns = [] } = this.config || {}, obj = columns.reduce((r, c) => ({...r, [c.prop]: c.type == 'checkbox' ? [] : (c.type == 'switch' ? false : '')}), {isAdd: true})
if (this.isRowLimit){
this.cellIndex = this.form.list.length
}
this.form.list.push(obj)
},
save(row, idx){
let ret = Object.keys(row).map(r => `list.${idx}.${r}`).filter(r => !/isAdd|opt/g.test(r)), { $refs: { form } } = this, num = 0
form.validateField(ret, valid => {
if(valid) {
num++
}
})
if(num == 0) this.$emit('submit', Object.fromEntries(Object.entries(row).filter(n => !/^(isAdd|opt)/.test(n[0]))))
},
del(idx){
this.form.list.splice(idx, 1)
this.$refs.form.fields.forEach(n => {
if(n.prop.split(".")[1] == idx){
n.clearValidate();
}
})
},
submit(){
this.$refs.form.validate(valid => {
if(valid){
this.$emit('submit', this.form.list.map(m => Object.fromEntries(Object.entries(m).filter(n => !/^(isAdd|opt)/.test(n[0])))))
}
})
},
resetField(idx){
this.$refs.form.fields.forEach(n => {
if(n.prop.split(".")[1] == idx){
n.resetField();
}
})
},
reset(){
this.$refs.form.resetFields();
},
// 回显数据
setData(form){
form = (form && form.length > 0) ? form.map(n => this.columns.reduce((r, c) => ({...r, [c.prop]: n[c.prop] == false ? n[c.prop] : (n[c.prop] || (c.type == 'checkbox' ? [] : ''))}), {}))
//: [this.columns.reduce((r, c) => ({...r, [c.prop]: c.type == 'checkbox' ? [] : (c.type == 'switch' ? false : '')}), {isAdd: true})]
:[]
Object.assign(this.form, {list: form})
setTimeout(()=> {this.$refs.form.clearValidate()})
},
setConfig(config){
this.config = config
},
// 校验table,失败返回errMap,成功返回null
validateTable() {
return this.$refs.form.validate()
},
/** 获取表格数据 */
getTableData() {
return this.form.list
},
}
}
</script>
<style scoped>
.width100{width: 100%;}
.el-link {margin-right: 10px;}
.el-form-item {
margin-bottom: 30px;
}
</style>