mt-yd-ui/src/components/base-dialog/addOrUpdate/index.vue

549 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog class="super-flexible-dialog" :title="isDetail ? title.detail : !dataForm.id ? title.add : title.edit" :visible.sync="visible" @close="handleClose">
<div style="max-height: 60vh; overflow-y: scroll; overflow-x: hidden;">
<el-form ref="dataForm" :model="dataForm" :rules="dataFormRules">
<!-- 如果需要更精细一点的布局可以根据配置项实现地再复杂一点但此处暂时全部采用一行两列布局 -->
<el-row v-for="n in rows" :key="n" :gutter="20">
<el-col v-for="c in COLUMN_PER_ROW" :key="`${n}+'col'+${c}`" :span="getSpan(n, c)">
<!-- <el-col v-for="c in COLUMN_PER_ROW" :key="`${n}+'col'+${c}`" :span="24 / COLUMN_PER_ROW"> -->
<!-- :class="{ 'hidden-input': configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].hidden }" -->
<el-form-item
v-if="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]"
:prop="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name"
:key="`${n}-col-${c}-item`"
:label="getLabel(n, c)"
>
<!-- 暂时先不实现部分输入方式 -->
<el-input
v-if="getType(n, c) === 'input'"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
clearable
/>
<el-radio v-if="getType(n, c) === 'radio'" v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"></el-radio>
<el-checkbox v-if="getType(n, c) === 'check'" v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"></el-checkbox>
<el-select
v-if="getType(n, c) === 'select'"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
clearable
@change="emitSelectChange(configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name, $event)"
>
<el-option v-for="opt in configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].options" :key="opt.label" :label="opt.label" :value="opt.value" />
</el-select>
<el-switch v-if="getType(n, c) === 'switch'" v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"></el-switch>
<el-cascader
v-if="getType(n, c) === 'cascader'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:options="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].options"
:props="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].props"
></el-cascader>
<el-time-select v-if="getType(n, c) === 'time'" v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"></el-time-select>
<el-date-picker
v-if="getType(n, c) === 'date'"
v-bind="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].props"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<!-- extra components , like Markdown or RichEdit -->
<template v-if="configs.extraComponents && configs.extraComponents.length > 0">
<el-form-item v-for="(ec, index) in configs.extraComponents" :key="ec.name + index" :label="ec.label" class="extra-components">
<component style="margin-top: 40px;" v-if="ec.hasModel" :is="ec.component" v-bind="ec.props" v-model="dataForm[ec.name]" @ready="handleEditorReady" />
<!-- <component v-if="ec.hasModel" :is="ec.component" v-bind="ec.props" v-model="dataForm[ec.name]" /> -->
<component
v-else
:is="ec.component"
v-bind="ec.props"
@uploader-update-filelist="handleUploadListUpdate($event, ec.props.extraParams.typeCode)"
:uploader-inject-file-list="/*用于设备分流的*/ fileList[ec.props.extraParams.typeCode]"
/>
</el-form-item>
</template>
</el-form>
<template v-if="dataForm.id && configs.subtable">
<attr-form :pId="dataForm.id" v-bind="configs.subtable" />
</template>
</div>
<span slot="footer" class="dialog-footer">
<template v-for="(operate, index) in configs.operations">
<!-- {{ operate.name | btnNameFilter }} -->
<el-button
v-if="
operate.showAlways ||
(((dataForm.id && operate.showOnEdit) || (!dataForm.id && !operate.showOnEdit)) && (operate.permission ? $hasPermission(operate.permission) : true))
"
:key="`operate-${index}`"
:type="btnType[operate.name]"
@click="handleClick(operate)"
>{{ btnName[operate.name] }}</el-button
>
</template>
</span>
</el-dialog>
</template>
<script>
import AttrForm from '../AttrForm'
import { pick } from 'lodash/object'
// 标题 for i18n
const title = {
detail: '详情',
add: '新增',
edit: '编辑'
}
// 或者也可以改造成自定义颜色:
const btnType = {
save: 'success',
update: 'primary',
reset: 'text'
// cancel: 'text'
// add more...
}
const btnName = {
// for i18n
save: '保存',
update: '更新',
reset: '重置',
cancel: '取消'
// add more...
}
// 每行的列数
const COLUMN_PER_ROW = 2
export default {
name: 'AddOrUpdateDialog',
components: { AttrForm },
props: {
configs: {
/**
* TODO: 定义及使用方式应改用README.md文件记录
* type: 'dialog' | 'drawer' | 'page'
* fields: Array<string|object>
* - fields.object: { name, type: 'number'|'textarea'|'select'|'date'|.., required: boolean, validator: boolean(是否需要验证), [options]: any[], api: string(自动获取数据的接口一般为getcode接口)}
* operations: Array[object], 操作名和对应的接口地址还有permission(如sys:dict:update)
*/
type: Object,
default: () => ({}) // 此处省去类型检查,使用者自行注意就好
}
},
filters: {
nameFilter: function(name) {
if (!name) return null
// for i18n
const defaultNames = {
name: '名称',
code: '编码',
remark: '备注',
description: '描述',
specifications: '规格'
// add more...
}
return defaultNames[name]
}
},
// provide() {
// return {
// _df: this.dataForm
// }
// },
data() {
return {
COLUMN_PER_ROW,
title,
/** 按钮相关属性 */
btnName,
btnType,
defaultNames: {
name: '名称',
code: '编码',
remark: '备注',
description: '描述',
specifications: '规格'
// add more...
},
defaultPlaceholders: {}, // 自动根据 defaultNames 计算得来
/** 表单相关属性 */
visible: false,
isEdit: false,
isDetail: false,
dataForm: {},
dataFormRules: {},
tempForm: [], // 临时保存自动生成的code或其他数据
shouldWait: null,
fileForm: {}, // 文件上传分流用、合并用的表单,根据 typeCode 进行分流,在请求时合并
fileList: {} // 文件加载时分流,依据 typeCode
}
},
computed: {
rows() {
// 本组件只实现了'一行两列'的表单布局
return Math.ceil(this.configs.fields.length / COLUMN_PER_ROW)
}
},
mounted() {
/** 计算 defaultPlaceholders */
const prefix = '请输入'
Object.entries(this.defaultNames).map(([key, value]) => {
this.defaultPlaceholders[key] = prefix + value
})
/** 转换 configs.fields 的结构,把纯字符串转为对象 */
this.$nextTick(() => {
this.configs.fields = this.configs.fields.map(item => {
if (typeof item === 'string') {
return { name: item }
}
return item
})
/** 动态设置dataForm字段 */
this.configs.fields.forEach(item => {
this.$set(this.dataForm, [item.name], '')
/** select 的默认值设置 */
if (item.type === 'select') {
const opts = item.options || []
const dft = opts.find(item => item.default || false)
if (dft) {
this.$set(this.dataForm, [item.name], dft.value)
}
}
if (item.api) {
/** 自动请求并填充 */
// or this.shouldWaitPool = []
this.shouldWait = this.$http({
url: this.$http.adornUrl(item.api),
method: 'POST' // 也可以改成动态决定
}).then(({ data: res }) => {
if (res && res.code === 0) {
// this.dataForm[item.name] = res.data // <=== 此处需要对接口
this.tempForm.push({ name: item.name, data: res.data })
}
})
} // end if (item.api)
if (item.required) {
const requiredRule = {
required: true,
message: '请输入必填项',
trigger: 'change'
}
/** 检查是否已经存在该字段的规则 */
const exists = this.dataFormRules[item.name] || null
/** 设置验证规则 */
if (exists) {
const unset = true
for (const rule of exists) {
if (rule.required) unset = false
}
if (unset) {
exists.push(requiredRule)
}
} else {
/** 不存在已有规则 */
this.$set(this.dataFormRules, [item.name], [requiredRule])
}
} // end if (item.required)
if (item.rules) {
const exists = this.dataFormRules[item.name] || null
if (exists) {
// 浅拷贝过去
exists.push(...item.rules)
} else {
this.$set(this.dataFormRules, [item.name], [...item.rules])
}
} // end if (item.rules)
})
/** 计算默认值 */
function calDefault(type) {
switch (type) {
case 'array':
return []
// more case...
default:
return ''
}
}
/** 检查是否需要额外的组件 */
this.configs.extraComponents &&
this.configs.extraComponents.forEach(item => {
if (Object.hasOwn(this.dataForm, [item.name])) {
console.log('有了!')
return
} else {
console.log('新建!')
this.$set(this.dataForm, [item.name], calDefault(item.fieldType))
}
console.log('component: ', item.component)
})
/** 单独设置 id */
this.$set(this.dataForm, 'id', null)
console.log('mounted: this.dataForm', JSON.stringify(this.dataForm))
})
},
methods: {
getSpan(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]
return opt && opt.span ? opt.span : 24 / COLUMN_PER_ROW
},
getLabel(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]
if (opt) {
// if opt is valid
return opt.label ? opt.label : this.defaultNames[opt.name]
}
},
getPlaceholder(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]
if (opt) {
// if opt is valid
return opt.placeholder
? opt.placeholder
: this.defaultPlaceholders[opt.name]
? this.defaultPlaceholders[opt.name]
: opt.label
? (opt.type === 'select' ? '请选择' : '请输入') + opt.label
: null
// : opt.type === 'select'
// ? '请选择'
// : '请输入'
}
},
getType(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]
if (opt) {
if (!opt.type || ['input', 'number' /** add more.. */].includes(opt.type)) {
return 'input'
} else if (['select' /** add more.. */].includes(opt.type)) {
return 'select'
} else if (['cascader'].includes(opt.type)) {
return 'cascader'
} else if (['date'].includes(opt.type)) {
return 'date'
}
// add more...
} else {
return 'input'
}
},
init(id) {
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
this.dataForm.id = id || null
if (this.dataForm.id) {
this.$http({
url: this.$http.adornUrl(`${this.configs.infoUrl}/${this.dataForm.id}`),
method: 'get'
}).then(({ data: res }) => {
if (res && res.code === 0) {
const dataFormKeys = Object.keys(this.dataForm)
console.log('data form keys: ', dataFormKeys, pick(res.data, dataFormKeys))
this.dataForm = pick(res.data, dataFormKeys)
// LABEL: FILE_RELATED
/** 对文件下载进行分流 */
this.fileList = {}
if (this.dataForm.files) {
console.log('files: ', this.dataForm.files)
this.dataForm.files.forEach(file => {
// const fileName = file.fileUrl.split('/').pop()
/** [1] 处理 fileList */
if (Object.hasOwn(this.fileList, file.typeCode)) {
/** 已存在 */
// this.fileList[file.typeCode].push({ id: file.id, name: fileName, typeCode: file.typeCode })
this.fileList[file.typeCode].push(file)
} else {
// this.fileList[file.typeCode] = [{ id: file.id, name: fileName, typeCode: file.typeCode }]
this.fileList[file.typeCode] = [file]
}
/** [2] 处理 fileForm */
if (Object.hasOwn(this.fileForm, file.typeCode)) {
this.fileForm[file.typeCode].push(file.id)
} else {
this.fileForm[file.typeCode] = [file.id]
}
})
console.log('after分流', this.fileList)
}
}
})
} else {
/** 如果不是编辑,就填充自动生成的数据 */
if (this.shouldWait)
this.shouldWait.then(() => {
if (this.tempForm.length) {
console.log('create new, tempform', JSON.stringify(this.tempForm.length))
this.tempForm.forEach(item => {
console.log('item data', item.data)
this.dataForm[item.name] = item.data
})
console.log('create new, dataform', JSON.stringify(this.dataForm))
}
})
}
})
},
emitSelectChange(name, id) {
this.$emit('select-change', { name, id })
},
handleEditorReady(val) {
console.log('editor rready..', val)
},
handleClick(btn) {
/** 提取url */
const urls = {}
this.configs.operations.map(item => {
urls[item.name] = {}
urls[item.name].url = item.url
urls[item.name].extraFields = item.extraFields || {}
})
/** 操作 */
switch (btn.name) {
case 'save':
case 'update':
/** 需要验证表单的操作 */
this.$refs['dataForm'].validate(valid => {
if (valid) {
/** 对于文件上传的单独处理(合并处理) */
if (Object.keys(this.fileForm).length) {
console.log('fileform 有值')
// LABEL: FILE_RELATED
let fileIds = []
for (const [key, item] of Object.entries(this.fileForm)) {
if (Array.isArray(item)) {
fileIds = fileIds.concat(item)
} else {
console.error('handleClick(): 上传文件数组类型不正确')
}
}
this.$set(this.dataForm, 'fileIds', fileIds)
}
console.log('before send: ', this.dataForm)
this.$http({
url: this.$http.adornUrl(urls[btn.name].url),
method: btn.name === 'save' ? 'POST' : 'PUT',
data: { ...this.dataForm, ...urls[btn.name].extraFields }
})
.then(({ data: res }) => {
if (res && res.code === 0) {
this.$message({
message: btn.name === 'save' ? '添加成功!' : '更新成功!',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('refreshDataList')
this.visible = false
}
})
} else {
this.$message.error(res.msg)
}
})
.catch(err => {
this.$message({
message: err,
type: 'error',
duration: 2000
})
})
}
})
return
case 'reset':
for (const key of Object.keys(this.dataForm)) {
if (typeof this.dataForm[key] === 'string') {
this.dataForm[key] = ''
} else if (this.dataForm[key] instanceof Array) {
this.dataForm[key].splice(0)
} else {
this.dataForm[key] = null
}
}
break
case 'cancel':
this.visible = false
// add more..
}
},
// LABEL: FILE_RELATED
handleUploadListUpdate(filelist, typeCode = 'DefaultTypeCode') {
console.log('before handleUploadListUpdate(): ', JSON.parse(JSON.stringify(this.fileForm)))
// 设备类型 typeCode: EquipmentTypeFile
// 设备信息 typeCode: EquipmentInfoFile | EquipmentInfoImage
// 原先写法:直接更新 dataForm 对象,不适用于有多个上传组件的需求
// this.$set(
// this.dataForm,
// 'fileIds',
// filelist.map(item => item.id)
// )
// console.log('handleUploadListUpdate(): ', this.dataForm)
// 现更改为分流写法
this.$set(
this.fileForm,
typeCode,
filelist.map(item => item.id)
)
console.log('after handleUploadListUpdate(): ', this.fileForm)
},
handleClose() {
this.$emit('destory-dialog')
this.visible = false
}
}
}
</script>
<style scoped>
.super-flexible-dialog >>> .el-select,
.super-flexible-dialog >>> .el-cascader {
width: 100%;
}
.super-flexible-dialog >>> ::-webkit-scrollbar {
width: 4px;
border-radius: 4px;
background: #fff;
}
.super-flexible-dialog >>> ::-webkit-scrollbar-thumb {
width: 4px;
border-radius: 4px;
background: #ccc;
}
.super-flexible-dialog >>> .hidden-input {
display: none;
}
</style>