You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

484 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script>
import formBuilder from '@/utils/formBuilder'
import moment from "moment/moment";
import MobileMultipleSelect from "@/components/MobileMultipleSelect/index.vue";
import {deepCopy} from "@/utils";
import {PopupManager} from "element-ui/lib/utils/popup";
import request from '@/utils/request'
import {getToken} from '@/utils/auth'
import { evaluateFieldLinkage } from "@/utils/fieldLinkage";
export default {
components: {
MobileMultipleSelect
},
props: {
config: {
type: Object
},
isFirstNode: {
type: Boolean,
default: true
},
needFlowTitle: {
type: Boolean,
default: true
},
readable: {
type: Array,
default: () => [],
required: true
},
writeable: {
type: Array,
default: () => [],
required: true
},
originalForm: {
type: Object,
default: () => ({}),
required: true
},
subForm: {
type: Map,
default: () => new Map()
},
device: {
type: String,
default: 'desktop',
required: true
},
fields: {
type: Array,
default: () => [],
required: true
},
scriptContent: String,
rules: {
type: Object,
default: () => ({}),
},
subRules: {
type: Object,
default: () => ({}),
},
logs: {
type: Array,
default: () => []
}
},
data() {
return {
isShowModal: false,
modalRender: () => {},
zIndex: PopupManager.nextZIndex(),
jointlySignLog: [], // 所有会签log记录
form: {},
// fill_flow_title 自动填充 watcher监听组件内部 form而不是父组件
_unwatchFillFlowTitle: null,
file: {
ggg: []
},
flows: {},
showControl: {},
vanCalendarOption: {
isShow: false,
forFormName: "",
originalObj: ""
},
vanTimePickerOption: {
isShow: false,
forFormName: "",
originalObj: ""
},
vanPopupOption: {
isShow: false,
forFormName: "",
columns: [],
originalObj: ""
},
multipleSelectOption: {
isShow: false,
forFormName: "",
columns: [],
originalObj: "",
multipleLimit: 50,
options: {
label: 'name',
value: 'id'
}
}
}
},
methods: {
setupFillFlowTitleWatcher() {
try {
if (typeof this._unwatchFillFlowTitle === "function") {
this._unwatchFillFlowTitle();
}
} catch (e) {
// ignore
}
this._unwatchFillFlowTitle = null;
// 只在新建流程时生效(有 flow_id 代表待办/编辑/详情)
if (this.$route?.query?.flow_id) return;
const list = Array.isArray(this.fields) ? this.fields : [];
const allowedTypes = new Set(["text", "textarea", "select"]);
const fillField = list.find((f) => Number(f?.fill_flow_title) === 1 && allowedTypes.has(f?.type) && f?.name);
if (!fillField) return;
const fieldName = fillField.name;
this._unwatchFillFlowTitle = this.$watch(
() => (this.form ? this.form[fieldName] : undefined),
(newVal) => {
// 保留 isFirstNode 判断
if (!this.isFirstNode || this.$route?.query?.flow_id) return;
const parseMultiValue = (raw) => {
if (raw === null || raw === undefined) return [];
if (Array.isArray(raw)) return raw.map((v) => String(v).trim()).filter(Boolean);
const s = String(raw).trim();
if (!s) return [];
const parts = s.includes("|") ? s.split("|") : (s.includes(",") ? s.split(",") : [s]);
return parts.map((v) => String(v).trim()).filter(Boolean);
};
const getOptionLabelFromSelectionModelItems = (field, value) => {
const items = field?.selection_model_items;
if (!Array.isArray(items) || items.length === 0) return null;
const v = String(value);
const hit = items.find((it) => String(it?.id ?? it?.value ?? it?.key ?? "") === v) || null;
if (!hit) return null;
return hit?.name ?? hit?.label ?? hit?.title ?? (hit?.id !== undefined ? String(hit.id) : null);
};
const getOptionLabelFromStub = (field, value) => {
const stub = field?.stub;
if (!stub) return null;
const lines = String(stub).split(/\\r?\\n/).map((s) => s.trim()).filter(Boolean);
const v = String(value);
return lines.find((line) => line === v) || null;
};
const formatSelectFillValue = (field, rawValue) => {
const values = parseMultiValue(rawValue);
if (values.length === 0) return "";
if (values.length > 1) {
return `${field.label || field.name} - ${values.join("、")}`;
}
const value = values[0];
const optionLabel =
getOptionLabelFromSelectionModelItems(field, value) ||
getOptionLabelFromStub(field, value) ||
value;
return `${optionLabel} - ${value}`;
};
let v = "";
if (fillField.type === "select") {
v = formatSelectFillValue(fillField, newVal);
} else {
v = newVal === null || newVal === undefined ? "" : String(newVal).trim();
}
if (!v) return;
this.$set(this.form, "flow_title", v);
},
{ immediate: true }
);
},
// 给自定义脚本注册的事件监听加保护,避免脚本内部抛错导致页面整体报错
installSafeEventListenerGuard() {
if (window.__oa_safe_listener_guard_installed) return;
window.__oa_safe_listener_guard_installed = true;
const map = new WeakMap();
const origAdd = EventTarget && EventTarget.prototype && EventTarget.prototype.addEventListener;
const origRemove = EventTarget && EventTarget.prototype && EventTarget.prototype.removeEventListener;
if (!origAdd || !origRemove) return;
EventTarget.prototype.addEventListener = function (type, listener, options) {
if (typeof listener === "function") {
let wrapped = map.get(listener);
if (!wrapped) {
wrapped = function (...args) {
try {
return listener.apply(this, args);
} catch (e) {
console.error("[OA Custom Script] event listener error:", e);
}
};
map.set(listener, wrapped);
}
return origAdd.call(this, type, wrapped, options);
}
return origAdd.call(this, type, listener, options);
};
EventTarget.prototype.removeEventListener = function (type, listener, options) {
if (typeof listener === "function") {
const wrapped = map.get(listener);
return origRemove.call(this, type, wrapped || listener, options);
}
return origRemove.call(this, type, listener, options);
};
},
request,
getToken,
async validate() {
const $vanForm = this.$refs['vanForm']
if ($vanForm) {
const res = await $vanForm.validate()
}
let subFormName = this.fields.filter(i => i.type === 'relation').map(i => i.name)
for (let i = 0;i < subFormName.length;i++) {
const $subVanForm = this.$refs[`subVanForm-${i}`]
if ($subVanForm) {
await $subVanForm.validate()
}
}
},
},
computed: {
linkage() {
return evaluateFieldLinkage({
fields: this.fields,
form: this.form,
writeable: this.writeable,
});
},
},
watch: {
info(newVal) {
let keys = newVal.map(i => i.name)
keys.forEach(key => {
this.form[key] = ''
})
},
originalForm(newVal) {
this.form = deepCopy(newVal)
this.$nextTick(() => this.setupFillFlowTitleWatcher());
},
fields: {
handler() {
this.$nextTick(() => this.setupFillFlowTitleWatcher());
},
deep: true
},
scriptContent(newVal) {
if(newVal) {
try {
// 使用 $nextTick 确保 DOM 已经渲染完成
this.$nextTick(() => {
try {
this.installSafeEventListenerGuard();
new Function(newVal).bind(this)();
} catch (err) {
console.error('脚本执行错误:', err);
// 不阻止页面正常使用,只记录错误
}
});
} catch (err) {
console.error('脚本编译错误:', err);
}
}
},
isShowModal(newVal) {
if(newVal) {
this.zIndex = PopupManager.nextZIndex()
}
},
logs: {
handler: function (newVal) {
if (newVal && newVal instanceof Array && newVal.length > 0) {
this.jointlySignLog = newVal.filter(log => {
try {
JSON.parse(log.data)
return log.is_jointly_sign && /custom_field_id/g.test(log.data)
} catch (e) {
return false
}
})
} else {
this.jointlySignLog = []
}
},
immediate: true
}
},
render(h) {
const authFields = this.fields.map(field => ({
...field,
_readable: this.readable.indexOf(field.id) !== -1,
_writeable: this.writeable.indexOf(field.id) !== -1,
_linkageDisabled: this.linkage.disabledFieldIds.has(Number(field.id)),
}))
const subFields = Array.from(this.subForm).map(i => i[1]?.customModel?.fields).filter(i => i).flat()
if (this.needFlowTitle) {
authFields.unshift({
name: "flow_title",
label: "工作名称",
type: "text",
label_show: 1,
_readable: !this.isFirstNode,
_writeable: this.isFirstNode,
});
}
return h('div',[
h('van-form',{
ref: 'vanForm',
props: {
'validate-first': true,
'scroll-to-error': true
}
},authFields.map(field => formBuilder.bind(this)(this.device, field, h))),
//calendar
(authFields.findIndex(i => i.type === 'date') !== -1 || subFields.findIndex(i => i.type === 'date')) ?
h('van-calendar',{
ref: `vanCalendar`,
props: {
position: 'bottom',
value: this.vanCalendarOption.isShow,
'min-date': this.$moment().subtract('years',10).toDate(),
'max-date': this.$moment().add('years',10).toDate(),
},
on: {
input: e => {
this.$set(this.vanCalendarOption,'isShow',e)
this.$set(this.vanCalendarOption,'isShow',false)
},
confirm: date => {
if(typeof this.vanCalendarOption.originalObj === 'object') {
this.$set(this.vanCalendarOption.originalObj,this.vanCalendarOption.forFormName,moment(date).format('YYYY-MM-DD'))
} else {
this.$set(this.form,this.vanCalendarOption.forFormName,moment(date).format('YYYY-MM-DD'))
}
this.$set(this.vanCalendarOption,'isShow',false)
}
}
}) : '',
//datetimepicker
(authFields.findIndex(i => i.type === 'datetime') !== -1 || subFields.findIndex(i => i.type === 'datetime') !== -1) ?
h('van-popup',{
props: {
value: this.vanTimePickerOption.isShow,
position: 'bottom',
},
on: {
input: e => {
this.$set(this.vanTimePickerOption,'isShow',e)
},
}
},[
h('van-datetime-picker',{
props: {
type: 'datetime',
title: '选择时间'
},
on: {
confirm: time => {
if(typeof this.vanTimePickerOption.originalObj === 'object') {
this.$set(this.vanTimePickerOption.originalObj,this.vanTimePickerOption.forFormName,moment(time).format('YYYY-MM-DD HH:mm:ss'))
} else {
this.$set(this.form,this.vanTimePickerOption.forFormName,moment(time).format('YYYY-MM-DD HH:mm:ss'))
}
this.$set(this.vanTimePickerOption,'isShow',false)
},
cancel: _ => {
this.$set(this.vanTimePickerOption,'isShow',false)
}
}
})
]) : '',
//popup
(authFields.findIndex(i => i.type === 'select') !== -1 || subFields.findIndex(i => i.type === 'select') !== -1) ?
h('van-popup',{
props: {
value: this.vanPopupOption.isShow,
position: 'bottom',
},
on: {
input: e => {
this.$set(this.vanPopupOption,'isShow',e)
},
}
},[
h('van-picker',{
props: {
'show-toolbar': true,
columns: this.vanPopupOption.columns,
'value-key': typeof this.vanPopupOption.columns[0] === 'object' ? 'name' : 'yext'
},
on: {
confirm: value => {
if(typeof this.vanPopupOption.originalObj === 'object') {
this.$set(this.vanPopupOption.originalObj,this.vanPopupOption.forFormName,typeof value === 'object' ? value['id'] : value)
} else {
this.$set(this.form,this.vanPopupOption.forFormName,typeof value === 'object' ? value['id'] : value)
}
this.$set(this.vanPopupOption,'isShow',false)
},
cancel: _ => {
this.$set(this.vanPopupOption,'isShow',false)
}
}
})
]) : '',
// 多选
(authFields.findIndex(i => ['relation-flow','choice','choices'].indexOf(i.type) !== -1) !== -1 || subFields.findIndex(i => ['relation-flow','choice','choices'].indexOf(i.type) !== -1) !== -1) ?
h('van-popup',{
props: {
value: this.multipleSelectOption.isShow,
position: 'bottom',
},
on: {
input: e => {
this.$set(this.multipleSelectOption,'isShow',e)
},
}
},[
h(MobileMultipleSelect,{
props: {
selectDataOpts: this.multipleSelectOption.columns,
multipleLimit: this.multipleSelectOption.multipleLimit,
options: this.multipleSelectOption.options,
outputType: this.multipleSelectOption.outputType,
},
on: {
confirm: value => {
if(typeof this.multipleSelectOption.originalObj === 'object') {
console.log(this.multipleSelectOption.originalObj)
this.$set(this.multipleSelectOption.originalObj,this.multipleSelectOption.forFormName,value)
} else {
this.$set(this.form,this.multipleSelectOption.forFormName,value)
}
this.$set(this.multipleSelectOption,'isShow',false)
},
cancel: _ => {
this.$set(this.multipleSelectOption,'isShow',false)
}
}
})
]) : '',
// 用于编写脚本中弹窗
h('vxe-modal',{
props: {
zIndex: this.zIndex,
value: this.isShowModal,
fullscreen: true,
transfer: true
},
on: {
input: e => {
this.isShowModal = e
}
}
}, [
this.modalRender.bind(this)(h)
])
])
}
}
</script>
<style scoped lang="scss">
</style>