pms-aomei/src/components/DialogJustForm.vue
2023-07-05 11:18:08 +08:00

771 lines
26 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="dialog-just-form"
:visible="dialogVisible"
@close="handleClose"
:destroy-on-close="false"
:close-on-click-modal="configs.clickModalToClose ?? false"
:width="configs.dialogWidth ?? '50%'">
<!-- title -->
<div slot="title" class="dialog-title">
<h1 class="">
{{ detailMode ? "查看详情" : dataForm.id ? "编辑" : "新增" }}
</h1>
</div>
<!-- form -->
<el-form ref="dataForm" :model="dataForm" v-loading="loadingStatus">
<el-row v-for="(row, rowIndex) in configs.form.rows" :key="'row_' + rowIndex" :gutter="20">
<el-col v-for="(col, colIndex) in row" :key="colIndex" :span="24 / row.length">
<!-- 通过多个 col === null 可以控制更灵活的 span 大小 -->
<el-form-item v-if="col !== null" :label="col.label" :prop="col.prop" :rules="col.rules || null">
<el-input
v-if="col.input"
v-model="dataForm[col.prop]"
clearable
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams" />
<el-input
v-if="col.forceDisabled && col.eraseOnSubmit"
v-model="shadowDataForm[col.prop]"
disabled
v-bind="col.elparams" />
<el-input
v-if="col.forceDisabled && !col.eraseOnSubmit"
v-model="dataForm[col.prop]"
disabled
v-bind="col.elparams" />
<el-button
type=""
plain
v-if="col.button"
v-bind="col.elparams"
style="width: 100%"
@click="handleButtonClick(col)">
{{ col.label }}
</el-button>
<el-cascader
v-if="col.cascader"
v-model="dataForm[col.prop]"
:options="col.options"
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams"></el-cascader>
<el-select
v-if="col.forceDisabledSelect"
disabled
v-model="dataForm[col.prop]"
clearable
v-bind="col.elparams">
<el-option
v-for="(opt, optIdx) in col.options"
:key="'option_' + optIdx"
:label="opt.label"
:value="opt.value" />
</el-select>
<el-select
v-if="col.select"
v-model="dataForm[col.prop]"
clearable
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams"
@change="handleSelectChange(col, $event)">
<el-option
v-for="(opt, optIdx) in col.options"
:key="'option_' + optIdx"
:label="opt.label"
:value="opt.value">
<span>{{ opt.label }}</span>
<span
v-if="col.customLabel"
style="display: inline-clock; margin-left: 12px; font-size: 0.9em;">
{{ opt[col.customLabel] || "-" }}
</span>
</el-option>
</el-select>
<el-switch
v-if="col.switch"
v-model="dataForm[col.prop]"
:active-value="col.activeValue ?? 1"
:inactive-value="col.activeValue ?? 0"
@change="handleSwitchChange"
:disabled="detailMode" />
<el-input
v-if="col.textarea"
type="textarea"
v-model="dataForm[col.prop]"
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams" />
<el-date-picker
v-if="col.datetime"
v-model="dataForm[col.prop]"
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams" />
<uploadBtn
v-if="col.upload"
:key="'upload_' + rowIndex + colIndex"
:action="col.actionUrl"
:file-list="dataForm['files']"
:disabled="IsNotAvaliable(col)"
v-bind="col.elparams"
@update-file-list="handleFilelistUpdate" />
<quillEditor
v-if="col.richInput"
ref="quill-editor"
v-model="dataForm[col.prop]"
:options="col.quillConfig ?? defaultQuillConfig"
style="margin-top: 42px"
:disabled="IsNotAvaliable(col)" />
<div class="" v-if="col.component" style="margin: 42px 0 0">
<!-- 下面这个 component 几乎是为 富文本 quill 定制的了... TODO后续可能会根据业务需求创建新的版本 -->
<component
:is="col.component"
:key="'component_' + col.prop"
@update:modelValue="handleComponentModelUpdate(col.prop, $event)"
:modelValue="dataForm[col.prop]"
:mode="detailMode ? 'detail' : dataForm.id ? 'edit' : 'create'" />
</div>
<!-- add more... -->
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- footer -->
<div slot="footer">
<template v-for="(operate, index) in configs.form.operations">
<el-button
v-if="showButton(operate)"
:key="'operation_' + index"
:type="operate.type"
@click="handleBtnClick(operate)">
{{ operate.label }}
</el-button>
</template>
<el-button @click="handleBtnClick({ name: 'cancel' })">取消</el-button>
</div>
</el-dialog>
</template>
<script>
import { pick as __pick } from "@/utils/filters";
import Cookies from "js-cookie";
import uploadBtn from "@/components/uploadBtn";
import moment from "moment";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { quillEditor } from "vue-quill-editor";
export default {
name: "DialogJustForm",
components: { uploadBtn, quillEditor },
props: {
configs: {
type: Object,
default: () => ({
clickModalToClose: false,
forms: null,
}),
},
dialogVisible: {
type: Boolean,
default: false,
},
// extraParams: {
// type: Object,
// default: () => ({})
// }
},
inject: ["urls"],
data() {
const dataForm = {};
const shadowDataForm = {};
const savedDatalist = {};
const cachedList = {};
const watchList = [];
this.configs.form.rows.forEach((row) => {
row.forEach((col) => {
if (col == null) return
if (!col.eraseOnSubmit && col.prop) dataForm[col.prop] = col.default ?? null;
else shadowDataForm[col.prop] = col.default ?? null;
if ("autoUpdateProp" in col) {
savedDatalist[col.prop] = [];
}
});
});
if (this.configs.extraFields)
this.configs.extraFields.forEach((cnf) => {
dataForm[cnf.prop] = cnf.default ?? null;
});
return {
loadingStatus: false,
dataForm,
shadowDataForm,
savedDatalist,
cachedList,
watchList,
detailMode: false,
editMode: false,
baseDialogConfig: null,
defaultQuillConfig: {
modules: {
toolbar: [
[{ font: [] }],
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
["bold", "italic", "underline", "strike"], // toggled buttons
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
["blockquote", "code-block"],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: "ordered" }, { list: "bullet" }],
// [{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent
// [{ 'direction': 'rtl' }], // text direction
// [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
// [{ 'align': [] }],
// ['clean'] // remove formatting button
],
},
theme: "snow",
readOnly: false,
placeholder: "在这里输入描述信息...",
scrollingContainer: null,
},
};
},
mounted() {
/** 临时保存所有发出的请求 */
const promiseHistory = {};
const getData = (col, param) => {
console.log("getData: ", col.prop, "/", param ?? "no param!");
// 获取数据 - 不需要等待前置条件时
promiseHistory[col.prop] = col.fetchData(param).then(({ data: res }) => {
if (res.code === 0) {
if ("list" in res.data) {
console.log(
"SdASD ",
res.data.list.map((i) => ({
label: col.optionLabel ? i[col.optionLabel] : i.name,
value: col.optionValue ? i[col.optionValue] : i.id,
[col.customLabel]: i[col.customLabel],
}))
);
// 填充 options
this.$set(
col,
"options",
col.customLabel
? res.data.list.map((i) => ({
label: col.optionLabel ? i[col.optionLabel] : i.name,
value: col.optionValue ? i[col.optionValue] : i.id,
[col.customLabel]: i[col.customLabel],
}))
: res.data.list.map((i) => ({
label: col.optionLabel ? i[col.optionLabel] : i.name,
value: col.optionValue ? i[col.optionValue] : i.id,
}))
);
// 是否需要缓存数据列表
if ("injectTo" in col || col.cached) {
this.cachedList[col.prop] = res.data.list;
}
// 设置监听
if ("injectTo" in col) {
console.log("set watcher: ", col.prop);
const valueProp = "optionValue" in col ? col.optionValue : "id";
const unwatch = this.$watch(
() => this.dataForm[col.prop],
(val) => {
console.log("do watcher: ", col.prop);
if (col.disableWatcherOnEdit && this.editMode) return;
if (!val) {
col.injectTo.map((item) => {
this.$set(this.dataForm, item[0], null);
this.$forceUpdate();
});
return;
}
const chosenObject = this.cachedList[col.prop].find((i) => i[valueProp] === val);
if (chosenObject) {
col.injectTo.map((item) => {
this.$set(this.dataForm, item[0], chosenObject[item[1]]);
this.$forceUpdate();
});
} else {
console.log('[x] if ("injectTo" in col) {');
}
},
{
immediate: false,
}
);
this.watchList.push(unwatch);
}
if ("autoUpdateProp" in col) {
this.$set(this.savedDatalist, col.prop, res.data.list);
}
// end branch
} else if (Array.isArray(res.data)) {
// 填充 options
this.$set(
col,
"options",
col.customLabel
? res.data.map((i) => ({
label: col.optionLabel ? i[col.optionLabel] : i.name,
value: col.optionValue ? i[col.optionValue] : i.id,
[col.customLabel]: i[col.customLabel],
}))
: res.data.map((i) => ({
label: col.optionLabel ? i[col.optionLabel] : i.name,
value: col.optionValue ? i[col.optionValue] : i.id,
}))
);
// 是否需要缓存数据列表
if ("injectTo" in col || col.cached) {
this.cachedList[col.prop] = res.data;
}
// 设置监听
if ("injectTo" in col && !col.watcher) {
console.log("set watcher: ", col.prop);
const valueProp = "optionValue" in col ? col.optionValue : "id";
const unwatch = this.$watch(
() => this.dataForm[col.prop],
(val) => {
if (col.disableWatcherOnEdit && this.editMode) return;
console.log("do watcher: ", col.prop);
if (!val) {
col.injectTo.map((item) => {
this.$set(this.dataForm, item[0], null);
this.$forceUpdate();
});
return;
}
const chosenObject = this.cachedList[col.prop].find((i) => i[valueProp] === val);
if (chosenObject) {
col.injectTo.map((item) => {
this.$set(this.dataForm, item[0], chosenObject[item[1]]);
this.$forceUpdate();
});
} else {
console.log('[x] if ("injectTo" in col) {');
}
},
{
immediate: false,
}
);
this.$set(col, "watcher", unwatch);
this.watchList.push(unwatch);
}
if ("autoUpdateProp" in col) {
this.$set(this.savedDatalist, col.prop, res.data);
}
}
// end branch
} else {
col.options.splice(0);
}
});
console.log("after getData: ", promiseHistory);
};
/** 处理函数 */
const handleFn = (prevProp, anchorField, anchorValue, targetField, col) => {
console.log("[handleFn]: ", prevProp, anchorField, anchorValue, targetField);
/** 此时 cachedList 已经确保可用了 */
const target = this.cachedList[prevProp].find((i) => i[anchorField] === anchorValue);
const param = target ? target[targetField] : "";
console.log("((( chosenObject )))", target);
getData(col, param);
};
this.configs.form.rows.forEach((row) => {
row.forEach((col) => {
if (col == null) return
if (col.fetchData && typeof col.fetchData === "function" && !col.hasPrev) {
getData(col);
}
});
});
/** 必须要遍历两遍 */
this.configs.form.rows.forEach((row) => {
row.forEach((col) => {
if (col == null) return
if (col.fetchData && typeof col.fetchData === "function" && col.hasPrev) {
console.log("[hasPrev] set watcher: ", col.hasPrev);
// 获取数据 - 需要等待前置条件时
const unwatch = this.$watch(
() => this.dataForm[col.hasPrev],
(val) => {
console.log("[hasPrev] do watcher: ", col.hasPrev);
if (!val) {
col.injectTo.map((item) => {
this.$set(this.dataForm, item[0], null);
this.$forceUpdate();
});
return;
}
if (!this.editMode) this.$set(this.dataForm, col.prop, null);
const { search, get } = col.fetchDataParam; // 伴随着 hasPrev 出现的
if (!this.cachedList[col.hasPrev]) {
// 如果 prev 还尚不存在,但此时 promiseHistory 一定存在了
promiseHistory[col.hasPrev].then(() => {
handleFn(col.hasPrev, search, val, get, col);
});
} else {
handleFn(col.hasPrev, search, val, get, col);
}
},
{
immediate: false,
}
);
this.watchList.push(unwatch);
}
});
});
if (this.configs.extraFields)
this.configs.extraFields.forEach((cnf) => {
if (cnf.listenTo) {
console.log("set watcher for: ", cnf.prop);
const unwatch = this.$watch(
() => this.dataForm[cnf.listenTo.prop],
(carId) => {
console.log("do watcher for: ", cnf.prop);
if (!carId) return;
if (cnf.disableWatcherOnEdit && this.editMode) return;
cnf.listenTo.handler.call(this, this.cachedList[cnf.listenTo.prop], carId);
},
{
immediate: false,
}
);
this.watchList.push(unwatch);
}
});
},
computed: {
uploadHeaders() {
return {
token: Cookies.get("token") || "",
};
},
},
methods: {
IsNotAvaliable(col) {
return this.detailMode || (col.disableOnEdit && this.editMode);
},
handleFilelistUpdate(newFilelist) {
// TODO: 直接访问 .files 可能不太合适
this.dataForm.files = newFilelist.map((file) => ({
id: file.id,
name: file.name,
url: file.url,
typeCode: file.typeCode,
}));
// 更新请求
if ("id" in this.dataForm && this.dataForm.id !== null && this.dataForm.id !== undefined) this.addOrUpdate("PUT");
else
this.$notify({
title: "等待保存",
message: "已添加文件,请手动点击保存!",
type: "warning",
duration: 2500,
});
},
/** utitilities */
showButton(operate) {
const notDetailMode = !this.detailMode;
const showAlways = operate.showAlways ?? false;
const editMode = operate.showOnEdit && this.dataForm.id;
const addMode = !operate.showOnEdit && !this.dataForm.id;
const permission = operate.permission ? this.$hasPermission(operate.permission) : true;
return notDetailMode && (showAlways || ((editMode || addMode) && permission));
},
resetForm(excludeId = false, immediate = false) {
setTimeout(
() => {
Object.keys(this.dataForm).forEach((key) => {
if (excludeId && key === "id") return;
this.dataForm[key] = null;
});
Object.keys(this.shadowDataForm).forEach((key) => {
this.shadowDataForm[key] = null;
});
this.$refs.dataForm.clearValidate();
this.$emit("dialog-closed"); // 触发父组件销毁自己
},
immediate ? 0 : 200
);
},
/** init **/
init(id, detailMode, tagInfo, extraParams) {
// console.log("[DialogJustForm] init", this.dataForm, id, detailMode);
if (this.$refs.dataForm) {
// console.log("[DialogJustForm] clearing form validation...");
// 当不是首次渲染dialog的时候一开始就清空验证信息本组件的循环里只有一个 dataForm 所以只用取 [0] 即可
this.$refs.dataForm.clearValidate();
}
this.detailMode = detailMode ?? false;
this.editMode = id ? true : false;
/** 判断 extraParams */
if (extraParams && typeof extraParams === "object") {
for (const [key, value] of Object.entries(extraParams)) {
// console.log("[dialog] dataForm | key | value", this.dataForm, key, value);
this.$set(this.dataForm, key, value);
}
}
this.$nextTick(() => {
this.dataForm.id = id || null;
if (this.dataForm.id) {
// 如果是编辑
this.loadingStatus = true;
// 获取基本信息
this.$http
.get(this.urls.base + `/${this.dataForm.id}`)
.then(({ data: res }) => {
if (res && res.code === 0) {
if (res.data) this.dataForm = __pick(res.data, Object.keys(this.dataForm));
else {
this.$message({
message: "后端返回的数据为 null",
type: "error",
duration: 2000,
});
}
/** 格式化文件上传列表 */
if (Array.isArray(this.dataForm.files)) {
this.dataForm.files = this.dataForm.files.map((file) => ({
id: file.id,
name: file.fileUrl.split("/").pop(),
typeCode: file.typeCode,
url: file.fileUrl,
}));
}
// console.log("[DialogJustForm] init():", this.dataForm);
} else {
this.$message({
message: `${res.code}: ${res.msg}`,
type: "error",
duration: 1500,
});
}
this.loadingStatus = false;
})
.catch((err) => {
this.loadingStatus = false;
this.$message({
message: `${err}`,
type: "error",
duration: 1500,
});
});
} else {
this.loadingStatus = false;
}
});
},
handleButtonClick(col) {
if (!("onClick" in col)) {
console.log("[handleButtonClick] 配置文件config.js 里没有绑定 onClick");
return;
}
col.onClick.call(this, this.dataForm.id);
},
/** handlers */
handleSelectChange(col, eventValue) {
if ("autoUpdateProp" in col) {
// 自动更新 相关联 的字段
// console.log(col.options, eventValue, this.savedDatalist);
const item = this.savedDatalist[col.prop].find((item) => item.id === eventValue);
this.shadowDataForm[col.autoUpdateProp] = item.cate ?? null;
}
this.$forceUpdate();
},
handleSwitchChange(val) {
console.log("[dialog] switch change: ", val, this.dataForm);
},
handleComponentModelUpdate(propName, { subject, payload: { data } }) {
this.dataForm[propName] = JSON.stringify(data);
console.log("[DialogJustForm] handleComponentModelUpdate", this.dataForm[propName]);
},
addOrUpdate(method = "POST", url) {
if ("parentId" in this.dataForm) {
console.log("[DialogJustForm parentId]", this.dataForm.parentId);
// 对特殊的键做特殊处理,如 parentId 是一个 cascader获取的值是 ["xxx"]后端只需要xxx
const lastItem = this.dataForm.parentId.length - 1;
this.dataForm.parentId = this.dataForm.parentId[lastItem];
}
this.$refs.dataForm.validate((passed, result) => {
if (passed) {
this.loadingStatus = true;
let httpPayload = null;
/** 针对有文件上传选项的弹窗提供特殊的 payload */
if (this.dataForm.files) {
httpPayload = {
...this.dataForm,
fileIds: this.dataForm.files.map((file) => file.id),
};
} else {
httpPayload = this.dataForm;
}
/** 针对时间段设置 payload */
if ("startTime" in this.dataForm && "endTime" in this.dataForm) {
const { startTime, endTime } = this.dataForm;
httpPayload = {
...httpPayload,
startTime: startTime
? moment(startTime).format("YYYY-MM-DDTHH:mm:ss")
: moment().format("YYYY-MM-DDTHH:mm:ss"),
endTime: endTime ? moment(endTime).format("YYYY-MM-DDTHH:mm:ss") : null, // moment().format("YYYY-MM-DDTHH:mm:ss"),
};
}
/** 针对时间段设置 payload */
if ("updateTime" in this.dataForm) {
const { updateTime } = this.dataForm;
httpPayload = {
...httpPayload,
updateTime: updateTime
? moment(updateTime).format("YYYY-MM-DDTHH:mm:ss")
: moment().format("YYYY-MM-DDTHH:mm:ss"),
};
}
/** 发送 */
return this.$http({
// url: this.urls.formUrl ? this.urls.formUrl : this.urls.base,
url: url ?? this.urls.base,
method,
data: httpPayload,
})
.then(({ data: res }) => {
console.log("[add&update] res is: ", res);
this.loadingStatus = false;
if (res.code === 0) {
this.$message.success(method === "POST" ? "添加成功" : "更新成功");
this.$emit("refreshDataList");
// if (method === "POST") this.handleClose();
this.handleClose();
} else {
this.$message({
message: `${res.code}: ${res.msg}`,
type: "error",
duration: 2000,
});
}
})
.catch((errMsg) => {
this.$message.error("参数错误:" + errMsg);
if (this.loadingStatus) this.loadingStatus = false;
});
} else {
this.$message.error("请核查字段信息");
}
});
},
handleBtnClick(payload) {
console.log("btn click payload: ", payload);
if ("name" in payload) {
switch (payload.name) {
case "cancel":
this.handleClose();
break;
case "reset":
this.resetForm(true, true); // true means exclude id, immediate execution
break;
case "add":
case "update":
this.addOrUpdate(payload.name === "add" ? "POST" : "PUT");
break;
case "add-pos-manually": {
this.addOrUpdate("POST", this.urls.posFormUrl);
break;
}
case "add-car-payload": {
console.log("edit-car-payload", payload);
this.addOrUpdate("POST", this.urls.payloadFormUrl);
break;
}
}
} else {
console.log("[x] 不是这么用的! 缺少name属性");
}
},
handleUploadChange(file, fileList) {
console.log("[Upload] handleUploadChange...", file, fileList);
},
handleClose() {
this.resetForm();
this.$emit("update:dialogVisible", false);
},
},
};
</script>
<style scoped>
.dialog-just-form >>> .el-dialog__body {
/* padding-top: 16px !important;
padding-bottom: 16px !important; */
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.el-select,
.el-cascader,
.el-date-editor {
width: 100% !important;
}
.dialog-just-form >>> .el-dialog__header {
padding: 10px 20px 10px;
/* background: linear-gradient(to bottom, rgba(0, 0, 0, 0.25), white); */
}
</style>