1116 lines
38 KiB
Vue
1116 lines
38 KiB
Vue
<!--
|
||
filename: DialogWithMenu.vue
|
||
author: liubin
|
||
date: 2023-05-26 09:05:57
|
||
description: v1.0 (full)
|
||
-->
|
||
|
||
<template>
|
||
<el-dialog
|
||
class="dialog-with-menu"
|
||
:visible="dialogVisible"
|
||
:destroy-on-close="false"
|
||
@close="handleClose"
|
||
:close-on-click-modal="configs.clickModalToClose ?? false">
|
||
<h1 slot="title" class="dialog-title">
|
||
{{ detailMode ? "查看详情" : dataForm.id ? "编辑" : "新增" }}
|
||
</h1>
|
||
|
||
<div class="dialog-body__inner relative">
|
||
<!-- v-if="dataForm.id && !detailMode && /属性|详情/.test(activeMenu) && $hasPermission()" -->
|
||
<el-button
|
||
v-if="configs.allowAdd ?? (dataForm.id && !detailMode && /属性|详情|参数/.test(activeMenu))"
|
||
plain
|
||
type="primary"
|
||
size="small"
|
||
class="at-right-top"
|
||
style="margin-bottom: 16px"
|
||
@click="handleAddParam()">
|
||
+ 添加
|
||
</el-button>
|
||
<template v-if="dataForm.id && !detailMode && /附件/.test(activeMenu)">
|
||
<el-upload
|
||
style="position: absolute; width: 100%; height: 0"
|
||
name="files"
|
||
:action="uploadUrl"
|
||
:show-file-list="false"
|
||
:headers="uploadHeaders"
|
||
:on-success="handleUploadSuccess"
|
||
:before-upload="handleUploadCheck">
|
||
<el-button plain type="primary" size="small" class="at-right-top" style="">
|
||
<i class="el-icon-upload"></i>
|
||
上传
|
||
</el-button>
|
||
</el-upload>
|
||
</template>
|
||
|
||
<!-- menu -->
|
||
<el-tabs v-model="activeMenu" type="card" @tab-click="handleTabClick">
|
||
<!-- <el-tab-pane v-for="(tab, index) in configs.menu" :key="index" :label="tab.name" :name="tab.name"> -->
|
||
<el-tab-pane v-for="(tab, index) in actualMenus" :key="index" :name="tab.name">
|
||
<span class="slot" slot="label">
|
||
<i
|
||
:class="{
|
||
'el-icon-edit': tab.key === 'info',
|
||
'el-icon-s-data': tab.key === 'attr',
|
||
'el-icon-folder-opened': tab.key === 'attachment',
|
||
}"></i>
|
||
{{ tab.name }}
|
||
</span>
|
||
|
||
<!-- 表单标签页 -->
|
||
<div v-if="tab.key === 'info'">
|
||
<!-- 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">
|
||
<el-form-item
|
||
:label="col.label"
|
||
:prop="col.prop"
|
||
:rules="col.rules || null"
|
||
v-show="!col.forceDisabled || (col.forceDisabled && dataForm.id)">
|
||
<div v-if="col.forceDisabled && dataForm.id" class="force-disabled">
|
||
<el-tag :key="col.key" :type="col.type">{{ dataForm[col.prop] || "-" }}</el-tag>
|
||
</div>
|
||
<el-input
|
||
v-if="col.input"
|
||
v-model="dataForm[col.prop]"
|
||
clearable
|
||
:disabled="disableCondition(col.prop)"
|
||
v-bind="col.elparams" />
|
||
<el-cascader
|
||
v-if="col.cascader"
|
||
v-model="dataForm[col.prop]"
|
||
:options="col.options"
|
||
:disabled="detailMode"
|
||
v-bind="col.elparams"></el-cascader>
|
||
<el-select
|
||
v-if="col.select"
|
||
v-model="dataForm[col.prop]"
|
||
clearable
|
||
:disabled="disableCondition(col.prop)"
|
||
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="1"
|
||
:inactive-value="0"
|
||
@change="handleSwitchChange"
|
||
:disabled="disableCondition(col.prop)" />
|
||
<el-input
|
||
v-if="col.textarea"
|
||
type="textarea"
|
||
v-model="dataForm[col.prop]"
|
||
:disabled="disableCondition(col.prop)"
|
||
v-bind="col.elparams" />
|
||
<quillEditor
|
||
v-if="col.richInput"
|
||
ref="quill-editor"
|
||
v-model="dataForm[col.prop]"
|
||
:options="col.quillConfig ?? defaultQuillConfig"
|
||
style="margin-top: 42px"
|
||
:disabled="disableCondition(col.prop)" />
|
||
|
||
<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>
|
||
</div>
|
||
|
||
<!-- 表格标签页 -->
|
||
<div v-if="dataForm.id && tab.key === 'attr'" key="attr-list">
|
||
<BaseListTable
|
||
:table-config="null"
|
||
:column-config="filteredTableProps"
|
||
:table-data="subList"
|
||
@operate-event="handleTableRowOperate"
|
||
:current-page="attrPage"
|
||
:current-size="attrSize"
|
||
:refresh-layout-key="Math.random()"
|
||
v-loading="loadingStatus" />
|
||
<!-- paginator -->
|
||
<el-pagination
|
||
class=""
|
||
style="text-align: left"
|
||
background
|
||
@size-change="handleSizeChange"
|
||
@current-change="handlePageChange"
|
||
:current-page.sync="attrPage"
|
||
:page-sizes="[10, 20, 50]"
|
||
:page-size="attrSize"
|
||
:total="attrTotal"
|
||
layout="total, sizes, prev, next"></el-pagination>
|
||
</div>
|
||
|
||
<!-- 附件标签页 -->
|
||
<div v-if="dataForm.id && tab.key === 'attachment'" key="attachment">
|
||
<div class="upload-tips" style="font-size: 0.8em; margin-bottom: 12px">
|
||
文件大小不要超过 2MB
|
||
</div>
|
||
<!-- 附件列表 -->
|
||
<div class="" v-loading="loadingStatus">
|
||
<ul class="file-list">
|
||
<li v-for="(file, index) in fileList" :key="index">
|
||
<span class="file-name">{{ file.name }}</span>
|
||
<span class="file-operations">
|
||
<span class="file-icon preview" @click="handleFileClick('view', file)">
|
||
<i class="el-icon-view" style="cursor: pointer"></i>
|
||
</span>
|
||
<span class="file-icon download" @click="handleFileClick('download', file)">
|
||
<i class="el-icon-download" style="color: #0b58ff; cursor: pointer"></i>
|
||
</span>
|
||
<span class="file-icon delete" @click="handleFileClick('delete', file)">
|
||
<i class="el-icon-delete" style="color: red; cursor: pointer"></i>
|
||
</span>
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<!-- img preview dialog -->
|
||
<el-dialog
|
||
key="image-preview-dialog"
|
||
class="image-preview-dialog"
|
||
:visible.sync="imgPreviewDialogVisible"
|
||
:append-to-body="true">
|
||
<div class="img-container">
|
||
<img width="100%" :src="currentImgUrl" alt="" />
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<!-- sub dialog -->
|
||
<small-dialog
|
||
:append-to-body="true"
|
||
v-if="showSubDialog"
|
||
ref="subDialog"
|
||
:url="urls.subase"
|
||
:configs="configs.subDialog"
|
||
:related-id="dataForm.id"
|
||
@refreshDataList="getSubList"></small-dialog>
|
||
|
||
<!-- 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)"
|
||
:loading="(operate.name === 'add' || operate.name === 'update') && btnLoading">
|
||
{{ 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 SmallDialog from "@/components/SmallDialog.vue";
|
||
import BaseListTable from "@/components/BaseListTable.vue";
|
||
import Cookies from "js-cookie";
|
||
import "quill/dist/quill.core.css";
|
||
import "quill/dist/quill.snow.css";
|
||
import "quill/dist/quill.bubble.css";
|
||
|
||
import { quillEditor } from "vue-quill-editor";
|
||
|
||
function reConstructTreeData(listObj) {
|
||
const entry = [];
|
||
Object.keys(listObj).map((key) => {
|
||
const currentNode = listObj[key];
|
||
currentNode.label = currentNode.name;
|
||
currentNode.value = currentNode.id;
|
||
if (currentNode.parentId === "0") {
|
||
entry.push(listObj[key]);
|
||
return; // return { label: currentNode.name, value: currentNode.id, children: currentNode.children ?? [] };
|
||
}
|
||
const parentNode = listObj[currentNode.parentId];
|
||
if (!parentNode.children) {
|
||
parentNode.children = [];
|
||
}
|
||
parentNode.children.push(currentNode);
|
||
});
|
||
return entry;
|
||
}
|
||
|
||
export default {
|
||
name: "DialogWithMenu",
|
||
components: { SmallDialog, BaseListTable, quillEditor },
|
||
props: {
|
||
configs: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
dialogVisible: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
},
|
||
inject: ["urls"],
|
||
data() {
|
||
const dataForm = {};
|
||
const autoDisabledQueue = [];
|
||
const watingToRefreshQueue = [];
|
||
const cached = {};
|
||
|
||
this.configs.form.rows.forEach((row) => {
|
||
row.forEach((col) => {
|
||
if (col.upload) dataForm[col.prop] = col.default ?? [];
|
||
else dataForm[col.prop] = col.default ?? null;
|
||
|
||
if (col.autoDisabled) {
|
||
autoDisabledQueue.push(col.prop);
|
||
}
|
||
if (!!col.refreshOptionsAfterConfirm) watingToRefreshQueue.push(col);
|
||
|
||
if (col.fetchData)
|
||
col.fetchData().then(({ data: res }) => {
|
||
//console.log("[Fetch Data]", "list" in res.data, res.data, res.data.list);
|
||
if (res.code === 0) {
|
||
if (col.cacheFetchedData) {
|
||
// cache fetched data
|
||
cached[col.prop] =
|
||
"list" in res.data ? res.data.list : Array.isArray(res.data) ? res.data : [];
|
||
}
|
||
|
||
if (!col.options || !col.options.length) {
|
||
this.$set(
|
||
col,
|
||
"options",
|
||
"list" in res.data
|
||
? "customLabel" in col
|
||
? 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,
|
||
}))
|
||
: "customLabel" in col
|
||
? 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,
|
||
}))
|
||
);
|
||
}
|
||
// col.options = res.data.list;
|
||
else if (col.options.length) {
|
||
"list" in res.data
|
||
? res.data.list.unshift(...col.options)
|
||
: res.data.unshift(...col.options);
|
||
this.$set(
|
||
col,
|
||
"options",
|
||
"list" in res.data
|
||
? res.data.list.map((i) => ({
|
||
label: col.optionLabel ? i[col.optionLabel] : i.name,
|
||
value: col.optionValue ? i[col.optionValue] : i.id,
|
||
}))
|
||
: res.data.map((i) => ({
|
||
label: col.optionLabel ? i[col.optionLabel] : i.name,
|
||
value: col.optionValue ? i[col.optionValue] : i.id,
|
||
}))
|
||
);
|
||
}
|
||
} else {
|
||
col.options.splice(0);
|
||
}
|
||
// dataForm[col.prop] = col.default ?? null; // not perfect!
|
||
});
|
||
else if (col.fetchTreeData) {
|
||
// 获取设备类型时触发的,用于前端构建属性结构,约定,parentId 为0时是顶级节点
|
||
col.fetchTreeData().then(({ data: res }) => {
|
||
//console.log("[Fetch Tree Data]", res.data);
|
||
if (res.code === 0) {
|
||
// 先把数据先重构成一个对象
|
||
const obj = {};
|
||
|
||
if ("list" in res.data) {
|
||
res.data.list.map((item) => {
|
||
obj[item.id] = item;
|
||
});
|
||
} else if (Array.isArray(res.data)) {
|
||
res.data.map((item) => {
|
||
obj[item.id] = item;
|
||
});
|
||
}
|
||
// 再过滤这个对象
|
||
let filteredList = reConstructTreeData(obj);
|
||
//console.log("** filteredList **", filteredList);
|
||
// 最后设置 options
|
||
this.$set(col, "options", filteredList);
|
||
} else {
|
||
col.options.splice(0);
|
||
this.$message.error(res.msg);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
return {
|
||
// configs,
|
||
btnLoading: false,
|
||
loadingStatus: false,
|
||
activeMenu: this.configs.menu[0].name,
|
||
dataForm,
|
||
detailMode: false,
|
||
autoDisabledQueue,
|
||
watingToRefreshQueue,
|
||
cached,
|
||
showBaseDialog: false,
|
||
baseDialogConfig: null,
|
||
subList: [],
|
||
showSubDialog: false,
|
||
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,
|
||
},
|
||
attrPage: 1,
|
||
attrSize: 20,
|
||
attrTotal: 0,
|
||
fileList: [],
|
||
imgPreviewDialogVisible: false,
|
||
currentImgUrl: "",
|
||
};
|
||
},
|
||
mounted() {
|
||
this.configs.form.rows.forEach((row) => {
|
||
row.forEach((col) => {
|
||
if (
|
||
col.changeReflects &&
|
||
typeof col.changeReflects === "object" &&
|
||
"fromKey" in col.changeReflects &&
|
||
"toProp" in col.changeReflects
|
||
) {
|
||
this.$watch(
|
||
() => this.dataForm[col.prop],
|
||
(val) => {
|
||
if (val && col.prop in this.cached) {
|
||
//console.log(
|
||
// "here changeReflects",
|
||
// col.prop,
|
||
// col.changeReflects.toProp,
|
||
// this.cached[col.prop]
|
||
// );
|
||
if (typeof col.changeReflects.fromKey === "string") {
|
||
this.dataForm[col.changeReflects.toProp] = this.cached[col.prop].find(
|
||
(item) => item.id === val
|
||
)?.[col.changeReflects.fromKey];
|
||
} else if (Array.isArray(col.changeReflects.fromKey) && col.changeReflects.delimiter) {
|
||
const foundItem = (this.dataForm[col.changeReflects.toProp] = this.cached[col.prop].find(
|
||
(item) => item.id === val
|
||
));
|
||
if (foundItem) {
|
||
const values = col.changeReflects.fromKey.map((key) => foundItem[key]);
|
||
this.dataForm[col.changeReflects.toProp] = values.join(col.changeReflects.delimiter);
|
||
} else {
|
||
this.dataForm[col.changeReflects.toProp] = col.changeReflects.delimiter;
|
||
//console.log("[DialogWithMenu] mounted() 没找到对应数据");
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
immediate: false,
|
||
}
|
||
);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
watch: {
|
||
dialogVisible: function (val) {
|
||
if (!!val) {
|
||
this.attrPage = 1;
|
||
this.attrSize = 20;
|
||
}
|
||
},
|
||
},
|
||
computed: {
|
||
actualMenus() {
|
||
return this.configs.menu.filter((m) => {
|
||
if (m.onlyEditMode && !this.dataForm.id) {
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
},
|
||
filteredTableProps() {
|
||
return this.detailMode
|
||
? this.configs.table.props.filter((v) => v.prop !== "operations")
|
||
: this.configs.table.props;
|
||
},
|
||
uploadHeaders() {
|
||
return {
|
||
token: Cookies.get("token") || "",
|
||
};
|
||
},
|
||
uploadUrl() {
|
||
return this.configs.menu.find((item) => item.key === "attachment")?.actionUrl || "#";
|
||
},
|
||
},
|
||
|
||
methods: {
|
||
disableCondition(prop) {
|
||
return this.detailMode || this.autoDisabledQueue.indexOf(prop) !== -1;
|
||
},
|
||
/** 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;
|
||
const currentMenuKey = this.configs.menu.find((item) => item.name === this.activeMenu)?.key;
|
||
return (
|
||
notDetailMode && (showAlways || ((editMode || addMode) && permission)) && currentMenuKey === "info"
|
||
);
|
||
},
|
||
|
||
resetForm(excludeId = false, immediate = false) {
|
||
setTimeout(
|
||
() => {
|
||
Object.keys(this.dataForm).forEach((key) => {
|
||
//console.log("reset form, key", key);
|
||
if (excludeId && key === "id") return;
|
||
if ("files" in this.dataForm) this.dataForm.files = [];
|
||
else if ("fileIds" in this.dataForm) this.dataForm.fileIds = [];
|
||
else this.$set(this.dataForm, key, null);
|
||
if (Array.isArray(this.fileList)) {
|
||
this.fileList = [];
|
||
}
|
||
});
|
||
this.activeMenu = this.configs.menu[0].name;
|
||
this.$refs.dataForm[0].resetFields();
|
||
//console.log("清除Form...", this.dataForm);
|
||
},
|
||
immediate ? 0 : 200
|
||
);
|
||
},
|
||
|
||
updateOptions() {
|
||
return new Promise((resolve, reject) => {
|
||
if (this.watingToRefreshQueue.length) {
|
||
this.watingToRefreshQueue.forEach((opt) => {
|
||
//console.log("[刷新数据, ", opt, "]");
|
||
if ("fetchData" in opt) {
|
||
opt.fetchData(this.dataForm.id ? this.dataForm.id : -1).then(({ data: res }) => {
|
||
if (res.code === 0) {
|
||
this.$set(
|
||
opt,
|
||
"options",
|
||
"list" in res.data
|
||
? res.data.list.map((i) => ({ label: i.code, value: i.id }))
|
||
: res.data.map((i) => ({ label: i.code, value: i.id }))
|
||
);
|
||
|
||
resolve({ done: true });
|
||
} else {
|
||
this.$message({
|
||
message: `${res.code}: ${res.msg}`,
|
||
type: "error",
|
||
duration: 1500,
|
||
});
|
||
|
||
resolve({ done: false });
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} else resolve(null);
|
||
});
|
||
},
|
||
|
||
/** init **/
|
||
init(id, detailMode, menu) {
|
||
// this.dialogVisible = true;
|
||
if (this.$refs.dataForm && this.$refs.dataForm.length) {
|
||
// 当不是首次渲染dialog的时候,一开始就清空验证信息,本组件的循环里只有一个 dataForm 所以只用取 [0] 即可
|
||
this.$refs.dataForm[0].clearValidate();
|
||
}
|
||
//console.log("[dialog] DialogWithHead init():", id, detailMode);
|
||
|
||
this.detailMode = detailMode ?? false;
|
||
this.$nextTick(() => {
|
||
this.dataForm.id = id || null;
|
||
if (this.dataForm.id) {
|
||
// 如果是编辑
|
||
this.loadingStatus = true;
|
||
// 提前获取属性列表
|
||
this.getSubList();
|
||
// 获取详情
|
||
this.updateOptions().then((result) => {
|
||
if (result === null || (typeof result === "object" && result.done)) {
|
||
this.$http.get(this.urls.base + `/${this.dataForm.id}`).then(({ data: res }) => {
|
||
if (res && res.code === 0) {
|
||
const dataFormKeys = Object.keys(this.dataForm);
|
||
//console.log("[DialogWithMenu] dataFormKeys -------->", dataFormKeys);
|
||
|
||
this.dataForm = __pick(res.data, dataFormKeys);
|
||
if ("files" in res.data) {
|
||
//console.log("[DialogWithMenu] fileList===>", res.data.files, this.fileList);
|
||
/** 返回的文件列表 */
|
||
this.fileList = res.data.files
|
||
? res.data.files.map((file) => ({
|
||
id: file.id,
|
||
name: file.fileUrl.split("/").pop(),
|
||
url: file.fileUrl,
|
||
typeCode: file.typeCode,
|
||
}))
|
||
: [];
|
||
}
|
||
}
|
||
this.loadingStatus = false;
|
||
// 是否要跳转到附件页
|
||
if (menu && menu.key) {
|
||
this.activeMenu = this.configs.menu.find((item) => item.key === menu.key)?.name;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} else {
|
||
// 如果不是编辑
|
||
this.updateOptions();
|
||
}
|
||
});
|
||
},
|
||
|
||
/** handlers */
|
||
handleFileClick(type, file) {
|
||
switch (type) {
|
||
case "view": {
|
||
// 加载图片
|
||
this.$http
|
||
.get("/pms/attachment/downloadFile", {
|
||
params: {
|
||
attachmentId: file.id,
|
||
type: 0, // 0 预览,1 下载
|
||
},
|
||
responseType: "blob",
|
||
})
|
||
.then(({ data: res }) => {
|
||
//console.log("preivew", res);
|
||
if (/image/i.test(res.type)) {
|
||
// 显示图片
|
||
this.currentImgUrl = URL.createObjectURL(res);
|
||
this.imgPreviewDialogVisible = true;
|
||
} else if (/pdf/i.test(res.type)) {
|
||
// 预览pdf
|
||
let a = document.createElement("a");
|
||
a.setAttribute("target", "_blank");
|
||
a.href = URL.createObjectURL(res);
|
||
a.click();
|
||
//console.log("before remove a ", a);
|
||
a.remove();
|
||
//console.log("removed a ", a);
|
||
} else {
|
||
this.$message({
|
||
message: "非图片和PDF文件请下载后预览",
|
||
type: "error",
|
||
duration: 1500,
|
||
});
|
||
}
|
||
});
|
||
break;
|
||
}
|
||
|
||
case "download": {
|
||
// 下载图片
|
||
this.$http
|
||
.get("/pms/attachment/downloadFile", {
|
||
params: {
|
||
attachmentId: file.id,
|
||
type: 1, // 0 预览,1 下载
|
||
},
|
||
responseType: "blob",
|
||
})
|
||
.then(({ data: res }) => {
|
||
const blob = new Blob([res]);
|
||
|
||
/** 通知 */
|
||
this.$notify({
|
||
title: "成功",
|
||
message: "开始下载",
|
||
type: "success",
|
||
duration: 1200,
|
||
});
|
||
|
||
if ("download" in document.createElement("a")) {
|
||
const alink = document.createElement("a");
|
||
alink.download = file.name;
|
||
alink.style.display = "none";
|
||
alink.target = "_blank";
|
||
alink.href = URL.createObjectURL(blob);
|
||
document.body.appendChild(alink);
|
||
alink.click();
|
||
URL.revokeObjectURL(alink.href);
|
||
document.body.removeChild(alink);
|
||
} else {
|
||
navigator.msSaveBlob(blob, fileName);
|
||
}
|
||
});
|
||
|
||
break;
|
||
}
|
||
|
||
case "delete": {
|
||
return this.$confirm(`确定删除图片: ${file.name}`, "提示", {
|
||
confirmButtonText: "确认",
|
||
cancelButtonText: "我再想想",
|
||
type: "warning",
|
||
})
|
||
.then(() => {
|
||
this.loadingStatus = true;
|
||
const newFilelist = this.fileList.filter((f) => f.id !== file.id);
|
||
this.updateRemoteFiles(newFilelist).then((msg) => {
|
||
if (msg && msg === "success") {
|
||
this.fileList = newFilelist;
|
||
this.loadingStatus = false;
|
||
/** 通知 */
|
||
this.$notify({
|
||
title: "成功",
|
||
message: "已删除",
|
||
type: "success",
|
||
duration: 1200,
|
||
});
|
||
}
|
||
});
|
||
})
|
||
.catch((err) => {});
|
||
}
|
||
}
|
||
},
|
||
|
||
handleUploadSuccess(response, file, fileList) {
|
||
//console.log("[DialogWithMenu] uploadedFileList", response, file, fileList);
|
||
|
||
if (response.code === 0) {
|
||
const uploadedFile = response.data[0];
|
||
const fileItem = {
|
||
id: uploadedFile.id,
|
||
name: uploadedFile.fileUrl.split("/").pop(),
|
||
url: uploadedFile.fileUrl,
|
||
typeCode: file.typeCode,
|
||
};
|
||
|
||
this.loadingStatus = true;
|
||
this.updateRemoteFiles([...this.fileList, fileItem]).then((msg) => {
|
||
if (msg && msg === "success") {
|
||
this.fileList.push(fileItem);
|
||
this.loadingStatus = false;
|
||
/** 通知 */
|
||
this.$notify({
|
||
title: "成功",
|
||
message: "上传成功",
|
||
type: "success",
|
||
duration: 1200,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
},
|
||
|
||
updateRemoteFiles(filelist) {
|
||
return this.$http
|
||
.put("/pms/product", {
|
||
id: "id" in this.dataForm ? this.dataForm.id : "DEFAULT_ID",
|
||
fileIds: filelist.map((f) => f.id),
|
||
})
|
||
.then(({ data: res }) => {
|
||
if (res.code === 0) return "success";
|
||
});
|
||
},
|
||
|
||
handleUploadCheck(file) {
|
||
//console.log("[before upload]", file);
|
||
const LIMIT = 2 * 1024 * 1024; // bytes
|
||
if (file.size > LIMIT) {
|
||
this.$message({
|
||
message: "文件大小不能超过 2MB",
|
||
type: "error",
|
||
duration: 1500,
|
||
});
|
||
return false;
|
||
} else return true;
|
||
},
|
||
|
||
handleComponentModelUpdate(propName, { subject, payload: { data } }) {
|
||
this.dataForm[propName] = JSON.stringify(data);
|
||
//console.log("[DialogJustForm] handleComponentModelUpdate", this.dataForm[propName]);
|
||
},
|
||
handleSelectChange(col, eventValue) {
|
||
//console.log("[dialog] select change: ", col, eventValue);
|
||
},
|
||
handleSwitchChange(val) {
|
||
//console.log("[dialog] switch change: ", val, this.dataForm);
|
||
},
|
||
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
|
||
break;
|
||
case "add":
|
||
case "update": {
|
||
this.$refs.dataForm[0].validate((passed, result) => {
|
||
if (passed) {
|
||
// 如果通过验证
|
||
this.btnLoading = true;
|
||
this.loadingStatus = true;
|
||
const method = payload.name === "add" ? "POST" : "PUT";
|
||
|
||
// 判断是否有附件选项
|
||
const hasAttachment = !!this.configs.menu.find((item) => item.key === "attachment");
|
||
if (hasAttachment) {
|
||
const fileIds = this.fileList.map((item) => item.id);
|
||
this.$set(this.dataForm, "fileIds", fileIds);
|
||
}
|
||
|
||
// 加载额外需要的 id
|
||
let extraIds = {};
|
||
if (this.configs.extraIds && typeof this.configs.extraIds === "object") {
|
||
// 如果配置里,有 extraIds 选项
|
||
Object.entries(this.configs.extraIds).forEach(([key, value]) => {
|
||
extraIds[key] = value;
|
||
});
|
||
}
|
||
|
||
// const actualPayload = Object.assign({}, JSON.parse(JSON.stringify(this.dataForm)));
|
||
// if (this.configs.excludeProps && Array.isArray(this.configs.excludePropss)) {
|
||
// // 如果配置里,有 excludeProps 选项
|
||
// this.configs.excludeProps.forEach((prop) => {
|
||
// delete actualPayload[prop];
|
||
// });
|
||
// //console.log('actualPayload', actualPayload);
|
||
// }
|
||
|
||
// 实际发送请求
|
||
this.btnLoading = true;
|
||
this.$http({
|
||
url: this.urls.base,
|
||
method,
|
||
data: {
|
||
...extraIds,
|
||
...this.dataForm,
|
||
},
|
||
})
|
||
.then(({ data: res }) => {
|
||
this.btnLoading = false;
|
||
this.loadingStatus = false;
|
||
//console.log("[add&update] res is: ", res);
|
||
if (res.code === 0) {
|
||
this.$message.success(payload.name === "add" ? "添加成功" : "更新成功");
|
||
this.$emit("refreshDataList");
|
||
|
||
// 如果 watingToRefreshQueue 队列里有数据
|
||
// if (this.watingToRefreshQueue.length) {
|
||
// // 刷新队列
|
||
// this.watingToRefreshQueue.forEach((opt) => {
|
||
// //console.log("[刷新数据, ", opt, "]");
|
||
// if ("fetchData" in opt) {
|
||
// opt.fetchData().then(({ data: res }) => {
|
||
// if (res.code === 0) {
|
||
// this.$set(
|
||
// opt,
|
||
// "options",
|
||
// "list" in res.data
|
||
// ? res.data.list.map((i) => ({ label: i.name, value: i.id }))
|
||
// : res.data.map((i) => ({ label: i.name, value: i.id }))
|
||
// );
|
||
// }
|
||
// });
|
||
// }
|
||
// });
|
||
// }
|
||
|
||
this.handleClose();
|
||
} else {
|
||
this.$message({
|
||
message: `${res.code}: ${res.msg}`,
|
||
type: "error",
|
||
duration: 2000,
|
||
});
|
||
if (this.btnLoading) this.btnLoading = false;
|
||
}
|
||
})
|
||
.catch((errMsg) => {
|
||
this.$message.error("参数错误:" + errMsg);
|
||
if (this.loadingStatus) this.loadingStatus = false;
|
||
if (this.btnLoading) this.btnLoading = false;
|
||
});
|
||
} else {
|
||
this.$message.error("请核查字段信息");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
},
|
||
handleTabClick(payload) {
|
||
// //console.log("tab click payload: ", this.activeMenu);
|
||
// if (this.activeMenu === this.configs.menu[1].name) {
|
||
// // 获取数据
|
||
// this.getSubList();
|
||
// }
|
||
},
|
||
|
||
handlePageChange(val) {
|
||
this.getSubList(val, this.attrSize);
|
||
},
|
||
handleSizeChange(val) {
|
||
this.attrPage = 1;
|
||
this.attrSize = val;
|
||
this.getSubList(1, val);
|
||
},
|
||
|
||
getSubList(page, size) {
|
||
const params = {};
|
||
params.page = page ?? this.attrPage;
|
||
params.limit = size ?? this.attrSize;
|
||
|
||
const requiredParams = this.configs.table.extraParams;
|
||
if (requiredParams) {
|
||
if (Array.isArray(requiredParams)) {
|
||
requiredParams.forEach((str) => {
|
||
if (/id/i.test(str)) {
|
||
params[str] = this.dataForm.id;
|
||
} else {
|
||
params[str] = "";
|
||
}
|
||
});
|
||
} else if (typeof requiredParams === "string") {
|
||
// 如果需要额外参数,一般肯定需要
|
||
params[this.configs.table.extraParams] = this.dataForm.id;
|
||
// 此时 dataForm.id 一定是存在的
|
||
}
|
||
}
|
||
this.$http.get(this.urls.subpage, { params }).then(({ data: res }) => {
|
||
//console.log("[DialogWithMenu] getSubList:", res);
|
||
if (res.code === 0 && res.data?.list) {
|
||
// 有数据
|
||
this.subList = res.data.list;
|
||
this.attrTotal = res.data.total;
|
||
} else {
|
||
this.subList.splice(0);
|
||
this.attrTotal = 0;
|
||
}
|
||
});
|
||
},
|
||
|
||
handleAddParam(id) {
|
||
this.showSubDialog = true;
|
||
this.$nextTick(() => {
|
||
this.$refs.subDialog.init(id ?? null);
|
||
});
|
||
},
|
||
|
||
handleClose() {
|
||
this.resetForm();
|
||
this.$emit("update:dialogVisible", false);
|
||
},
|
||
|
||
/** 列表handlers */
|
||
handleAddItem() {
|
||
// //console.log('[dialog] handleAddItem ot list...');
|
||
this.showBaseDialog = true;
|
||
this.$nextTick(() => {
|
||
//console.log("[sub-dialog] ", this.$refs["sub-dialog"].init());
|
||
});
|
||
},
|
||
handleTableRowOperate({ type, data }) {
|
||
//console.log("handleTableRowOperate", type, data);
|
||
|
||
switch (type) {
|
||
case "delete": {
|
||
// 确认是否删除
|
||
//console.log("delete....", data);
|
||
const itemName =
|
||
typeof data === "object" ? data.attrName || data.name || data.material || data.id : data;
|
||
return this.$confirm(`是否删除条目: ${itemName}`, "提示", {
|
||
confirmButtonText: "确认",
|
||
cancelButtonText: "我再想想",
|
||
type: "warning",
|
||
})
|
||
.then(() => {
|
||
// this.$http.delete(this.urls.base + `/${data}`).then((res) => {
|
||
this.$http({
|
||
url: this.urls.subase,
|
||
method: "DELETE",
|
||
data: [`${data.id}`],
|
||
}).then(({ data: res }) => {
|
||
if (res.code === 0) {
|
||
this.$message.success({
|
||
message: "删除成功!",
|
||
duration: 1000,
|
||
onClose: () => {
|
||
this.getSubList(1, 20);
|
||
},
|
||
});
|
||
}
|
||
});
|
||
})
|
||
.catch((err) => {});
|
||
}
|
||
case "edit": {
|
||
this.handleAddParam(data); /** data is ==> id */
|
||
break;
|
||
}
|
||
// case 'view-detail-action':
|
||
// this.openDialog(data, true);
|
||
// break;
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.el-menu {
|
||
margin: 16px 0 !important;
|
||
}
|
||
|
||
.el-menu.el-menu--horizontal {
|
||
border: none !important;
|
||
/* background: #0f02 !important; */
|
||
}
|
||
|
||
.dialog-with-menu >>> .el-dialog__body {
|
||
/* padding-top: 16px !important;
|
||
padding-bottom: 16px !important; */
|
||
padding-top: 0 !important;
|
||
padding-bottom: 0 !important;
|
||
}
|
||
|
||
/* .dialog-with-menu >>> .dialog-body__inner {
|
||
padding: 16px;
|
||
} */
|
||
|
||
.el-select,
|
||
.el-cascader {
|
||
width: 100% !important;
|
||
}
|
||
|
||
.relative {
|
||
position: relative;
|
||
}
|
||
|
||
.at-right-top {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
z-index: 10000;
|
||
}
|
||
|
||
ul.file-list,
|
||
ul.file-list > li {
|
||
padding: 0;
|
||
margin: 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.file-list {
|
||
max-height: 20vh;
|
||
overflow-y: auto;
|
||
margin-top: 12px;
|
||
/* width: 240px; */
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
ul.file-list > li {
|
||
border-radius: 4px;
|
||
background-color: #edededd2;
|
||
padding: 8px;
|
||
margin-bottom: 2px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
ul.file-list > li:hover {
|
||
background-color: #ededed;
|
||
}
|
||
|
||
.file-operations {
|
||
display: flex;
|
||
}
|
||
|
||
.file-icon {
|
||
margin-right: 8px;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
display: flex;
|
||
place-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
/* .image-preview-dialog {
|
||
} */
|
||
|
||
.force-disabled {
|
||
margin-top: 42px;
|
||
}
|
||
</style>
|