diff --git a/jeecgboot-vue3/src/views/super/online/cgform/CgformCopyList.vue b/jeecgboot-vue3/src/views/super/online/cgform/CgformCopyList.vue new file mode 100644 index 000000000..bab1febdd --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/CgformCopyList.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue new file mode 100644 index 000000000..73b847197 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue @@ -0,0 +1,168 @@ + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineForm.vue new file mode 100644 index 000000000..541aa594e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineForm.vue @@ -0,0 +1,1806 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue new file mode 100644 index 000000000..ca6e15cd4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue new file mode 100644 index 000000000..147a9e9e9 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue @@ -0,0 +1,911 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue new file mode 100644 index 000000000..bcab85cec --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue new file mode 100644 index 000000000..4f2224672 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue new file mode 100644 index 000000000..005ff212f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue @@ -0,0 +1,863 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue new file mode 100644 index 000000000..d3ad61252 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue @@ -0,0 +1,415 @@ + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue new file mode 100644 index 000000000..2ade93904 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue new file mode 100644 index 000000000..c4bf0d23d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue new file mode 100644 index 000000000..6dad39dda --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/ProcessOnlineForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/ProcessOnlineForm.vue new file mode 100644 index 000000000..8a90fd527 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/ProcessOnlineForm.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/TaskOpinionList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/TaskOpinionList.vue new file mode 100644 index 000000000..4657d8d4c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/TaskOpinionList.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts new file mode 100644 index 000000000..c8613b8c4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts @@ -0,0 +1,177 @@ +import InputWidget from './impl/InputWidget'; +import { FormSchema } from '/@/components/Form'; +import DateWidget from './impl/DateWidget'; +import SelectWidget from './impl/SelectWidget'; +import PasswordWidget from './impl/PasswordWidget'; +import FileWidget from './impl/FileWidget'; +import ImageWidget from './impl/ImageWidget'; +import TextAreaWidget from './impl/TextAreaWidget'; +import SelectMultiWidget from './impl/SelectMultiWidget'; +import SelectSearchWidget from './impl/SelectSearchWidget'; +import PopupWidget from './impl/PopupWidget'; +// update-begin--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 +import PopupDictWidget from './impl/PopupDictWidget'; +// update-end--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 +import TreeCategoryWidget from './impl/TreeCategoryWidget'; +import SelectDepartWidget from './impl/SelectDepartWidget'; +import SelectUserWidget from './impl/SelectUserWidget'; +import EditorWidget from './impl/EditorWidget'; +import MarkdownWidget from './impl/MarkdownWidget'; +import PcaWidget from './impl/PcaWidget'; +import AreaLinkage from './impl/AreaLinkage'; +import TreeSelectWidget from './impl/TreeSelectWidget'; +import RadioWidget from './impl/RadioWidget'; +import CheckboxWidget from './impl/CheckboxWidget'; +import SwitchWidget from './impl/SwitchWidget'; +import TimeWidget from './impl/TimeWidget'; +import LinkDownWidget from './impl/LinkDownWidget'; +import SlotWidget from './impl/SlotWidget'; +import NumberWidget from './impl/NumberWidget'; +import LinkTableWidget from './impl/LinkTableWidget' +import LinkTableFieldWidget from './impl/LinkTableFieldWidget' +import LinkTableForQueryWidget from './impl/LinkTableForQueryWidget' +import CascaderPcaForQueryWidget from './impl/CascaderPcaForQueryWidget' +import SelectUser2Widget from './impl/SelectUser2Widget' +import RangeWidget from "./impl/RangeWidget"; + +export default class FormSchemaFactory { + static createFormSchema(key, data, queryItem) { + let view = data.view; + switch (view) { + case 'password': + //2.密码输入框 + return new PasswordWidget(key, data); + case 'list': + //3.下拉框 + return new SelectWidget(key, data); + case 'radio': + // 4. 单选 + return new RadioWidget(key, data); + case 'checkbox': + // 5.多选 + return new CheckboxWidget(key, data); + case 'date': + case 'datetime': + // 6.日期 + // 7.日期时间 + return new DateWidget(key, data, queryItem); + case 'time': + // 8 时间 + return new TimeWidget(key, data); + case 'file': + // 9.文件 + return new FileWidget(key, data); + case 'image': + // 10.图片 + return new ImageWidget(key, data); + case 'textarea': + // 11.多行文本 + return new TextAreaWidget(key, data); + case 'list_multi': + // 12.下拉多选框 + return new SelectMultiWidget(key, data); + case 'sel_search': + // 13.下拉搜索框 + return new SelectSearchWidget(key, data); + case 'popup': + // 14. popup + return new PopupWidget(key, data); + case 'cat_tree': + // 15.分类字典树 + return new TreeCategoryWidget(key, data); + case 'sel_depart': + // 16.部门选择 + return new SelectDepartWidget(key, data); + case 'sel_user': + // 17.用户选择 + return new SelectUserWidget(key, data); + case 'umeditor': + // 18.富文本 + return new EditorWidget(key, data); + case 'markdown': + // 19.MarkDown + return new MarkdownWidget(key, data); + case 'pca': + // 20.省市区 + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件 + // return new PcaWidget(key, data); + return new AreaLinkage(key, data); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件 + case 'link_down': + // 21.联动组件 + return new LinkDownWidget(key, data); + case 'sel_tree': + // 22.自定义树控件 + return new TreeSelectWidget(key, data); + case 'switch': + // 23.开关组件 + return new SwitchWidget(key, data); + case 'link_table': + // 24.关联记录 + return new LinkTableWidget(key, data); + case 'link_table_field': + // 25.他表字段 + return new LinkTableFieldWidget(key, data); + // update-begin--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 + case 'popup_dict': + // 14. popup字典 + return new PopupDictWidget(key, data); + // update-end--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 + case 'slot': + // slot + return new SlotWidget(key, data); + case 'LinkTableForQuery': + return new LinkTableForQueryWidget(key, data); + case 'CascaderPcaForQuery': + return new CascaderPcaForQueryWidget(key, data, queryItem); + case 'select_user2': + return new SelectUser2Widget(key, data); + case 'rangeDate': + case 'rangeTime': + case 'rangeNumber': + return new RangeWidget(key, data); + case 'hidden': + // 隐藏的控件 如分类树的文本 + return new InputWidget(key, data).isHidden(); + default: + if (data.type == 'number') { + return new NumberWidget(key, data); + } else { + //1.普通输入框 + return new InputWidget(key, data); + } + } + } + + static createSlotFormSchema(key, data) { + let slotFs = new SlotWidget(key, data); + let view = data.view; + if ('date' == view) { + slotFs.groupDate(); + } else if ('datetime' == view) { + slotFs.groupDatetime(); + } else if ('time' == view) { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + slotFs.groupTime(); + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + } else { + let type = data.type; + if (type == 'number' || type == 'integer') { + slotFs.groupNumber(); + } + } + return slotFs; + } + + /** + * 表单ID 默认是隐藏的 + */ + static createIdField(): FormSchema { + return { + label: '', + field: 'id', + component: 'Input', + show: false, + }; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts new file mode 100644 index 000000000..44bdb5445 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts @@ -0,0 +1,469 @@ +import {computed, watch} from 'vue' +import { FormSchema, Rule } from '/@/components/Form'; +import { FieldExtends, POP_CONTAINER } from '../../../types/onlineRender'; +import { LABELLENGTH } from '../../../util/constant'; +import {replaceUserInfoByExpression} from "@/utils/common/compUtils"; +/** + * 1.部门选择/用户选择 无:单选配置 + * 控件类 + */ +export default abstract class IFormSchema { + _data; + field: string; + label: string; + labelLength: number; + formRef: any; + hidden: boolean; + order: number; + required: boolean; + onlyValidator: any; + hasChange: boolean; + pre: string; + setFieldsValue: any; + schemaProp: any; + searchForm: boolean; + disabled: boolean; + popContainer: string; + inPopover: boolean; + + constructor(key, data) { + // 考虑不需要存data + this._data = data; + this.field = key; + this.label = data.title; + this.hidden = false; + this.order = data.order || 999; + this.required = false; + this.onlyValidator = ''; + this.setFieldsValue = ''; + this.hasChange = true; + if (key.indexOf('@') > 0) { + this.pre = key.substring(0, key.indexOf('@') + 1); + } else { + this.pre = ''; + } + this.schemaProp = {}; + this.searchForm = false; + this.disabled = false; + this.popContainer = ''; + this.handleWidgetAttr(data); + this.inPopover = false; + this.labelLength = LABELLENGTH; + this.initLabelLength(); + } + + /** + * 获取最终的表单配置项,外面获取调用此方法 + */ + getFormItemSchema(): FormSchema { + let schema = this.getItem(); + this.addDefaultChangeEvent(schema); + return schema; + } + + /** + * 获取表单配置,子类重写此方法 + */ + getItem(): FormSchema { + let fs: FormSchema = { + field: this.field, + label: this.label, + labelLength: this.labelLength, + component: 'Input', + itemProps:{ + labelCol:{ + class: 'online-form-label' + } + } + }; + let rules = this.getRule(); + if (rules.length > 0 && this.onlyValidator) { + fs['rules'] = rules; + } + if (this.hidden === true) { + fs['show'] = false; + } + return fs; + } + + /** + * 设置表单ref + * popup、分类树需要关联设置其他表单值的时候用到 + * @param ref + */ + setFormRef(ref) { + this.formRef = ref; + } + + /** + * 设置表单元素隐藏 + */ + isHidden() { + this.hidden = true; + return this; + } + + /** + * 设置是否必填项 + * @param array + */ + isRequired(array) { + // 子表必填 TODO + if (array && array.length > 0) { + if (array.indexOf(this.field) >= 0) { + this.required = true; + } + } + return this; + } + + /** + * 初始化 label长度 + */ + initLabelLength(){ + let obj = this.getExtendData() + if(obj && obj.labelLength){ + this.labelLength = obj.labelLength; + } + } + + /** + * 获取扩展参数 + */ + getExtendData() { + let extend: FieldExtends = {}; + let { fieldExtendJson } = this._data; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + try { + let json = JSON.parse(fieldExtendJson); + extend = { ...json }; + } catch (e) { + console.error(e); + } + } + } + return extend; + } + + /*** + * 获取和此字段相关的其他字段 需要设置其为隐藏 + */ + getRelatedHideFields(): string[] { + return []; + } + + /** + * placeholder + */ + getPlaceholder(view) { + let text = '请输入'; + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-218】针对组件分别提示对应的校验语 + if ( + [ + 'list', + 'radio', + 'checkbox', + 'date', + 'datetime', + 'time', + 'list_multi', + 'sel_search', + 'popup', + 'cat_tree', + 'sel_depart', + 'sel_user', + 'pca', + 'link_down', + 'sel_tree', + 'switch', + 'link_table', + 'link_table_field', + 'popup_dict', + 'LinkTableForQuery', + 'CascaderPcaForQuery', + 'select_user2', + 'rangeDate', + 'rangeTime', + 'rangeNumber', + ].includes(view) + ) { + text = '请选择'; + } else if (['file', 'image'].includes(view)) { + text = '请上传'; + } + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-218】针对组件分别提示对应的校验语 + return text + this.label; + } + + /** + * 唯一校验 + */ + setOnlyValidateFun(validateFun) { + if (validateFun) { + this.onlyValidator = async (rule, value) => { + let error = await validateFun(rule, value); + if (!error) { + return Promise.resolve(); + } else { + return Promise.reject(error); + } + }; + } + } + + /** + * 获取校验规则 + */ + getRule(): any[] { + let rules: Rule[] = []; + const { view, errorInfo, pattern, type, fieldExtendJson } = this._data; + if (this.required === true) { + let msg = this.getPlaceholder(view); + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-80】扩展参数配置中的校验提示不生效 + if (fieldExtendJson) { + const json = JSON.parse(fieldExtendJson); + if (json.validateError) { + msg = json.validateError; + } + } + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-80】扩展参数配置中的校验提示不生效 + if (errorInfo) { + msg = errorInfo; + } + if (view == 'sel_depart' || view == 'sel_user') { + //如果是部门和用户组件 使用 required:true + this.schemaProp['required'] = true; + // update-begin--author:liaozhiyang---date:20240429---for:【QQYUN-9109】online使用部门和用户组件必填时label前面没有必填的*号 + rules.push({ required: true, message: msg }); + // update-end--author:liaozhiyang---date:20240429---for:【QQYUN-9109】online使用部门和用户组件必填时label前面没有必填的*号 + } else { + rules.push({ required: true, message: msg }); + } + } + if ('sel_user' == view) { + if (pattern === 'only' && this.onlyValidator) { + rules.push({ validator: this.onlyValidator }); + } + } + if ('list' === view || 'radio' === view || 'markdown' === view || 'pca' === view || view.indexOf('sel') >= 0 || 'time' === view) { + return rules; + } + if (view.indexOf('upload') >= 0 || view.indexOf('file') >= 0 || view.indexOf('image') >= 0) { + return rules; + } + if (pattern) { + if (pattern === 'only') { + if (this.onlyValidator) { + rules.push({ validator: this.onlyValidator }); + } + } else if (pattern === 'z') { + if (type == 'number' || type == 'integer') { + // this.onlyInteger=true TODO + } else { + rules.push({ pattern: /^-?\d+$/, message: '请输入整数' }); + } + } else { + let msg = errorInfo || '正则校验失败'; + let reg + try { + reg = new RegExp(pattern); + if (!reg) { + reg = pattern; + } + } catch { + reg = pattern; + } + rules.push({ pattern: reg, message: msg }); + } + } + return rules; + } + + /** + * 添加默认的change事件 + * @param schema + */ + addDefaultChangeEvent(schema) { + if (this.hasChange) { + if (!schema.componentProps) { + schema.componentProps = {}; + } + //update-begin-author:taoyan date:2022-5-24 for: VUEN-1095 只读未控制住 + if (this.disabled == true) { + schema.componentProps.disabled = true; + } + //update-end-author:taoyan date:2022-5-24 for: VUEN-1095 只读未控制住 + if (!schema.componentProps.hasOwnProperty('onChange')) { + schema.componentProps['onChange'] = (value, formData) => { + if (value instanceof Event) { + // 输入框 value是event对象 + value = (value.target as any).value; + } + // 部门组件抛出事件的value是数组 + if (value instanceof Array) { + value = value.join(','); + } + // VUEN-1467【vue3 工作流】流程处理 一对多表单 子表tab切换后,关闭不了 导致整个浏览器无法操作 多操作几次,不一定每次必现--- + if(!this.formRef || !this.formRef.value || !this.formRef.value.$formValueChange){ + console.log('当前表单无法触发change事件,field:'+this.field) + }else{ + this.formRef.value.$formValueChange(this.field, value, formData) + } + }; + // update-begin--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + if (schema.component === 'JPopup') { + schema.componentProps['onPopUpChange'] = schema.componentProps['onChange'] + } + // update-end--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + } + } + // 顺带处理其他的 schemaProp + Object.keys(this.schemaProp).map((k) => { + schema[k] = this.schemaProp[k]; + }); + } + + noChange() { + this.hasChange = false; + } + + updateField(field) { + this.field = field; + } + + /** + * 高级查询 没有表单ref对象 手动设置setFieldValue方法用于 popup设置表单值 + */ + setFunctionForFieldValue(func) { + if (func) { + this.setFieldsValue = func; + } + } + + asSearchForm() { + this.searchForm = true; + } + + /**获取modal作为类下拉组件pop的父容器*/ + getModalAsContainer() { + let ele = this.getPopContainer(); + // update-begin--author:liaozhiyang---date:20231205---for:【QQYUN-7150】online缓存路由打开多页导致下拉类型的组件打不开 + if (ele != 'body') { + const elems = document.querySelectorAll(ele); + if (elems && elems.length > 1) { + const data: HTMLElement[] = []; + elems.forEach((item: HTMLElement) => { + if (!(item.offsetWidth == 0 && item.offsetHeight == 0)) { + data.push(item); + } + }); + if (data.length === 1) { + return data[0]; + } + } + } + // update-end--author:liaozhiyang---date:20231205---for:【QQYUN-7150】online缓存路由打开多页导致下拉类型的组件打不开 + return document.querySelector(ele); + } + + /**区分modal表单和查询表单*/ + getPopContainer() { + if (this.searchForm === true) { + return 'body'; + } else if(this.inPopover === true){ + return `.${this.popContainer}`; + }else if(this.popContainer){ + return `.${this.popContainer} .ant-modal-content` + }else { + return POP_CONTAINER; + } + } + + handleWidgetAttr(data) { + if (data.ui) { + if (data.ui.widgetattrs) { + if (data.ui.widgetattrs.disabled == true) { + this.disabled = true; + } + } + } + } + + /** + * 设置 popContainer + */ + setCustomPopContainer(modalClass){ + this.popContainer = modalClass; + } + + //update-begin-author:taoyan date:2022-8-5 for: 他表字段/关联记录用 + // 获取他表字段的 配置信息 + getLinkFieldInfo():any{ + return ''; + } + + // 1.将他表字段的配置信息设置到关联记录字段上 + setOtherInfo(_arg){ + } + //update-end-author:taoyan date:2022-8-5 for: 他表字段/关联记录用 + + // 表单设计器高级查询用 + isInPopover(){ + this.inPopover = true; + } + + handleDictTableParams() { + if (!this.formRef.value) { + return + } + const dictTable = this._data.dictTable as string + if (!dictTable) { + return + } + const matches = dictTable.match(/\${([^}]+)}/g) + if (!matches || matches.length == 0) { + return + } + // 去除 ${} + const keys = matches.map((item: string) => item.replace('${', '').replace('}', '')) + const values = computed(() => { + const formModel = this.formRef.value.formModel + return keys.map((key) => formModel[key]).join(''); + }) + let timer: ReturnType | null = null; + watch(values, () => { + if (timer) { + clearTimeout(timer) + } + timer = setTimeout(() => { + const formModel = this.formRef.value.formModel + // 替换动态参数,如果有 ${xxx} 则替换为实际值 + let tempDictTable = dictTable.replace(/\${([^}]+)}/g, (_$0, $1) => { + if (formModel[$1] == null) { + return '' + } + return formModel[$1] + }); + this.updateDictTable(tempDictTable) + }, 150) + }, {immediate: true}) + } + + updateDictTable(_dictTable: string) { + console.log('请在子类实现 updateDictTable 方法') + } + + /** + * 获取表字典的编码,可替换系统变量 + * @param dictTable + * @param dictText + * @param dictCode + */ + genDictTableCode(dictTable: string, dictText: string, dictCode: string) { + // 替换系统变量 + dictTable = replaceUserInfoByExpression(dictTable) + return encodeURI(`${dictTable},${dictText},${dictCode}`); + } + +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts new file mode 100644 index 000000000..9aa09d534 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts @@ -0,0 +1,26 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 省市区 + */ +export default class PcaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + // update-begin--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + const extendData: any = this.getExtendData(); + const componentProps: any = {} + if (extendData.displayLevel) { + componentProps.displayLevel = extendData.displayLevel; + componentProps.saveCode = extendData.displayLevel === 'all' ? 'region' : componentProps.displayLevel; + } + // update-end--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + return Object.assign({}, item, { + component: 'JAreaLinkage', + componentProps: { + saveCode: 'region', + ...componentProps, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts new file mode 100644 index 000000000..1249e57fe --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts @@ -0,0 +1,38 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 表单设计器-省市区查询 + */ +export default class CascaderPcaForQueryWidget extends IFormSchema { + + schema: Recordable; + // 省市县联动级别 + areaLevel: number; + // 是否允许更改级别 + allowChangeLevel: boolean; + + constructor(key: string, data: Recordable, queryItem: Recordable) { + super(key, data); + this.schema = data + this.areaLevel = data['areaLevel'] ?? 3; + // 只有等于和不等于才能更改级别 + this.allowChangeLevel = ['eq', 'ne'].includes(queryItem?.rule) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'CascaderPcaInFilter', + componentProps:{ + areaLevel: this.areaLevel, + allowChangeLevel: this.allowChangeLevel, + placeholder: '请选择…', + style: { + width: '100%', + } + } + }); + } + +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts new file mode 100644 index 000000000..15bd04702 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts @@ -0,0 +1,60 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * checkbox + */ +export default class CheckboxWidget extends IFormSchema { + /*title-value*/ + options: any[]; + constructor(key, data) { + super(key, data); + this.options = this.getOptions(data['enum']); + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + options:[], + dictCode: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JCheckbox', + componentProps: { + options: this.options, + triggerChange: true, + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + useDicColor: true, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + }, + }); + } + + getOptions(array) { + if (!array || array.length == 0) { + return []; + } + let arr: any[] = []; + for (let item of array) { + arr.push({ + value: item.value, + label: item.title, + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + color: item.color, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + }); + } + return arr; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts new file mode 100644 index 000000000..dbd0c5b71 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts @@ -0,0 +1,59 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +enum DateFormat { + datetime = 'YYYY-MM-DD HH:mm:ss', + date = 'YYYY-MM-DD', +} + +/** + * 日期、时间 + */ +export default class DateWidget extends IFormSchema { + format: string; + showTime: boolean; + picker: string | undefined; + + allowSelectRange: boolean; + + constructor(key, data, queryItem) { + super(key, data); + this.format = DateFormat[data.view]; + this.showTime = data.view == 'date' ? false : true; + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + let fieldExtendJson = data.fieldExtendJson; + if (data.view == 'date' && fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + this.picker = fieldExtendJson.picker; + } else { + this.picker = undefined; + } + } + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + // 只有等于和不等于才能选择预设范围(今天、昨天、本周等) + this.allowSelectRange = ['eq', 'ne'].includes(queryItem?.rule) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'DatePickerInFilter', + componentProps: { + placeholder: `请选择${this.label}`, + showTime: this.showTime, + valueFormat: this.format, + allowSelectRange: this.allowSelectRange, + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + picker: this.picker, + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + style: { + width: '100%', + }, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts new file mode 100644 index 000000000..a1b634f36 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts @@ -0,0 +1,25 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 富文本 + */ +export default class EditorWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JEditor', + componentProps: { + //update-begin-author:taoyan date:2022-6-1 for: VUEN-1159 第一次加载时,点击第一个输入框,光标会跑到富文本输入框 + options: { + auto_focus: false, + }, + //update-end-author:taoyan date:2022-6-1 for: VUEN-1159 第一次加载时,点击第一个输入框,光标会跑到富文本输入框 + // fileMax:1, + // showImageUpload:false, + // width:"966px", + // height:"200px" + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts new file mode 100644 index 000000000..e0914b10f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts @@ -0,0 +1,26 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 文件 + */ +export default class FileWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JUpload', + componentProps, + }); + } + + getComponentProps() { + let json = this.getExtendData(); + if (json && json.uploadnum) { + return { + maxCount: Number(json.uploadnum), + }; + } + return {}; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts new file mode 100644 index 000000000..d045f0674 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts @@ -0,0 +1,28 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; +import { UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload'; + +/** + * 图片 + */ +export default class ImageWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JUpload', + componentProps, + }); + } + + getComponentProps() { + let props = { + fileType: UploadTypeEnum.image, + }; + let json = this.getExtendData(); + if (json && json.uploadnum) { + props['maxCount'] = Number(json.uploadnum); + } + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts new file mode 100644 index 000000000..211a203a8 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts @@ -0,0 +1,15 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框 + */ +export default class InputWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + if (this.hidden === true) { + item['show'] = false; + } + return item; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts new file mode 100644 index 000000000..3c0f102db --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts @@ -0,0 +1,108 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉联动- 原理是: + * 使用JDictSelectTag组件(2022-03-09测试可行版 后续如有改动请注意) + * 监听表单的change事件,清空下级表单值,并改变props + * 问题在于1.没有code的时候 不需要设置选项 + * 优势在于: 可以不考虑组件位置(但是需要改后台接口) + */ +export default class LinkDownWidget extends IFormSchema { + /*title-value*/ + options: any[]; + next: string; + type: string; + table: string; + txt: string; + store: string; + pidField: string; + idField: string; + origin: boolean; + condition: string; + + constructor(key, data) { + super(key, data); + const { dictTable, dictText, dictCode, pidField, idField, origin, condition } = data; + this.table = dictTable; + this.txt = dictText; + this.store = dictCode; + this.idField = idField; + this.pidField = pidField; + this.origin = origin; + this.condition = condition; + // 都是空数组 + this.options = []; + this.next = data.next || ''; + this.type = data.type; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'OnlineSelectCascade', + componentProps, + }); + } + + getComponentProps() { + let baseProp = { + table: this.table, + txt: this.txt, + store: this.store, + pidField: this.pidField, + idField: this.idField, + origin: this.origin, + pidValue: '-1', + style: { + width: '100%', + }, + onChange: (value) => { + console.log('级联组件-onChange', value); + this.valueChange(value); + }, + onNext: (pidValue) => { + console.log('级联组件-onNext', pidValue); + this.nextOptionsChange(pidValue); + }, + }; + if (this._data.origin === true) { + baseProp['condition'] = this.condition; + } + return baseProp; + } + + async nextOptionsChange(pidValue) { + if (!this.formRef) { + console.error('表单引用找不到'); + return; + } + if (!this.next) { + return; + } + let ref = this.formRef.value; + await ref.updateSchema({ + field: this.next, + componentProps: { + pidValue, + }, + }); + } + + async valueChange(value) { + if (!this.formRef) { + console.error('表单引用找不到'); + return; + } + // update-begin--author:liaozhiyang---date:20240717---for:【TV360X-1856】联动组件最后一个js增强onchang方法不生效 + let ref = this.formRef.value; + // 触发form层级的change事件 + ref.$formValueChange(this.field, value); + if (this.next) { + // 重置value + await ref.setFieldsValue({ [this.next]: '' }); + } + // update-end--author:liaozhiyang---date:20240717---for:【TV360X-1856】联动组件最后一个js增强onchang方法不生效 + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts new file mode 100644 index 000000000..dfbb44d31 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts @@ -0,0 +1,42 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 他表字段 + */ +export default class LinkTableFieldWidget extends IFormSchema { + + dictTable: string; + dictText: string; + + constructor(key, data) { + super(key, data); + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + componentProps: { + readOnly: true, + allowClear: false, + disabled: true, + style:{ + background: 'none', + color:'rgba(0, 0, 0, 0.85)', + border:'none' + } + } + }); + return item; + } + + /** + * 获取他表字段的关联信息 + */ + getLinkFieldInfo(){ + let arr = [this.dictTable, `${this.field},${this.dictText}`]; + return arr; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts new file mode 100644 index 000000000..5248fea16 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts @@ -0,0 +1,35 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 表单设计器-关联记录查询 使用下拉搜索 + */ +export default class LinkTableForQueryWidget extends IFormSchema { + + code: string; + titleField: string; + multi: boolean; + + constructor(key, data) { + super(key, data); + this.code = data['code']; + this.titleField = data['titleField']; + this.multi = data['multi']||false; + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'LinkTableForQuery', + componentProps:{ + code: this.code, + multi: this.multi, + field: this.titleField, + style: { + width: '100%', + } + } + }); + } + +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts new file mode 100644 index 000000000..a77296752 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts @@ -0,0 +1,72 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 关联记录 + */ +export default class LinkTableWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + view: string; + componentString: string; + linkFields: Array; + + constructor(key, data) { + super(key, data); + this.dictTable = data.dictTable; + this.dictText = data.dictText; + this.dictCode = data.dictCode; + this.view = data.view; + this.componentString = '' + this.linkFields = [] + } + + getItem(): FormSchema { + let item = super.getItem(); + const componentProps = this.getComponentProps() + return Object.assign({}, item, { + component: this.componentString, + componentProps: componentProps + }); + } + + getComponentProps() { + let props = { + textField: this.dictText, + tableName: this.dictTable, + valueField: this.dictCode, + }; + let extend = this.getExtendData(); + // 是否多选 + if (extend.multiSelect) { + props['multi'] = true; + }else{ + props['multi'] = false; + } + //封面图 + if (extend.imageField) { + props['imageField'] = extend.imageField; + }else{ + props['imageField'] = '' + } + //显示类型 + if (extend.showType=='select') { + this.componentString = 'LinkTableSelect' + let popContainer = this.getPopContainer(); + props['popContainer'] = popContainer + }else{ + this.componentString = 'LinkTableCard' + } + if(this.linkFields.length>0){ + props['linkFields'] = this.linkFields; + } + return props; + } + + // 他表字段用于翻译 + setOtherInfo(arr){ + // ["表单字段,表字典字段","表单字段,表字典字段"] + this.linkFields = arr; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts new file mode 100644 index 000000000..47ae5ddba --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts @@ -0,0 +1,17 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * markdown + */ +export default class MarkdownWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JMarkdownEditor', + componentProps: { + // height: 300, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts new file mode 100644 index 000000000..a572327f7 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts @@ -0,0 +1,49 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框-数字 + */ +export default class NumberWidget extends IFormSchema { + dbPointLength: number; + + constructor(key, data) { + super(key, data); + this.dbPointLength = data.dbPointLength; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + const safeIntRule = { + validator: (_rule, value) => { + if (value !== null && value !== undefined && value !== '') { + if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) { + return Promise.reject(`数值超出安全范围(${Number.MIN_SAFE_INTEGER}~${Number.MAX_SAFE_INTEGER}),精度将丢失,请重新输入`); + } + } + return Promise.resolve(); + }, + }; + const existingRules = item.rules || []; + return Object.assign({}, item, { + component: 'InputNumber', + componentProps, + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-9790】online中数字类型超出js语言数值范围加提示 + rules: [...existingRules, safeIntRule], + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-9790】online中数字类型超出js语言数值范围加提示 + }); + } + + getComponentProps() { + const props = { + style: { + width: '100%', + }, + }; + if (this.dbPointLength >= 0) { + props['precision'] = this.dbPointLength; + } + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts new file mode 100644 index 000000000..a3e4a5a99 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts @@ -0,0 +1,14 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框- 密码 + */ +export default class PasswordWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'InputPassword', + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts new file mode 100644 index 000000000..403292f7a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts @@ -0,0 +1,14 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 省市区 + */ +export default class PcaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JAreaSelect', + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts new file mode 100644 index 000000000..2161fc211 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts @@ -0,0 +1,43 @@ +import {unref} from 'vue' +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * popupDict + */ +export default class PopupDictWidget extends IFormSchema { + dictCode: string; + multi: boolean; + constructor(key, data) { + super(key, data); + this.dictCode = `${data['code']},${data['destFields']},${data['orgFields']}`; + this.multi = data['popupMulti']; + } + + getItem(): FormSchema { + const item = super.getItem(); + const componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JPopupDict', + componentProps, + }); + } + + getComponentProps() { + const props = { + dictCode: this.dictCode, + multi: this.multi, + }; + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if (this.inPopover) { + props['getContainer'] = () => { + return this.getModalAsContainer(); + }; + } + + // 获取表单数据 + props['getFormValues'] = () => unref(this.formRef).getFieldsValue(); + + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts new file mode 100644 index 000000000..9224897f8 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts @@ -0,0 +1,72 @@ +import {unref} from 'vue' +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * popup + */ +export default class PopupWidget extends IFormSchema { + code: string; + multi: boolean; + fieldConfig: any[]; + + constructor(key, data) { + super(key, data); + this.code = data['code']; + this.multi = data['popupMulti']; + this.fieldConfig = this.getFieldConfig(data); + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JPopup', + componentProps, + }); + } + + getComponentProps() { + let props = { + code: this.code, + multi: this.multi, + fieldConfig: this.fieldConfig, + }; + if (this.formRef) { + props['formElRef'] = this.formRef; + } else { + props['setFieldsValue'] = this.setFieldsValue; + } + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + + // 获取表单数据 + props['getFormValues'] = () => unref(this.formRef).getFieldsValue(); + + return props; + } + + getFieldConfig(data) { + let { destFields, orgFields, dictText } = data; + if (!destFields || destFields.length == 0) { + return []; + } + let arr1 = destFields.split(','); + let arr2 = orgFields.split(','); + let arr3 = dictText ? dictText.split(',') : null; + let config: any[] = []; + const pre = this.pre; + for (let i = 0; i < arr1.length; i++) { + config.push({ + target: pre + arr1[i], + source: arr2[i], + label: arr3 ? arr3[i] : void 0, + }); + } + return config; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts new file mode 100644 index 000000000..7b5d7a059 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts @@ -0,0 +1,66 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * radio + * 没有现成的 只能借用JDictSelectTag + */ +export default class RadioWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + + constructor(key, data) { + super(key, data); + // 可以从这个里面取 但是换成临时加载的 + //this.options = this.getOptions(data['enum']) + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JDictSelectTag', + componentProps, + }); + } + + getComponentProps() { + if (!this.dictTable && !this.dictCode) { + // 字典表 和 字典 都没填数据 + return {}; + } else { + if (!this.dictTable) { + return { + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + useDicColor: true, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + dictCode: this.dictCode, + type: 'radio', + }; + } else { + return { + dictCode: this.genDictTableCode(this.dictTable, this.dictText, this.dictCode), + type: 'radio', + }; + } + } + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts new file mode 100644 index 000000000..ebc3179e0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts @@ -0,0 +1,43 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 日期、时间、数值-范围 + */ +export default class RangeWidget extends IFormSchema { + + componentType: string; + datetime: boolean; + format: string; + + constructor(key, data) { + super(key, data); + let view = data.view; + this.format = data.format; + this.datetime = false; + if('rangeNumber'===view){ + this.componentType = 'JRangeNumber' + }else if('rangeTime'===view){ + this.componentType = 'RangeTime' + }else{ + this.componentType = 'RangeDate' + if(data.datetime===true){ + this.datetime = true; + } + } + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: this.componentType, + componentProps: { + datetime: this.datetime, + format: this.format, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts new file mode 100644 index 000000000..faedd55f3 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts @@ -0,0 +1,49 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 部门选择 + */ +export default class SelectDepartWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectDept', + componentProps, + }); + } + + getComponentProps() { + let extend = this.getExtendData(); + let props = { + // update-begin--author:liaozhiyang---date:20260414---for:【QQYUN-9801】修复online点击展开全部,树节点没全部展开 + sync: false, + // update-end--author:liaozhiyang---date:20260414---for:【QQYUN-9801】修复online点击展开全部,树节点没全部展开 + checkStrictly: true, + showButton: false, + }; + if (extend.text) { + props['labelKey'] = extend.text; + } + if (extend.store) { + props['rowKey'] = extend.store; + } + if (extend.multiSelect === false) { + props['multiple'] = false; + } + + if (extend.multiSelect === true) { + props['multiple'] = true; + } + props['maxTagCount'] = 3; + + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts new file mode 100644 index 000000000..ab39279ff --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts @@ -0,0 +1,67 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉多选框 + */ +export default class SelectMultiWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + + constructor(key, data) { + super(key, data); + // 可以从这个里面取 但是换成临时加载的 + //this.options = this.getOptions(data['enum']) + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode) + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectMultiple', + componentProps: componentProps, + }); + } + + getComponentProps() { + if (!this.dictTable && !this.dictCode) { + // 字典表 和 字典 都没填数据 + return {}; + } else { + let props = {}; + if (!this.dictTable) { + props['dictCode'] = this.dictCode; + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + props['useDicColor'] = true; + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + } else { + props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode); + // update-begin--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + // 默认滚动加载字典表数据 + props['scrollLoad'] = true; + // update-end--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + } + props['triggerChange'] = true; + props['popContainer'] = this.getPopContainer(); + return props; + } + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts new file mode 100644 index 000000000..78691bcb4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts @@ -0,0 +1,53 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉搜索 + */ +export default class SelectSearchWidget extends IFormSchema { + dict: string; + type: number; + constructor(key, data) { + super(key, data); + if (data.dictTable && data.dictText && data.dictCode) { + // 字典表 + this.dict = this.genDictTableCode(data.dictTable, data.dictText, data.dictCode); + this.type = 1; + } else { + // 数据字典 + this.dict = encodeURI(`${data.dictCode}`); + this.type = 0; + } + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dict: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let popContainer = this.getPopContainer(); + return Object.assign({}, item, { + component: 'JSearchSelect', + componentProps: { + dict: this.dict, + pageSize: 10, + // update-begin--author:liaozhiyang---date:20240628---for:【issues/6336】online下拉搜索框设置数据字典编辑弹窗报错 + async: this.type ? true : false, + // update-end--author:liaozhiyang---date:20240628---for:【issues/6336】online下拉搜索框设置数据字典编辑弹窗报错 + useDicColor: true, + popContainer: popContainer, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts new file mode 100644 index 000000000..66a96b62f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts @@ -0,0 +1,44 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 用户选择 + */ +export default class SelectUser2Widget extends IFormSchema { + + multi: boolean; + store: string; + query: boolean; + + constructor(key, data) { + super(key, data); + this.multi = data.multi === true ? true : false; + this.store = data.store||''; + // 是否是查询条件,查询条件显示为输入框样式 + this.query = data.query||false; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'UserSelect', + componentProps + }); + } + + getComponentProps() { + let props = { + multi: this.multi, + store: this.store, + query: this.query, + } + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts new file mode 100644 index 000000000..0d02b7d7a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts @@ -0,0 +1,54 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 用户选择 + */ +export default class SelectUserWidget extends IFormSchema { + + showButton: boolean; + + constructor(key, data) { + super(key, data); + this.showButton = data.showButton === false ? false : true; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectUser', + componentProps, + }); + } + + getComponentProps() { + let extend = this.getExtendData(); + let props = { + showSelected: false, + allowClear: true, + isRadioSelection: false, + showButton: this.showButton + }; + if (extend.text) { + props['labelKey'] = extend.text; + } + if (extend.store) { + props['rowKey'] = extend.store; + } + if (extend.multiSelect === false) { + //props['multiple'] = false + props['isRadioSelection'] = true; + } + props['maxTagCount'] = 3; + + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + + return props; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts new file mode 100644 index 000000000..f9cbe14f1 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts @@ -0,0 +1,150 @@ +import { FormSchema } from '/@/components/Form'; +import { h } from 'vue'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉框 + * //待处理: 表字典取数据可以考虑传参前端再请求 + */ +export default class SelectWidget extends IFormSchema { + schema: Recordable; + /*title-value*/ + options: any[]; + dictTable: string; + dictText: string; + dictCode: string; + multi: boolean; + + constructor(key, data) { + super(key, data); + this.schema = data; + // 静态数据选项(enum)转换为 JSelectSingle 所需格式 + this.options = data['enum'] ? this.getOptions(data['enum'], '') : []; + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + this.multi = data['multi'] || false; + } + + getItem(): FormSchema { + let item = super.getItem(); + let component = this.getFormComponent() + let componentProps = this.getComponentProps() + return Object.assign({}, item, { + component, + componentProps, + renderComponentContent: this.getSlots(componentProps), + }); + } + + getFormComponent(){ + // update-begin--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + // if(this.options.length>0){ + // return 'Select' + // }else{ + // return 'JDictSelectTag' + // } + return 'JSelectSingle' + // update-end--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode), + } + })) + } + + getComponentProps() { + let mode = this.multi===true?'multiple':'combobox' + let props: any = { + allowClear: true, + mode, + style: { + width: '100%', + }, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + // update-begin--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + // 下拉框展开/关闭的回调 + // onDropdownVisibleChange: (visible: boolean)=> { + // if (visible && typeof this.schema.updateOptions === 'function') { + // this.schema.updateOptions() + // } + // }, + // update-end--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + } + // update-begin--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + if (!this.dictTable) { + props['dictCode'] = this.dictCode; + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + props['useDicColor'] = true; + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + // 静态数据(无 dictCode)时,将 enum 选项直接传给组件 + if (!this.dictCode && this.options.length > 0) { + props['options'] = this.options; + } + } else { + props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode); + props['scrollLoad'] = true; + delete props.onDropdownVisibleChange; + } + // update-end--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + return props + } + + getSlots(componentProps: Recordable) { + const {useDicColor} = componentProps; + return function () { + return { + option(option: Recordable) { + const style: Recordable = {}; + if (useDicColor && option.color) { + style.color = '#fff'; + style.height = '20px'; + style.lineHeight = '20px'; + style.padding = '0 6px'; + style.fontSize = '12px'; + style.borderRadius = '8px'; + style.backgroundColor = option.color; + style.display = 'inline-block'; + } + return h('span', { + style, + }, option.text || option.label); + }, + }; + } + } + + getOptions(array, type) { + if (!array || array.length == 0) { + return []; + } + let isNum = 'number' == type; + let arr: any[] = []; + for (let item of array) { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9359】加强判断,防止数据有null报错 + if (item == null) break; + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9359】加强判断,防止数据有null报错 + let value = item.value; + if(isNum){ + value = parseInt(value) + } + arr.push({ + ...item, + value, + label: item.title, + }); + } + return arr; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts new file mode 100644 index 000000000..086360400 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts @@ -0,0 +1,68 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * slot + */ +export default class SlotWidget extends IFormSchema { + slot: string; + picker: string | undefined; + precision: number | undefined; + + constructor(key, data) { + super(key, data); + this.slot = ''; + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + let fieldExtendJson = data.fieldExtendJson; + if (data.view == 'date' && fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + this.picker = fieldExtendJson.picker; + } else { + this.picker = undefined; + } + } + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + // update-begin--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + this.precision = data.dbPointLength; + // update-end--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + } + + getItem(): FormSchema { + let item = super.getItem(); + let slot = this.slot; + const componentProps: any = {}; + this.picker && (componentProps.picker = this.picker); + // update-begin--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + this.precision && (componentProps.precision = this.precision); + // update-end--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + return Object.assign({}, item, { + slot, + componentProps, + }); + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + } + + groupDate() { + this.slot = 'groupDate'; + return this; + } + + groupDatetime() { + this.slot = 'groupDatetime'; + return this; + } + + groupTime() { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + this.slot = 'groupTime'; + return this; + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + } + + groupNumber() { + this.slot = 'groupNumber'; + return this; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts new file mode 100644 index 000000000..92204b7b8 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts @@ -0,0 +1,43 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; +import { isArray, isObject } from '/@/utils/is'; + +/** + * 开关 + */ +export default class SwitchWidget extends IFormSchema { + constructor(key, data) { + super(key, data); + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-54】开关只读未生效 + // this.hasChange = false; + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-54】开关只读未生效 + } + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSwitch', + componentProps, + }); + } + + getComponentProps() { + let { fieldExtendJson } = this._data; + let options = ['Y', 'N']; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-25】扩展参数配置中增加开关是否选项配置 + const json = JSON.parse(fieldExtendJson); + if (isArray(json) && json.length == 2) { + options = json; + } else if (isObject(json) && isArray(json.switchOptions)) { + options = json.switchOptions; + } + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-25】扩展参数配置中增加开关是否选项配置 + } + } + return { + options, + }; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts new file mode 100644 index 000000000..a280565a1 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts @@ -0,0 +1,19 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框-textarea + */ +export default class TextAreaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'InputTextArea', + componentProps:{ + autoSize : { + minRows: 4, maxRows: 10 + } + } + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts new file mode 100644 index 000000000..66a5ad7ca --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts @@ -0,0 +1,24 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 日期、时间 + */ +export default class TimeWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'TimePicker', + componentProps: { + placeholder: `请选择${this.label}`, + valueFormat: 'HH:mm:ss', + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + style: { + width: '100%', + }, + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts new file mode 100644 index 000000000..e159ecdb4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts @@ -0,0 +1,73 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 分类字典 + */ +export default class TreeCategoryWidget extends IFormSchema { + pid: string; + multi: boolean; + textField: string; + pcode: string; + + constructor(key, data) { + super(key, data); + this.multi = false; + this.pid = data['pidValue']; + this.pcode = data['pcode']; + this.textField = data['textField']; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + componentProps, + component: 'JCategorySelect', + }); + } + + /** + * 1. 不带返回值的 + * 2. 带文本返回的 + */ + getComponentProps() { + // VUEN-1049 分类字典保存后,列表不展示 单表 树表 --> 配错编码后,表单界面还显示分类字典选项,可直接不显示字典选项 + let param = { + placeholder: '请选择' + this.label + } + if(this.pcode){ + param['pcode'] = this.pcode; + }else{ + let pidValue = this.pid || 'EMPTY_PID'; + param['pid'] = pidValue; + } + if (!this.textField) { + return { + multiple: this.multi, + ...param + }; + } else { + return { + loadTriggleChange: true, + multiple: this.multi, + ...param, + back: this.textField, + onChange: (val, backVal) => { + if (this.formRef) { + this.formRef.value.setFieldsValue(backVal); + this.formRef.value.$formValueChange(this.field, val) + } + }, + }; + } + } + + getRelatedHideFields(): string[] { + let arr: string[] = []; + if (this.textField) { + arr.push(this.textField); + } + return arr; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts new file mode 100644 index 000000000..2a97a0df7 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts @@ -0,0 +1,41 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 自定义树 + */ +export default class TreeSelectWidget extends IFormSchema { + /*表名、显示字段、存储字段*/ + dict: string; + /*父级ID的字段名*/ + pidField: string; + /*父级ID的字段值*/ + pidValue: string; + /*是否有子节点*/ + hasChildField: string; + + constructor(key, data) { + super(key, data); + this.dict = data['dict']; + this.pidField = data['pidField']; + this.pidValue = data['pidValue']; + // update-begin--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + this.hasChildField = data['hasChildField']; + // update-end--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JTreeSelect', + componentProps: { + dict: this.dict, + pidField: this.pidField, + pidValue: this.pidValue, + // update-begin--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + hasChildField: this.hasChildField, + // update-end--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + }, + }); + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/index.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/index.ts new file mode 100644 index 000000000..f0a93d516 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/index.ts @@ -0,0 +1,13 @@ +import type { App } from 'vue'; +import {defineAsyncComponent} from 'vue' +const SuperQuery = defineAsyncComponent(() => import('./superquery/SuperQuery.vue')) +const JOnlineSearchSelect = defineAsyncComponent(() => import('./JOnlineSearchSelect.vue')) + +export const registerOnlineComp = { + install(app: App) { + app.component('JOnlineSearchSelect', JOnlineSearchSelect); + app.component('SuperQuery', SuperQuery); + + console.log("---初始化---, 全局注册Online部分组件--------------") + }, +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue new file mode 100644 index 000000000..14fdea22f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue @@ -0,0 +1,716 @@ + + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue new file mode 100644 index 000000000..59073a24b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue @@ -0,0 +1,108 @@ + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts new file mode 100644 index 000000000..4b710200d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts @@ -0,0 +1,703 @@ +import { useModalInner } from '/@/components/Modal'; +import { randomString } from '/@/utils/common/compUtils'; +import { reactive, ref, toRaw, watch } from 'vue'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { createLocalStorage } from '/@/utils/cache'; +import { useRoute } from 'vue-router'; +import FormSchemaFactory from '../factory/FormSchemaFactory'; +import {useExtendComponent} from '../../../hooks/auto/useExtendComponent' +import { cloneDeep } from 'lodash-es'; +/** + * 表单类型转换成查询类型 + * 普通查询和高级查询组件区别 :高级查询不支持联动组件 + */ +const FORM_VIEW_TO_QUERY_VIEW = { + "password": "text", + "file": "text", + "image": "text", + "textarea": "text", + "umeditor": "text", + "markdown": "text", + "checkbox": "list_multi", + "radio": "list", +} + +// 查询条件存储编码前缀 +const SAVE_CODE_PRE = 'JSuperQuerySaved_'; + +/** + * 查询项 + * */ +interface SuperQueryItem { + field: string|undefined; + rule: string|undefined; + val: string|number; + key: string; + // 解决inputNumber组件对不齐样式问题 + curLineAlign: string | undefined; + fileType: string; + // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 + view: string; + // 最先原始的组件类型;view字段可能会被改变 + originView?: string; + // update-end--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 +} +/** + * 查询项-第一个控件树model + * */ +interface TreeModel { + title: string, + value: string, + isLeaf?: boolean, + disabled?: boolean, + children?: TreeModel[], + order?: number, + fieldType?: string; + // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 + view: string; + originView?: string; + // update-end--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 +} + +/** + * 查询信息保存结构 + * */ +interface SaveModel{ + title: string, + content: string, + type: string, +} + +export function useSuperQuery(props){ + // 添加表单组件 + const {linkTableCard2Select} = useExtendComponent(); + + const { createMessage: $message } = useMessage(); + /** 表单ref*/ + const formRef = ref(); + + /** 数据*/ + const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({ + values: [], + }); + /** and/or */ + const matchType = ref('and'); + + // 保存查询弹窗确定按钮loading状态 + const saveModalLoading = ref(false); + // 弹框显示 + const [registerModal, {setModalProps}] = useModalInner(() => { + setModalProps({confirmLoading: false}); + }) + + // 高级查询类型不支持联动组件,需要额外设置联动组件的view为text + const view2QueryViewMap = Object.assign({}, {"link_down":"text"}, FORM_VIEW_TO_QUERY_VIEW) + + /** + * 确认按钮事件 + */ + function handleSubmit() { + console.log('handleSubmit', dynamicRowValues.values) + } + + /** + * 关闭按钮事件 + */ + function handleCancel() { + //closeModal(); + } + + /** + * val组件赋值 + */ + function setFormModel(key: string, value: any, item: any) { + console.log('setFormModel', key, value) + // formModel[key] = value; + item['val'] = value; + } + + // 字段-Properties + const fieldProperties = ref({}) + // 字段-左侧查询项-树控件数据 + const fieldTreeData = ref([]) + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + const filterComponent = (data) => { + const { properties = {} } = data; + Object.entries(properties).forEach(([field, value]) => { + if (value.view === 'table') { + filterComponent(value); + } + if (['link_down'].includes(value.originView || value.view)) { + delete properties[field]; + } + }); + }; + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + /** + * 初始化数据-最开始的方法 + * 1.获取 表名@字段名-->配置 这样的一个map + * 2.获取树形结构的数据 显示:文本; 存储:表名@字段名 + * 当树改变时,及时获取配置更新表单 + * @param json + */ + function init(json) { + console.log('=============') + console.log('=============', json) + console.log('=============') + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + filterComponent(json); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + let { allFields, treeData } = getAllFields(json); + fieldProperties.value = allFields; + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1005】有子表时结构化主表且超长省略 + const properties = json.properties ?? {}; + const subTable: string[] = []; + const tableName = json.table; + Object.entries(properties).forEach(([key, value]: [string, any]) => { + if (value.view === 'table') { + subTable.push(key); + } + }); + if (subTable.length) { + let arr: TreeModel[] = []; + arr = treeData.filter((item) => !subTable.includes(item.value)); + for (let i = 0, len = treeData.length; i < len; i++) { + const item = treeData[i]; + if (!subTable.includes(item.value)) { + treeData.splice(i, 1); + i--; + len--; + } + } + treeData.unshift({ title: '主表', value: tableName, disabled: true, order: 200, children: arr, view: 'table' }); + } + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1005】有子表时结构化主表且超长省略 + fieldTreeData.value = treeData; + } + + /** + * 左侧查询项 添加一行 + * @param index + */ + function addOne(index) { + let item = { + field: undefined, + rule: 'eq', + val:'', + key: randomString(16) + } + if(index===false){ + // 重置后需要调用 + dynamicRowValues.values = [] + dynamicRowValues.values.push(item) + }else if(index===true){ + // 打开弹框是需要调用 + if(dynamicRowValues.values.length==0){ + dynamicRowValues.values.push(item) + } + }else{ + // 其余就是 正常的点击加号增加行 + dynamicRowValues.values.splice(++index, 0, item) + } + } + + /** + * 左侧查询项 删除一行 + */ + function removeOne(item: SuperQueryItem) { + let arr = toRaw(dynamicRowValues.values); + let index = -1; + for(let i=0;i{ + item['val'] = values[item.field] + } + temp.setFunctionForFieldValue(setFieldValue) + let schema = temp.getFormItemSchema() + //schema['valueField'] = 'val' + // 特殊规则,需要禁用组件 + // 为空、不为空 + if (['empty', 'not_empty'].includes(item.rule)) { + schema.componentProps = { ...schema.componentProps, disabled: true }; + } + linkTableCard2Select(schema); + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-389】普通查询关联记录去掉编辑按钮 + if (schema.component === 'LinkTableSelect') { + let componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, editBtnShow: false }; + } + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-389】普通查询关联记录去掉编辑按钮 + // update-begin--author:liaozhiyang---date:20231219---for:【QQYUN-7640】高级查询数字组件会偏移 + if (schema && schema.component === 'InputNumber') { + item.curLineAlign = 'start'; + } + // update-end--author:liaozhiyang---date:20231219---for:【QQYUN-7640】高级查询数字组件会偏移 + // update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8229】高级选择自定义树下拉显示不全 + if (schema?.component === 'JTreeSelect') { + let componentProps: any = schema.componentProps; + if (componentProps) { + componentProps.getPopupContainer = () => document.body + } else { + schema.componentProps = { getPopupContainer: () => document.body } + }; + } + // update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8229】高级选择自定义树下拉显示不全 + // update-begin--author:liaozhiyang---date:20240529---for:【TV360X-499】高级查询开关组件换成下拉,用户组件不显示按钮 + if (schema?.component === 'JSwitch') { + const componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, query: true }; + } + if (schema?.component === 'JSelectUser') { + const componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, showButton: false }; + } + // update-end--author:liaozhiyang---date:20240529---for:【TV360X-499】高级查询开关换成下拉,用户组件不显示按钮 + return schema + } + + /*-----------------------右侧保存信息相关-begin---------------------------*/ + + /** + * 右侧树 的 数据 + */ + const saveTreeData = ref('') + // 本地缓存 + const $ls = createLocalStorage(); + //需要保存的信息(一条) + const saveInfo = reactive({ + visible: false, + title: '', + content: '', + saveCode: '' + }); + //按钮loading + const loading = ref(false) + + // 当前页面路由 + const route = useRoute(); + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + watch(props.saveSearchData, () => { + currentPageSavedArray.value = props.saveSearchData; + }); + } else { + // 监听路由信息,路由发生改变,则重新获取保存的查询信息-->currentPageSavedArray + watch(()=>route.fullPath, (val)=>{ + console.log('fullpath', val); + initSaveQueryInfoCode(); + }); + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + + // 当前页面存储的 查询信息 + const currentPageSavedArray = ref([]); + // 监听当前页面是否有新的数据保存了,然后更新右侧数据->saveTreeData + watch(()=>currentPageSavedArray.value, (val)=>{ + let temp:any[] = [] + if(val && val.length>0){ + val.map(item=>{ + let key = randomString(16) + temp.push({ + title: item.title, + slots: { icon: 'custom' }, + value: key + }) + }) + } + saveTreeData.value = temp + }, {immediate:true, deep: true}) + + + // 重新获取保存的查询信息 + function initSaveQueryInfoCode(){ + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + currentPageSavedArray.value = cloneDeep(props.saveSearchData); + } else { + let code = SAVE_CODE_PRE + route.fullPath; + saveInfo.saveCode = code; + let list = $ls.get(code); + if(list && list instanceof Array){ + currentPageSavedArray.value = list + } + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + } + + // 执行一次 获取保存的查询信息 + initSaveQueryInfoCode(); + + /** + * 保存按钮事件 + */ + function handleSave(){ + // 获取实际数据转成字符串 + let fieldArray = getQueryInfo(); + if(!fieldArray){ + $message.warning('空条件不能保存') + return; + } + let content = JSON.stringify(fieldArray) + openSaveInfoModal(content) + } + + // 输入保存标题 弹框显示 + function openSaveInfoModal(content){ + saveInfo.visible = true; + saveInfo.title = ''; + saveInfo.content = content + } + + /** + * 确认保存查询信息 + */ + function doSaveQueryInfo(){ + let { title, content, saveCode } = saveInfo; + let index = getTitleIndex(title); + const saveSearchCondition = (type) => { + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + const curPageSave: any = cloneDeep(currentPageSavedArray.value); + saveModalLoading.value = true; + if (type) { + // 覆盖已有 + curPageSave.splice(index, 1, { + content, + title, + type: matchType.value, + }); + } else { + curPageSave.push({ + content, + title, + type: matchType.value, + }); + } + const run = () => { + saveInfo.visible = false; + $message.success('保存成功'); + currentPageSavedArray.value = curPageSave; + saveModalLoading.value = false; + }; + if (props.isCustomSave) { + props + .save(curPageSave, 0) + .then(() => { + run(); + }) + .catch((err) => { + saveModalLoading.value = false; + }); + } else { + // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8357】高级查询保存的查询条件缓存改成一个月 + const expire = 60 * 60 * 24 * 30; + // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8357】高级查询保存的查询条件缓存改成一个月 + $ls.set(saveCode, curPageSave, expire); + run(); + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + }; + if (index >= 0) { + // 已存在是否覆盖 + Modal.confirm({ + title: '提示', + content: `${title} 已存在,是否覆盖?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + saveSearchCondition(1); + }, + }); + } else { + saveSearchCondition(0); + } + } + + // 根据填入的 title找本地存储的信息,如果有需要询问是否覆盖 + function getTitleIndex(title){ + let savedArray = currentPageSavedArray.value + let index = -1; + for(let i=0;i v) + let tempVal:any = toRaw(item.val) + if(tempVal instanceof Array){ + tempVal = tempVal.map(v => formatValue(v)).join(',') + } else { + tempVal = formatValue(tempVal) + } + let fieldName = getRealFieldName(item) + let obj = { + field: fieldName, + rule: item.rule, + val: tempVal, + fileType: item.fileType , + }; + if(isEmit===true){ + //如果当前数据用于emit事件,需要设置dbtype和type + let prop = fieldProps[item.field] + if(prop){ + obj['type'] = prop.view + obj['dbType'] = prop.type + } + } + fieldArray.push(obj) + } + } + if(fieldArray.length==0){ + return false; + } + return fieldArray + } + + //update-begin-author:taoyan date:2022-5-31 for: VUEN-1148 主子联动下,高级查询查子表数据,无效 + /** + * 高级查询参数 字段名 + * 获取后台需要的 字段名格式:表名,字段名 + * @param item + */ + function getRealFieldName(item){ + let fieldName = item.field + if(fieldName.indexOf('@')>0){ + fieldName = fieldName.replace('@', ',') + } + return fieldName; + } + //update-end-author:taoyan date:2022-5-31 for: VUEN-1148 主子联动下,高级查询查子表数据,无效 + + /** + * 右侧数据 点击事件,重新将数据显示到左侧 + * @param key + * @param node + */ + function handleTreeSelect(key, {node}){ + console.log(key, node) + let title = node.dataRef.title + let arr = currentPageSavedArray.value.filter(item=>item.title==title) + if(arr && arr.length>0){ + // 拿到数据渲染 + let { content, type } = arr[0] + let data = JSON.parse(content) + let rowsValues: SuperQueryItem[] = [] + for(let item of data){ + // update-begin--author:liaozhiyang---date:20240108---for:【issues/962】高级查询保存的查询是子表,下次查询不出结果 + item.field = item.field.replace(',','@'); + // update-end--author:liaozhiyang---date:20240108---for:【issues/962】高级查询保存的查询是子表,下次查询不出结果 + rowsValues.push(Object.assign({}, {key: randomString(16)}, item)) + } + dynamicRowValues.values = rowsValues + matchType.value = type + } + } + + /** + * 右侧数据 删除事件 + */ + function handleRemoveSaveInfo(title){ + console.log(title) + let index = getTitleIndex(title) + if(index>=0){ + // update-begin--author:liaozhiyang---date:20240513---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + const curPageSave = cloneDeep(currentPageSavedArray.value); + curPageSave.splice(index, 1); + props + .save(curPageSave, 1) + .then(() => { + currentPageSavedArray.value = curPageSave; + }) + .catch((err) => { + console.log(`删除是吧~,${err}`); + }); + } else { + currentPageSavedArray.value.splice(index, 1); + $ls.set(saveInfo.saveCode, currentPageSavedArray.value); + } + // update-end--author:liaozhiyang---date:20240513---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + } + } + + /*-----------------------右侧保存信息相关-end---------------------------*/ + + // 获取所有字段配置信息 + function getAllFields(properties){ + // 获取所有配置 查询字段 是否联合查询 + // const {properties, table, title } = json; + let allFields = {} + let order = 1; + let treeData:TreeModel[] = [] + /* let mainNode:TreeModel = { + title, + value: table, + disabled: true, + children: [] + };*/ + //treeData.push(mainNode) + if(properties.properties){ + properties = properties.properties + } + Object.keys(properties).map(field=>{ + let item = properties[field] + if(item.view == 'table'){ + // 子表字段 + // 联合查询开启才需要子表字段作为查询条件 + let subProps = item['properties'] || item['fields'] + let subTableOrder = order * 100; + let subNode:TreeModel = { + title: item.title, + value: field, + disabled: true, + children: [], + order: subTableOrder, + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: item.type, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + } + Object.keys(subProps).map(subField=>{ + let subItem = subProps[subField]; + // 保证排序统一 + subItem['order'] = subTableOrder + subItem['order'] + let subFieldKey = field+'@'+subField + allFields[subFieldKey] = subItem + subNode.children!.push({ + title: subItem.title, + value: subFieldKey, + isLeaf: true, + order: subItem['order'], + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: subItem.type, + view: subItem.view, + originView: subItem.view, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + }) + }); + orderField(subNode); + treeData.push(subNode); + order++; + }else{ + // 主表字段 + //let fieldKey = table+'@'+field + let fieldKey = field + allFields[fieldKey] = item + treeData.push({ + title: item.title, + value: fieldKey, + isLeaf: true, + order: item.order, + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: item.type, + view: item.view, + originView: item.view, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + }); + } + }); + orderField(treeData); + return {allFields, treeData} + } + + //根据字段的order重新排序 + function orderField(data){ + let arr = data.children || data; + arr.sort(function (a, b) { + return a.order - b.order + }); + } + + function initDefaultValues(values) { + const { params, matchType } = values + if(params){ + let rowsValues: SuperQueryItem[] = [] + for(let item of params){ + rowsValues.push(Object.assign({}, {key: randomString(16)}, item)) + } + dynamicRowValues.values = rowsValues + matchType.value = matchType + } + } + + return { + formRef, + init, + dynamicRowValues, + matchType, + registerModal, + handleSubmit, + handleCancel, + handleSave, + doSaveQueryInfo, + saveInfo, + saveTreeData, + handleRemoveSaveInfo, + handleTreeSelect, + fieldTreeData, + addOne, + removeOne, + setFormModel, + getSchema, + loading, + getQueryInfo, + initDefaultValues, + saveModalLoading, + fieldProperties, + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoList.vue new file mode 100644 index 000000000..1dec48116 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoList.vue @@ -0,0 +1,532 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue new file mode 100644 index 000000000..6fb4b50af --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue new file mode 100644 index 000000000..6ef43417a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue new file mode 100644 index 000000000..382f25f5b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue new file mode 100644 index 000000000..82107c7ca --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue new file mode 100644 index 000000000..d3f37da4e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue new file mode 100644 index 000000000..4f7244498 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue new file mode 100644 index 000000000..05765ab28 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue new file mode 100644 index 000000000..1333133a0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue @@ -0,0 +1,537 @@ + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue new file mode 100644 index 000000000..fdfb5ffeb --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue new file mode 100644 index 000000000..18934cc2e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue new file mode 100644 index 000000000..b41dfa717 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue @@ -0,0 +1,458 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue new file mode 100644 index 000000000..f44586377 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue @@ -0,0 +1,395 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue new file mode 100644 index 000000000..e9bd2ca84 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue new file mode 100644 index 000000000..28b4a76df --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue new file mode 100644 index 000000000..dd32af5b1 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue @@ -0,0 +1,1284 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue new file mode 100644 index 000000000..6f11fb8fa --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue b/jeecgboot-vue3/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue new file mode 100644 index 000000000..e54f940a4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue @@ -0,0 +1,588 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/cgform.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/cgform.api.ts new file mode 100644 index 000000000..cdabae979 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/cgform.api.ts @@ -0,0 +1,83 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/cgform/head/list', + delete = '/online/cgform/head/delete', + deleteBatch = '/online/cgform/head/deleteBatch', + databaseSync = '/online/cgform/api/doDbSynch', + removeRecord = '/online/cgform/head/removeRecord', + copyOnline = '/online/cgform/head/copyOnline', + copyTable = '/online/cgform/head/copyOnlineTable', + + // CgformModal页面API + addAll = '/online/cgform/api/addAll', + editAll = '/online/cgform/api/editAll', + queryField = '/online/cgform/field/listByHeadId', + queryIndex = '/online/cgform/index/listByHeadId', + checkOnlyTable = '/online/cgform/api/checkOnlyTable', + // 只修改表配置,不改字段 + editHead = '/online/cgform/head/edit' +} + +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +// 批量移除(移除只会删除表单配置) +export const doBatchRemove = (idList: string[]) => doRemove(idList, 0); +export const doSingleRemove = (pid) => defHttp.delete({ url: Api.removeRecord, params: { id: pid } }, + { joinParamsToUrl: true }); +// 批量删除(删除会删除对应的数据库表以及子表) +export const doBatchDelete = (idList: string[]) => doRemove(idList, 1); +export const doSingleDelete = (pid) => defHttp.delete({ url: Api.delete, params: { id: pid } }, + { joinParamsToUrl: true }); + +// 执行删除操作 +function doRemove(idList: string[], flag: number) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { + ids: idList.join(','), + flag: flag, + }, + }, + { joinParamsToUrl: true } + ); +} + +// 同步数据库 +export const doDatabaseSync = (id, method) => + defHttp.post({ url: `${Api.databaseSync}/${id}/${method}`, timeout: 12000, timeoutErrorMessage: '同步数据库超时,已自动刷新' }); + +export const doCopyOnlineView = (id) => defHttp.post({ url: `${Api.copyOnline}?code=${id}` }); + +/** + * 复制表 + * @param id 要复制的表的id + * @param tableName 新的表名 + * @param params 其他参数 + */ +export const doCopyTable = (id, tableName, params?) => defHttp.get({ url: `${Api.copyTable}/${id}`, params: { tableName, ...params } }); + +// 弹窗formApi +export const formApi = { + // 查询表字段 e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + doQueryField: (headId: string, params?) => defHttp.get({ url: Api.queryField, params: { headId, ...params } }), + // 查询表index配置 + doQueryIndexes: (headId: string, params?) => defHttp.get({ url: Api.queryIndex, params: { headId, ...params } }), + // 新增或修改 + doSaveOrUpdate: (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.editAll, params }); + } else { + return defHttp.post({ url: Api.addAll, params }); + } + }, + //只是修改表配置不改字段 + editHead: (params)=>{ + return defHttp.put({ url: Api.editHead, params }); + } +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/cgform.data.ts b/jeecgboot-vue3/src/views/super/online/cgform/cgform.data.ts new file mode 100644 index 000000000..4e67255e5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/cgform.data.ts @@ -0,0 +1,308 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { getDictItemsByCode } from '/@/utils/dict'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { buildUUID } from '/@/utils/uuid'; + +// 校验失败 flag +export const VALIDATE_FAILED = 'validate-failed'; + +export const columns: BasicColumn[] = [ + { + title: '表类型', + align: 'center', + sorter: true, + dataIndex: 'tableType', + width: 140, + customRender({ text, record }) { + let tableTypeDictOptions = getDictItemsByCode('cgform_table_type'); + let tbTypeText = filterDictText(tableTypeDictOptions, text); + if (record.isTree === 'Y') { + tbTypeText += '(树)'; + } + if (record.themeTemplate === 'innerTable') { + tbTypeText += '(内嵌)'; + } else if (record.themeTemplate === 'erp') { + tbTypeText += '(ERP)'; + } else if (record.themeTemplate === 'tab') { + tbTypeText += '(TAB)'; + } + return tbTypeText; + }, + }, + { + title: '表名', + sorter: true, + align: 'center', + dataIndex: 'tableName', + width: 240, + }, + { + title: '表描述', + align: 'center', + dataIndex: 'tableTxt', + width: 220, + }, + { + title: '版本', + align: 'center', + dataIndex: 'tableVersion', + width: 120, + }, + { + title: '同步状态', + align: 'center', + sorter: true, + dataIndex: 'isDbSynch', + slots: { customRender: 'dbSync' }, + width: 120, + }, + { + title: '创建时间', + align: 'center', + sorter: true, + dataIndex: 'createTime', + width: 240, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '表名', + field: 'tableName', + component: 'JInput', + }, + { + label: '表类型', + field: 'tableType_MultiString', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'cgform_table_type', + mode: 'multiple', + }, + }, + { + label: '表描述', + field: 'tableTxt', + component: 'JInput', + }, +]; + +/** 扩展JSON默认值 */ +export const ExtConfigDefaultJson = { + // 对接报表打印 + reportPrintShow: 0, + reportPrintUrl: '', + joinQuery: 0, + modelFullscreen: 0, + modalMinWidth: '', + commentStatus: 0, + tableFixedAction: 1, + tableFixedActionType: 'right', + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + formLabelLengthShow: 0, + formLabelLength: null, + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + // 是否启用外部链接 + enableExternalLink: 0, + externalLinkActions: 'add,edit,detail', +}; + +/** 获取主表的初始化数据 */ +export function useInitialData() { + let initialData = [ + { + dbFieldName: 'id', + dbFieldTxt: '主键', + dbLength: 36, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '1', + dbIsNull: '0', + // table2 + isShowForm: '0', + isShowList: '0', + isReadOnly: '1', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'create_by', + dbFieldTxt: '创建人', + dbLength: 50, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'create_time', + dbFieldTxt: '创建日期', + dbLength: 0, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'Datetime', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'datetime', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'update_by', + dbFieldTxt: '更新人', + dbLength: 50, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'update_time', + dbFieldTxt: '更新日期', + dbLength: 0, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'Datetime', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'datetime', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'sys_org_code', + dbFieldTxt: '所属部门', + dbLength: 64, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + // { + // dbFieldName: 'sys_org_code', + // dbFieldTxt: '所属部门', + // dbLength: 50, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // }, + // { + // dbFieldName: 'sys_company_code', + // dbFieldTxt: '所属公司', + // dbLength: 50, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // }, + // { + // dbFieldName: 'bpm_status', + // dbFieldTxt: '流程状态', + // dbLength: 32, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // } + ]; + // 临时 id,不保存到数据库 + let tempIds: string[] = []; + initialData.forEach((record) => { + record['id'] = buildUUID(); + tempIds.push(record['id']); + }); + return { initialData, tempIds }; +} + +/** 获取树的初始化数据 */ +export function useTreeNeedFields() { + return [ + { + dbFieldName: 'pid', + dbFieldTxt: '父级节点', + dbLength: 32, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '1', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'has_child', + dbFieldTxt: '是否有子节点', + dbLength: 3, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'list', + fieldLength: '200', + queryMode: 'single', + // table3 + dictField: 'yn', + dbIsSync: '1' + }, + ]; +} + +/** + * online 默认按钮 + */ +export const onlineDefaultButton = [ + { code: 'add', title: '新增', status: 0 }, + { code: 'edit', title: '编辑', status: 0 }, + { code: 'delete', title: '删除', status: 0 }, + { code: 'export', title: '导出', status: 0 }, + { code: 'import', title: '导入', status: 0 }, + { code: 'query', title: '查询', status: 0 }, +]; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/AiModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/AiModal.vue new file mode 100644 index 000000000..014d1302a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/AiModal.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CgformAddressModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformAddressModal.vue new file mode 100644 index 000000000..32f19a63b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformAddressModal.vue @@ -0,0 +1,232 @@ + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CgformFieldItem.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformFieldItem.vue new file mode 100644 index 000000000..3c06369a3 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformFieldItem.vue @@ -0,0 +1,62 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CgformHeadForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformHeadForm.vue new file mode 100644 index 000000000..94eb8e9ec --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformHeadForm.vue @@ -0,0 +1,533 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CgformModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformModal.vue new file mode 100644 index 000000000..042114846 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CgformModal.vue @@ -0,0 +1,879 @@ + + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileListModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileListModal.vue new file mode 100644 index 000000000..049f03ebe --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileListModal.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileViewModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileViewModal.vue new file mode 100644 index 000000000..a431e021d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeFileViewModal.vue @@ -0,0 +1,378 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/CodeGeneratorModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeGeneratorModal.vue new file mode 100644 index 000000000..13fb64905 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/CodeGeneratorModal.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/DbToOnlineModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/DbToOnlineModal.vue new file mode 100644 index 000000000..b6e5758ec --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/DbToOnlineModal.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/ExtendConfigModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/ExtendConfigModal.vue new file mode 100644 index 000000000..2ed195ff7 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/ExtendConfigModal.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/FileSelectModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/FileSelectModal.vue new file mode 100644 index 000000000..c1f96c80a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/FileSelectModal.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue new file mode 100644 index 000000000..28cf4b178 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthSetterModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthSetterModal.vue new file mode 100644 index 000000000..c224431ca --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/AuthSetterModal.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.api.ts new file mode 100644 index 000000000..810e056d1 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.api.ts @@ -0,0 +1,81 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + authField = '/online/cgform/api/authColumn', + authButton = '/online/cgform/api/authButton', + authData = '/online/cgform/api/authData', + authPage = '/online/cgform/api/authPage', + roleAuth = '/online/cgform/api/roleAuth', + saveButton = '/online/cgform/api/roleButtonAuth', + saveData = '/online/cgform/api/roleDataAuth', + validData = '/online/cgform/api/validAuthData', + saveField = '/online/cgform/api/roleColumnAuth', + batchAuthField = '/online/cgform/api/authColumn/batch', +} + +// 字段权限,查询数据 +export const authFieldLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authField}/${cgformId}`, params }); +// 字段权限,更新启用状态 +export const authFieldUpdateStatus = (params) => defHttp.put({ url: Api.authField, params }); +// 字段权限,更新权限状态 +export const authFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.authField, params }); + +// 字段权限,批量更新启用状态 +export const batchAuthFieldUpdateStatus = (params) => defHttp.put({ url: Api.batchAuthField, params }); +// 字段权限,批量更新权限状态 +export const batchAuthFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.batchAuthField, params }); + + +// 按钮权限,查询数据 +export const authButtonLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authButton}/${cgformId}`, params }); +// 按钮权限,启用 +export const authButtonEnable = (params) => defHttp.post({ url: Api.authButton, params }); +// 按钮权限,禁用 +export const authButtonDisable = (id: string, params?) => defHttp.put({ url: `${Api.authButton}/${id}`, params }); + +// 数据权限,查询数据 +export const authDataLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authData}/${cgformId}`, params }); +// 数据权限,更新启用状态 +export const authDataUpdateStatus = (params) => defHttp.put({ url: Api.authData, params }); +// 数据权限,保存或修改 +export const authDataSaveOrUpdate = (params, isUpdate: boolean) => { + if (isUpdate) { + return defHttp.put({ url: Api.authData, params }); + } else { + return defHttp.post({ url: Api.authData, params }); + } +}; +// 数据权限,删除 +export const authDataDelete = (id: string, params?) => defHttp.delete({ url: `${Api.authData}/${id}`, params }); + +export const authFieldLoadTree = (cgformId: string, authType: number, params?) => { + let url = `${Api.authPage}/${cgformId}/${authType}`; + return defHttp.get({ url, params }); +}; + +export const authDataLoadTree = (cgformId: string, params?) => { + let url = `${Api.validData}/${cgformId}`; + return defHttp.get({ url, params }); +}; + +export const authButtonLoadTree = (cgformId: string, authType: number, params?) => { + let url = `${Api.authPage}/${cgformId}/${authType}`; + return defHttp.get({ url, params }); +}; + +export const loadRoleAuthChecked = (params) => defHttp.get({ url: Api.roleAuth, params }); + +export const saveAuthField = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveField}/${roleId}/${cgformId}`; + return defHttp.post({ url, params }); +}; + +export const saveAuthData = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveData}/${roleId}/${cgformId}`; + return defHttp.post({ url, params }); +}; + +export const saveAuthButton = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveButton}/${roleId}/${cgformId}`; + return defHttp.post({url, params}, {successMessageMode: 'none', isTransformResponse: false}); +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.data.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.data.ts new file mode 100644 index 000000000..63f3f6b3a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/auth.data.ts @@ -0,0 +1,235 @@ +import { computed } from 'vue'; +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { useConditionFilter } from '/@/utils/index'; + +// 字段权限列配置 +export const authFieldColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 100, + align: 'center', + slots: { customRender: 'switch' }, + }, + { + title: '字段名称', + width: 200, + dataIndex: 'code', + }, + { + title: '字段描述', + // width: 200, + dataIndex: 'title', + }, + { + title: '列表控制', + dataIndex: 'list', + width: 120, + slots: { customRender: 'list' }, + }, + { + title: '表单控制', + dataIndex: 'form', + width: 180, + slots: { customRender: 'form' }, + }, +]; + +// 按钮权限列配置 +export const authButtonColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 80, + slots: { customRender: 'switch' }, + }, + { + title: '名称', + dataIndex: 'title', + }, + { + title: '编码', + dataIndex: 'code', + }, + { + title: '权限控制', + dataIndex: 'control', + width: 180, + slots: { customRender: 'control' }, + }, +]; + +export const authButtonFixedList = [ + { code: 'add', title: '新增', status: 0 }, + { code: 'edit', title: '编辑', status: 0 }, + { code: 'detail', title: '详情', status: 0 }, + { code: 'delete', title: '删除', status: 0 }, + { code: 'batch_delete', title: '批量删除', status: 0 }, + { code: 'export', title: '导出', status: 0 }, + { code: 'import', title: '导入', status: 0 }, + { code: 'query', title: '查询', status: 0 }, + { code: 'reset', title: '重置', status: 0 }, + { code: 'aigc_mock_data', title: '生成测试数据', status: 0 }, + { code: 'bpm', title: '提交流程', status: 0 }, + { code: 'super_query', title: '高级查询', status: 0 }, + { code: 'form_confirm', title: '确定', status: 0 }, +]; + +export const USE_SQL_RULES = 'USE_SQL_RULES'; +// 数据权限列配置 +export const authDataColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 80, + slots: { customRender: 'switch' }, + }, + { + title: '规则名称', + dataIndex: 'ruleName', + width: 130, + }, + { + title: '规则描述', + dataIndex: 'description', + customRender({ record: { ruleOperator, ruleValue, ruleColumn } }) { + if (ruleOperator == USE_SQL_RULES) { + return `自定义SQL: ${ruleValue}`; + } else { + return `${ruleColumn} ${ruleOperator} ${ruleValue}`; + } + }, + }, +]; + +export function useAuthDataFormSchemas(props, methods) { + const formSchemas = computed(() => [ + { + label: '规则名称', + field: 'ruleName', + required: true, + component: 'Input', + componentProps: { + onChange: methods.onRuleNameChange, + }, + }, + { + label: '规则字段', + field: 'ruleColumn', + component: 'JSearchSelect', + componentProps: { + dictOptions: props.authFields, + getPopupContainer: () => document.body, + onChange: methods.onRuleColumnChange, + }, + dynamicRules({ model }) { + return [{ required: model.ruleOperator != USE_SQL_RULES, message: '请输入规则字段' }]; + }, + show: ({ model }) => model.ruleOperator != USE_SQL_RULES, + }, + // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-201】权限管理条件根据控件过滤 + { + label: '条件规则', + field: 'ruleOperator', + required: true, + component: 'JDictSelectTag', + componentProps: { + options: [], + onChange: methods.onRuleOperatorChange, + getPopupContainer: () => document.body, + }, + dynamicPropskey: 'options', + dynamicPropsVal: ({ model, field }) => { + const getFieldType = (type) => { + if (['BigDecimal', 'double', 'int'].includes(type)) { + return 'number'; + } else { + return; + } + }; + const { filterCondition } = useConditionFilter(); + if (model.ruleColumn) { + const findItem = props.authFields.find((item) => item.value === model.ruleColumn) ?? {}; + const result = filterCondition({ view: findItem.view, fieldType: getFieldType(findItem.dbType) }).map((item) => ({ + label: item.title ?? item.label, + value: item.val ?? item.value, + })); + result.push({ value: 'USE_SQL_RULES', label: '自定义SQL' }); + return result; + } else { + return [{ value: 'USE_SQL_RULES', label: '自定义SQL' }]; + } + }, + }, + // { + // label: '条件规则', + // field: 'ruleOperator', + // required: true, + // component: 'JDictSelectTag', + // componentProps: { + // dictCode: 'rule_conditions', + // onChange: methods.onRuleOperatorChange, + // getPopupContainer: () => document.body, + // }, + // }, + // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-201】权限管理条件根据控件过滤 + { + label: '规则值', + field: 'ruleValue', + required: true, + // -update-begin--author:liaozhiyang---date:20240607---for:【TV360X-536】数据权限配置配置优化及新增JInputSelect组件 + component: 'JInputSelect', + componentProps: { + selectPlaceholder: '可选择系统变量', + inputPlaceholder: '请输入', + getPopupContainer: () => document.body, + selectWidth: '200px', + options: [ + { + label: '登录用户账号', + value: '#{sys_user_code}', + }, + { + label: '登录用户名称', + value: '#{sys_user_name}', + }, + { + label: '当前日期', + value: '#{sys_date}', + }, + { + label: '当前时间', + value: '#{sys_time}', + }, + { + label: '登录用户部门', + value: '#{sys_org_code}', + }, + { + label: '用户拥有的部门', + value: '#{sys_multi_org_code}', + }, + { + label: '登录用户租户', + value: '#{tenant_id}', + }, + ], + }, + // -update-end--author:liaozhiyang---date:20240607---for:【TV360X-536】数据权限配置配置优化及新增JInputSelect组件 + }, + { + label: '状态', + field: 'status', + required: true, + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '有效', value: 1 }, + { label: '无效', value: 0 }, + ], + }, + defaultValue: 1, + }, + ]); + return { formSchemas }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue new file mode 100644 index 000000000..32aa925a9 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue new file mode 100644 index 000000000..3652864f4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue @@ -0,0 +1,229 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue new file mode 100644 index 000000000..3af8ed264 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue new file mode 100644 index 000000000..fed7e338a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue new file mode 100644 index 000000000..967ef9b11 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue new file mode 100644 index 000000000..45abe2a40 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue new file mode 100644 index 000000000..ad929b004 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue @@ -0,0 +1,70 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftRole.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftRole.vue new file mode 100644 index 000000000..146f44887 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftRole.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftUser.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftUser.vue new file mode 100644 index 000000000..686b2772e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/auth/setter/LeftUser.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/button/BuiltInButtonList.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/button/BuiltInButtonList.vue new file mode 100644 index 000000000..03798f301 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/button/BuiltInButtonList.vue @@ -0,0 +1,164 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/button/CustomButtonList.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/button/CustomButtonList.vue new file mode 100644 index 000000000..ca4eb1952 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/button/CustomButtonList.vue @@ -0,0 +1,228 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.api.ts new file mode 100644 index 000000000..79eac3511 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.api.ts @@ -0,0 +1,42 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/cgform/button/list/', + delete = '/online/cgform/button/delete', + deleteBatch = '/online/cgform/button/deleteBatch', + save = '/online/cgform/button/add', + edit = '/online/cgform/button/edit', + + builtInList = '/online/cgform/button/builtInList/', +} + +export const list = (code: string, params) => defHttp.get({ url: Api.list + code, params }); + +// 执行删除操作 +export function doBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存或者更新 + */ +export const saveOrUpdate = (params, isUpdate: boolean) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 加载内置按钮列表 + */ +export const builtInList = (code: string, params) => defHttp.get({url: Api.builtInList + code, params}); diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.ts new file mode 100644 index 000000000..696eb46c4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.ts @@ -0,0 +1,194 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +// @ts-ignore +import {getButtonIconRender} from "./button.data.tsx"; + +export const columns: BasicColumn[] = [ + { title: '按钮编码', align: 'center', dataIndex: 'buttonCode' }, + { title: '按钮名称', align: 'center', dataIndex: 'buttonName' }, + { + title: '按钮样式', + align: 'center', + dataIndex: 'buttonStyle', + customRender({ text, record }) { + if (text === 'form') { + let p = record.optPosition; + return text + '(' + (p == '2' ? '底部' : '侧面') + ')'; + } else { + return text; + } + }, + }, + { title: '按钮类型', align: 'center', dataIndex: 'optType' }, + { title: '排序', align: 'center', dataIndex: 'orderNum' }, + { + title: '按钮图标', + align: 'center', + dataIndex: 'buttonIcon', + customRender: ({text}) => { + return getButtonIconRender({text}); + }, + }, + { title: '表达式', align: 'center', dataIndex: 'exp' }, + { + title: '按钮状态', + align: 'center', + dataIndex: 'buttonStatus', + customRender({ text }) { + if (text == 1) { + return '激活'; + } else { + return '未激活'; + } + }, + }, +]; + +export const formSchemas = ({ redoModalHeight }): FormSchema[] => { + return [ + { + label: '按钮编码', + field: 'buttonCode', + component: 'Input', + required: true, + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-139】按钮编码加上正则校验 + dynamicRules: () => { + return [ + { + validator: (_, value) => { + //需要return 一个Promise对象 + return new Promise((resolve, reject) => { + const reg = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; + if (reg.test(value)) { + resolve(); + } else { + reject('编码只能包含字母、数字、下划线 (_) 和美元符号 ($)且不能以数字开头'); + } + }); + }, + }, + // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1693】自定义按钮编码排除sql和java系统内置编码 + { + validator: (_, value) => { + //需要return 一个Promise对象 + return new Promise((resolve, reject) => { + const exclude = ['add', 'edit', 'detail', 'delete', 'batch_delete', 'import', 'export', 'query', 'reset', 'bpm', 'super_query', 'form_confirm']; + if (exclude.includes(value)) { + reject('不可使用内置按钮编码,请在“管理内置按钮”中修改内置按钮'); + } else { + resolve(); + } + }); + }, + }, + // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1693】自定义按钮编码排除sql和java系统内置编码 + ]; + }, + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-139】按钮编码加上正则校验 + }, + { + label: '按钮名称', + field: 'buttonName', + component: 'Input', + required: true, + }, + { + label: '按钮样式', + field: 'buttonStyle', + component: 'Select', + componentProps: { + options: [ + { label: 'Link', value: 'link' }, + { label: 'Button', value: 'button' }, + { label: 'Form', value: 'form' }, + ], + // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-1306】自定义按钮弹窗按钮样式切换是重置弹窗高度 + onChange: () => { + redoModalHeight(); + }, + // update-end--author:liaozhiyang---date:20240618---for:【TV360X-1306】自定义按钮弹窗按钮样式切换是重置弹窗高度 + }, + defaultValue: 'link', + }, + { + label: '按钮位置', + field: 'optPosition', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + // { label: '侧面', value: '1' }, + { label: '底部', value: '2' }, + ], + }, + defaultValue: '2', + show: ({ model }) => model.buttonStyle === 'form', + }, + { + label: '按钮类型', + field: 'optType', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + { label: 'Js', value: 'js' }, + { label: 'Action', value: 'action' }, + ], + }, + defaultValue: 'js', + }, + { + label: '排序', + field: 'orderNum', + component: 'InputNumber', + componentProps: { + style: 'width: 100%', + }, + }, + { + label: '按钮图标', + field: 'buttonIcon', + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-136】按钮图标改成图标组件选择 + component: 'IconPicker', + componentProps: { + clearSelect: true, + iconPrefixSave: false, + }, + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-136】按钮图标改成图标组件选择 + ifShow: ({ values, model }) => { + if (values.buttonStyle == 'button' || values.buttonStyle == 'form') { + return true; + } else { + // model.buttonIcon = null; + return false; + } + }, + }, + { + label: '表达式', + field: 'exp', + component: 'Input', + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-89】自定义按钮样式是link时,展示表达式配置 + ifShow: ({ values, model }) => { + if (values.buttonStyle == 'link') { + return true; + } else { + model.exp = ''; + return false; + } + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-89】自定义按钮样式是link时,展示表达式配置 + }, + { + label: '按钮状态', + field: 'buttonStatus', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '激活', value: '1' }, + { label: '未激活', value: '0' }, + ], + }, + defaultValue: '1', + }, + ] +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.tsx b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.tsx new file mode 100644 index 000000000..4594d3f79 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/button/button.data.tsx @@ -0,0 +1,9 @@ +import {Icon} from "@/components/Icon"; + +export function getButtonIconRender({text}) { + if (!text) { + return '' + } + // @ts-ignore + return ; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue new file mode 100644 index 000000000..73af6db82 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue @@ -0,0 +1,209 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue new file mode 100644 index 000000000..39c5ddd25 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue new file mode 100644 index 000000000..d4e9550ac --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue new file mode 100644 index 000000000..aefb907c9 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue @@ -0,0 +1,213 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/codeHinting.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/codeHinting.ts new file mode 100644 index 000000000..20ad74eca --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/codeHinting.ts @@ -0,0 +1,268 @@ +export const keywords: any = { + list: [ + //------ 列表api ------- + // 属性 + { text: '.acceptHrefParams', displayText: 'acceptHrefParams', superiors: 'this', desc: '获取地址栏上的条件' }, + { text: '.currentPage', displayText: 'currentPage', superiors: 'this', desc: '获取当前页数,默认1' }, + { text: '.currentTableName', displayText: 'currentTableName', desc: '获取当前表名' }, + { text: '.description', displayText: 'description', superiors: 'this', desc: '获取当前表描述' }, + { text: '.hasChildrenField', displayText: 'hasChildrenField', superiors: 'this', desc: '如果是树形列表,获取是否有子节点字段名' }, + { text: '.ID', displayText: 'ID', superiors: 'this', desc: '获取当前表的配置ID' }, + { text: '.pageSize', displayText: 'pageSize', superiors: 'this', desc: '获取当前每页条数,默认10' }, + { text: '.queryParam', displayText: 'queryParam', superiors: 'this', desc: '获取查询表单的查询条件' }, + { text: '.selectedRowKeys', displayText: 'selectedRowKeys', superiors: 'this', desc: '获取选中行的id的数组' }, + { text: '.selectedRows', displayText: 'selectedRows', superiors: 'this', desc: '获取选中行的数据数组' }, + { text: '.sortField', displayText: 'sortField', superiors: 'this', desc: '获取排序字段,默认‘id’' }, + { text: '.sortType', displayText: 'sortType', superiors: 'this', desc: '获取排序类型,默认升序‘asc’' }, + { text: '.total', displayText: 'total', superiors: 'this', desc: '获取总条数' }, + { text: '.loading', displayText: 'loading', superiors: 'this', desc: '设置/获取loading' }, + // 方法 + { text: '.loadData()', displayText: 'loadData()', superiors: 'this', desc: '加载数据' }, + { text: '.clearSelectedRow()', displayText: 'clearSelectedRow()', superiors: 'this', desc: '清除选中的行' }, + { + text: '.getLoadDataParams()', + displayText: 'getLoadDataParams()', + superiors: 'this', + desc: '获取所有的查询条件,返回一个对象,包括:查询表单,高级查询,地址栏参数,分页信息,排序信息等', + }, + { text: '.isTree()', displayText: 'isTree()', superiors: 'this', desc: '判断当前表是不是树,返回布尔值' }, + // 事件(前置) + { + text: `beforeEdit(row){ + return new Promise((resolve, reject) => { + if(row.字段名 == '字段值'){ + reject('测试~'); + }else{ + resolve(); + } + }) +}`, + displayText: 'beforeEdit(row){}', + desc: '点击操作列下的编辑按钮触发,返回promise对象', + }, + { + text: `beforeDelete(row){ + return new Promise((resolve, reject) => { + if(row.字段名 == '字段值'){ + reject('测试~'); + }else{ + resolve(); + } + }) +}`, + displayText: 'beforeDelete(row){}', + desc: '点击操作列下的删除按钮触发,返回promise对象', + }, + { text: 'console.log()', displayText: 'console.log()', desc: '打印日志' }, + ], + form: [ + //------ 表单api ------- + // 属性 + { text: '.loading', displayText: 'loading', superiors: 'this', desc: '是否加载中,返回的是一个ref对象' }, + { text: '.isUpdate', displayText: 'isUpdate', superiors: 'this', desc: '是否是编辑页面,返回的是一个ref对象' }, + { text: '.onlineFormRef', displayText: 'onlineFormRef', superiors: 'this', desc: '主表/单表表单的ref对象' }, + { text: '.refMap', displayText: 'refMap', superiors: 'this', desc: '子表表单/子表table的ref对象map,key为子表表名' }, + { text: '.subActiveKey', displayText: 'subActiveKey', superiors: 'this', desc: '子表的激活的tab索引值对应的字符串,从‘0’开始,返回的是一个ref对象' }, + { text: '.sh', displayText: 'sh', superiors: 'this', desc: '单表/主表字段的显示隐藏状态' }, + { text: '.submitFlowFlag', displayText: 'submitFlowFlag', superiors: 'this', desc: '是否提交表单后自动提交流程,返回一个ref对象' }, + { text: '.subFormHeight', displayText: 'subFormHeight', superiors: 'this', desc: '一对一子表表单的高度,不需要设置,返回一个ref对象' }, + { text: '.subTableHeight', displayText: 'subTableHeight', superiors: 'this', desc: '一对多子表table的高度,不需要设置,返回一个ref对象' }, + { text: '.tableName', displayText: 'tableName', superiors: 'this', desc: '当前表名,返回的是一个ref对象' }, + { text: '.$nextTick', displayText: '$nextTick', superiors: 'this', desc: '调用的是vue3的nextTick' }, + { text: '.字段名_load', displayText: '字段名_load', superiors: 'this', desc: '控制字段的加载与否,设置为false表示当前字段不加载' }, + { text: '.字段名_disabled', displayText: '字段名_disabled', superiors: 'this', desc: '控制字段的禁用与否,设置为true表示当前字段禁用' }, + // 方法 + { text: '.addSubRows(tableName, rows)', displayText: 'addSubRows(tableName, rows)', superiors: 'this', desc: '往一对多子表table里添加数据' }, + { + text: '.changeOptions(field, options)', + texdisplayTextt: 'changeOptions(field, options)', + superiors: 'this', + desc: '改变单表/主笔 下拉控件的下拉选项', + }, + { text: '.clearSubRows(tableName)', displayText: 'clearSubRows(tableName)', superiors: 'this', desc: '清空一对多子表table的数据' }, + { + text: '.clearThenAddRows(tableName, rows)', + displayText: 'clearThenAddRows(tableName, rows)', + superiors: 'this', + desc: '先清空一对多子表table的数据,再往里添加数据', + }, + { text: '.getFieldsValue()', displayText: 'getFieldsValue()', superiors: 'this', desc: '获取主表/单表 所有字段的值' }, + { + text: '.getSubTableInstance(tableName)', + displayText: 'getSubTableInstance(tableName)', + superiors: 'this', + desc: '获取子表的实例对象,这个对象可以调用子表table的方法', + }, + { text: '.setFieldsValue(row)', displayText: 'setFieldsValue(row)', superiors: 'this', desc: '设置主表/单表 字段的值' }, + { + text: '.triggleChangeValues(values,id,target)', + displayText: 'triggleChangeValues(values,id,target)', + superiors: 'this', + desc: '改变单表/主表/子表 字段的值,一般用于change事件,其中id,target需要通过change事件的内置参数获取,如果不传id,target的值,则改变的是主表的字段', + }, + { text: '.triggleChangeValue(field, value)', displayText: 'triggleChangeValue(field, value)', superiors: 'this', desc: '设置单表/主表 字段的值' }, + { + text: '.onlineFormValueChange(field, value, otherValus)', + displayText: 'onlineFormValueChange(field, value, otherValus)', + superiors: 'this', + desc: '定义后,当表单值改变的时候会触发该方法(因js增强hook方式不支持原来的onlChange,所以定义此方法)', + }, + { + text: '.changeSubTableOptions(tableName,field,options)', + displayText: 'changeSubTableOptions(tableName,field,options)', + superiors: 'this', + desc: '改变一对一子表下拉框options', + }, + { + text: '.changeSubFormbleOptions(tableName,field,options)', + displayText: 'changeSubFormbleOptions(tableName,field,options)', + superiors: 'this', + desc: '改变一对多子表下拉框options', + }, + { + text: '.changeRemoteOptions({ field, dict, label, type?, subTableName? })', + displayText: 'changeRemoteOptions({ field, dict, label, type?, subTableName? })', + superiors: 'this', + desc: '改变动态下拉框options', + }, + { + text: '.submitFormAndFlow()', + displayText: 'submitFormAndFlow()', + superiors: 'this', + desc: '表单提交且发起流程', + }, + // 提交前置事件 + { + text: `beforeSubmit(row){ + return new Promise((resolve, reject)=>{ + //此处模拟等待时间,可能需要发起请求 + setTimeout(()=>{ + if(row.字段名 == '字段值'){ + // 当某个字段不满足要求的时候可以reject + reject('测试~'); + }else{ + resolve(); + } + },3000) + }) +}`, + displayText: 'beforeSubmit(row){}', + desc: '提交前置事件', + }, + // 表单加载事件 + { + text: `loaded(){ + this.$nextTick(()=>{ + // let text = '测试js增强设置默认值'; + // if(this.isUpdate.value === true){ + // text = '测试js增强修改表单值'; + // } + this.setFieldsValue({ + 字段名: 修改的值 + }) + }) +}`, + displayText: 'loaded(){}', + desc: '表单加载事件', + }, + // 单表#表单值改变事件 + { + text: `onlChange(){ + return { + 字段名(){ + let value = event.value + console.log(value) + this.triggleChangeValues({'字段名': '修改后的值'}) + } + } + }`, + displayText: 'onlChange(){}', + desc: '单表#表单值改变事件', + }, + // 子表#表单值改变事件 + { + text: `子表名_onlChange(){ + return { + 字段名(){ + let value = event.value; + console.log(value); + let row = {'字段名': '测试一对多值改变:'+value}; + this.triggleChangeValues(row, event.row.id, event.target) + } + } +}`, + displayText: '子表名_onlChange(){}', + desc: '子表#表单值改变事件', + }, + // 子改主#表单值改变事件 + { + text: `子表名_onlChange(){ + return { + 子表字段01(){ + this.getSubTableInstance('子表名').getValues((err,values)=>{ + this.triggleChangeValues({'主表字段名': '修改后的值'}) + }) + }, + } +} +`, + displayText: '子表名_onlChange(){}', + desc: '子改主#表单值改变事件', + }, + // js增强实现下拉联动 + { + text: `onlChange(){ + return { + 字段名01(){ + let value = event.value + this.changeOptions('字段名02', '修改后的值'); + } + 字段名02(){ + let value = event.value + this.changeOptions('字段名03', '修改后的值'); + } + } +}`, + displayText: 'changeOptions()', + desc: 'js增强实现下拉联动', + }, + { text: 'console.log()', displayText: 'console.log()', desc: '打印日志' }, + ], + common: [ + // JS增强 http请求 + { + text: `getAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'getAction(url, param)', + desc: 'get请求', + }, + { + text: `postAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'postAction(url, param)', + desc: 'post请求', + }, + { + text: `putAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'putAction(url, param)', + desc: 'put请求', + }, + { + text: `deleteAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'deleteAction(url, param)', + desc: 'delete请求', + }, + { text: 'this', displayText: 'this', desc: '上下文' }, + { + text: '.openCustomModal({title,width,row,formComponent,requestUrl,hide,show})', + displayText: 'openCustomModal({title,width,row,formComponent,requestUrl,hide,show})', + desc: '打开一个弹窗-参考 Js增强打开自定义弹窗', + }, + ], +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.api.ts new file mode 100644 index 000000000..d4e38f7f5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.api.ts @@ -0,0 +1,141 @@ +import { defHttp } from '/@/utils/http/axios'; +import { isArray } from '/@/utils/is'; + +export enum Api { + enhanceJs = '/online/cgform/head/enhanceJs/', + enhanceButton = '/online/cgform/head/enhanceButton/', +} + +export enum EnhanceJavaApi { + enhanceJava = '/online/cgform/head/enhanceJava', + deleteBatch = '/online/cgform/head/deleteBatchEnhanceJava', +} + +export enum EnhanceSqlApi { + enhanceSql = '/online/cgform/head/enhanceSql', + deleteBatch = '/online/cgform/head/deletebatchEnhanceSql', +} + +/** + * 获取JS增强 + * @param code + * @param type + * @param params + */ +export async function getEnhanceJsByCode(code: string, type: string, params?) { + let { success, result } = await defHttp.get( + { + url: Api.enhanceJs + code, + params: { + ...params, + type, + }, + }, + { isTransformResponse: false } + ); + if (!success) { + result = { cgJs: '' }; + } + return result; +} + +/** + * 保存 JS 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceJs = (code: string, params, isUpdate: boolean) => { + let url = `${Api.enhanceJs}${code}`; + if (isUpdate) { + return defHttp.put({ url, params }, { successMessageMode: 'none' }); + } else { + return defHttp.post({ url, params }, { successMessageMode: 'none' }); + } +}; + +// 加载Java增强数据 +export async function getEnhanceJavaByCode(code: string, params) { + // 先加载按钮 + let btnRes = await defHttp.get({ url: Api.enhanceButton + code }, { isTransformResponse: false }); + let btnList = []; + if (btnRes.success && isArray(btnRes.result)) { + // 按钮过滤 java增强只看action按钮 + btnList = btnRes.result.filter((item) => item.optType == 'action'); + } + // 再加载数据 + let path = `${EnhanceJavaApi.enhanceJava}/${code}`; + let dataSource = await defHttp.get({ url: path, params }); + return { btnList, dataSource }; +} + +// 执行删除操作 +export function doEnhanceJavaBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: EnhanceJavaApi.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存 Java 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceJava = (code: string, params, isUpdate: boolean) => { + let url = `${EnhanceJavaApi.enhanceJava}/${code}`; + if (isUpdate) { + return defHttp.put({ url, params }); + } else { + return defHttp.post({ url, params }); + } +}; + +// 加载Sql增强数据 +export async function getEnhanceSqlByCode(code: string, params) { + // 先加载按钮 + let btnRes = await defHttp.get({ url: Api.enhanceButton + code }, { isTransformResponse: false }); + let btnList = []; + if (btnRes.success && isArray(btnRes.result)) { + // 按钮过滤 java增强只看action按钮 + btnList = btnRes.result.filter((item) => item.optType == 'action'); + } + // 再加载数据 + let path = `${EnhanceSqlApi.enhanceSql}/${code}`; + let dataSource = await defHttp.get({ url: path, params }); + return { btnList, dataSource }; +} + +// 执行删除操作 +export function doEnhanceSqlBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: EnhanceSqlApi.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存 Java 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceSql = (code: string, params, isUpdate: boolean) => { + let url = `${EnhanceSqlApi.enhanceSql}/${code}`; + if (isUpdate) { + return defHttp.put({ url, params }); + } else { + return defHttp.post({ url, params }); + } +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.data.ts b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.data.ts new file mode 100644 index 000000000..a95963509 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/enhance/enhance.data.ts @@ -0,0 +1,204 @@ +import { computed, Ref } from 'vue'; +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { onlineDefaultButton } from '../../cgform.data'; + +export function useJavaColumns(btnList: Ref) { + let columns: BasicColumn[] = [ + { + title: '页面按钮', + align: 'center', + dataIndex: 'buttonCode', + customRender: ({ text }) => renderButtonText(text, btnList.value), + }, + { + title: '事件状态', + align: 'center', + dataIndex: 'event', + customRender: ({ text }) => (text == 'start' ? '开始' : '结束'), + }, + { + title: '类型', + align: 'center', + dataIndex: 'cgJavaType', + customRender: ({ text }) => { + if (text == 'spring') { + return 'spring-key'; + } else if (text === 'class') { + return 'java-class'; + } else if (text === 'http') { + return 'http-api'; + } else { + return text; + } + }, + }, + { + title: '内容', + align: 'center', + dataIndex: 'cgJavaValue', + }, + { + title: '是否生效', + align: 'center', + dataIndex: 'activeStatus', + customRender: ({ text }) => { + if (text == '1') { + return '有效'; + } else { + return '无效'; + } + }, + }, + ]; + return { columns }; +} + +export function useJavaFormSchemas(btnList: Ref) { + const formSchemas = computed(() => { + return [ + { + label: '页面按钮', + field: 'buttonCode', + component: 'Select', + componentProps: { + options: [ + { label: '新增', value: 'add' }, + { label: '编辑', value: 'edit' }, + { label: '删除', value: 'delete' }, + { label: '导入', value: 'import' }, + { label: '导出', value: 'export' }, + { label: '查询', value: 'query' }, + ...btnList.value.map((item) => ({ label: item.buttonName, value: item.buttonCode })), + ], + }, + defaultValue: 'add', + }, + { + label: '事件状态', + field: 'event', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开始', value: 'start' }, + { label: '结束', value: 'end' }, + ], + }, + defaultValue: 'end', + }, + { + label: '类型', + field: 'cgJavaType', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: 'spring-key', value: 'spring' }, + { label: 'java-class', value: 'class' }, + { label: 'http-api', value: 'http' }, + ], + }, + defaultValue: 'spring', + }, + { + label: '内容', + field: 'cgJavaValue', + component: 'Input', + required: true, + }, + { + label: '是否生效', + field: 'activeStatus', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '有效', value: '1' }, + { label: '无效', value: '0' }, + ], + }, + defaultValue: '1', + }, + ]; + }); + + return { formSchemas }; +} + +export function useSqlColumns(btnList: Ref) { + let columns: BasicColumn[] = [ + { + title: '页面按钮', + align: 'center', + dataIndex: 'buttonCode', + customRender: ({ text }) => renderButtonText(text, btnList.value), + }, + { + title: '增强SQL', + align: 'center', + dataIndex: 'cgbSql', + ellipsis: true, + }, + ]; + return { columns }; +} + +export function useSqlFormSchemas(btnList: Ref) { + const formSchemas = computed(() => { + return [ + { + label: '页面按钮', + field: 'buttonCode', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + { label: '新增', value: 'add' }, + { label: '编辑', value: 'edit' }, + { label: '删除', value: 'delete' }, + ...btnList.value.map((item) => ({ label: item.buttonName, value: item.buttonCode })), + ], + }, + defaultValue: 'add', + }, + { + label: '增强SQL', + field: 'cgbSql', + component: 'JCodeEditor', + componentProps: { + language: 'sql', + placeholder: '请输入SQL语句', + languageChange: false, + lineNumbers: false, + fullScreen: true, + height: '320px', + }, + defaultValue: '', + }, + { + label: '描述', + field: 'content', + component: 'InputTextArea', + defaultValue: '', + }, + ]; + }); + + return { formSchemas }; +} + +function renderButtonText(text: string, btnList: any[]) { + let str = ''; + for (let item of onlineDefaultButton) { + if (item.code === text) { + str = item.title; + break; + } + } + if (!str) { + for (let item of btnList) { + if (item.buttonCode === text) { + str = item.buttonName; + break; + } + } + } + return str || text; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/CheckDictTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/CheckDictTable.vue new file mode 100644 index 000000000..015695c20 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/CheckDictTable.vue @@ -0,0 +1,266 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/DBAttributeTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/DBAttributeTable.vue new file mode 100644 index 000000000..6b25ff459 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/DBAttributeTable.vue @@ -0,0 +1,641 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue new file mode 100644 index 000000000..ac34c9325 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/IndexTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/IndexTable.vue new file mode 100644 index 000000000..d2d2e494d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/IndexTable.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/PageAttributeTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/PageAttributeTable.vue new file mode 100644 index 000000000..fd70c05fb --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/PageAttributeTable.vue @@ -0,0 +1,642 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/QueryTable.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/QueryTable.vue new file mode 100644 index 000000000..a1f34afbc --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/QueryTable.vue @@ -0,0 +1,158 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/components/tables/components/aiModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/components/aiModal.vue new file mode 100644 index 000000000..2f67cc0af --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/components/tables/components/aiModal.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue new file mode 100644 index 000000000..5e70ed6f6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue @@ -0,0 +1,380 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/SetSwitchOptions.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/SetSwitchOptions.vue new file mode 100644 index 000000000..818ef5dee --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/SetSwitchOptions.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/form/DetailForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/form/DetailForm.vue new file mode 100644 index 000000000..6186cf36f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/form/DetailForm.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/form/useDetailForm.ts b/jeecgboot-vue3/src/views/super/online/cgform/extend/form/useDetailForm.ts new file mode 100644 index 000000000..7d91b7398 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/form/useDetailForm.ts @@ -0,0 +1,520 @@ +import { FormSchema, RenderCallbackParams } from '/@/components/Form'; +import { computed, ref, watch } from 'vue'; +import { getDictItemsByCode } from '/@/utils/dict'; +import { filterMultiDictText, filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { initDictOptions } from '/@/utils/dict/index'; +import { loadDictItem, queryDepartTreeSync, getUserList } from '/@/api/common/api'; +import { defHttp } from '/@/utils/http/axios'; +import { getAreaTextByCodeAnyLevel } from '/@/components/Form/src/utils/Area'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { createImgPreview } from '/@/components/Preview/index'; +import { useMessage } from '/@/hooks/web/useMessage'; + +/*** + * 表单字段的扩展配置解析结果 + */ +interface FieldExtends { + //上传数量 + uploadnum?: number | string; + + //限制大文本在列表页面的展示长度 + showLength?: number | string; + + //popup是否支持多选 + popupMulti?: boolean; + + //部门、用户组件 用于存储的字段名 + store?: string; + + //部门、用户组件 用于展示的字段名 + text?: string; + + //部门、用户组件 是否多选 + multiSelect?: boolean; + + //查询排序规则 + orderRule?: 'asc' | 'desc'; + //关联记录展示类型 + showType?:string; +} + +export interface DetailFormSchema { + field: string; + label: string; + span?: number; + view?: string; + isHtml?: boolean; + isImage?: boolean; + isFile?: boolean; + isCard?: boolean; + multi?:boolean; + order?: any; + dictTable?: string; + dictText?: string; + dictCode?: string; + dict?: string; + fieldExtendJson?: string; + ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + // update-begin--author:liaozhiyang---date:20240425---for:【issues/6139】online详情支持js增强loaded事件及设置值、获取值、隐藏功能 + // js增强隐藏 + hidden?: boolean; + // update-end--author:liaozhiyang---date:20240425---for:【issues/6139】online详情支持js增强loaded事件及设置值、获取值、隐藏功能 +} + +/*interface DetailFormProps { + span?: number; + schemas?: DetailFormSchema[]; + data?: any; + containerClass?: string; +}*/ + +export function useDetailForm(props: any) { + console.log(props); + const dictOptionsMap = {}; + const currentLinkFields: string[] = []; + const detailFormData = ref({}); + const { createMessage } = useMessage(); + + const formContainerClass = computed(() => { + if (props.containerClass) { + return `jeecg-detail-form ${props.containerClass}`; + } else { + return 'jeecg-detail-form'; + } + }); + + watch( + () => props.data, + async (formData) => { + if (formData) { + let arr = props.schemas; + let temp = {}; + if (arr && arr.length > 0) { + for (let item of arr) { + let field = item.field; + try { + temp[field] = await getItemContent(item); + } catch (e) { + console.error('字段【' + field + '】文本获取失败', e); + } + } + } + detailFormData.value = temp; + } + }, + { deep: true, immediate: true } + ); + + async function getItemContent(item) { + let formData = props.data; + if (formData) { + let value = formData[item.field]; + if (!value && value !== '0' && value !== 0) { + return ''; + } + let str = value; + let view = item.view; + if (view == 'list' || view == 'radio' || view == 'checkbox' || view == 'list_multi') { + str = await getSelectText(item, formData); + } else if (view == 'sel_search') { + str = await getTableDataText(item, formData); + } else if (view == 'cat_tree') { + //分类字典树 + str = await getCategoryDataText(item, formData); + } else if (view == 'link_table') { + str = await getLinkTableData(item, formData); + } else if (view == 'sel_depart') { + //部门选择 + str = await getDepartDataText(item, formData); + } else if (view == 'sel_user') { + // 用户选择 + str = await getUserDataText(item, formData); + } else if (view == 'pca') { + //省市区 + // update-begin--author:liaozhiyang---date:20260227---for:【QQYUN-14788】online详情单独的省市没显示 + let includeParent = true; + let fieldExtendJson = item?.fieldExtendJson; + let level = 3; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (['province', 'city', 'region'].includes(fieldExtendJson.displayLevel)) { + if (fieldExtendJson.displayLevel === 'province') { + level = 1; + } else if (fieldExtendJson.displayLevel === 'city') { + level = 2; + } else if (fieldExtendJson.displayLevel === 'region') { + level = 3; + } + includeParent = false; + } + } + str = getAreaTextByCodeAnyLevel(value, includeParent, level as 1 | 2 | 3); + // update-end--author:liaozhiyang---date:20260227---for:【QQYUN-14788】online详情单独的省市没显示 + } else if (view == 'link_down') { + //联动组件 + str = await getLinkDownDataText(item, formData); + } else if (view == 'sel_tree') { + //自定义树控件 + str = await getTreeDataText(item, formData); + } else if (view == 'switch') { + //开关组件 + str = await getSwitchDataText(item, formData); + } else if (view == 'image' || view == 'file') { + str = getFileList(item, formData); + } else if (view == 'popup_dict') { + // update-begin--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + const ditc = formData[`${item.field}_dictText`]; + if (ditc !== undefined) { + str = ditc; + } + // update-end--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + } else { + if (currentLinkFields.indexOf(item.field) >= 0) { + let arr = dictOptionsMap[item.field]; + if (arr && arr.length > 0) { + str = filterMultiDictText(arr, value); + } + } + } + return str; + } + return ''; + } + + // 数据字典/表字典 + async function getSelectText(item, formData) { + // 先从缓存取 + let dictCode = getRequestDictCode(item); + let value = formData[item.field]; + if (!dictCode) { + return value; + } + let options = getDictItemsByCode(dictCode); + if (options && options.length > 0) { + return filterMultiDictText(options, value); + } else { + let dictRes = []; + if (dictOptionsMap[dictCode]) { + dictRes = dictOptionsMap[dictCode]; + } else { + //取不到再请求 + dictRes = (await initDictOptions(dictCode)) || []; + } + if (dictRes && dictRes.length > 0) { + dictOptionsMap[dictCode] = dictRes; + return filterMultiDictText(dictRes, value); + } + } + return ''; + } + + function getRequestDictCode(item) { + let temp = ''; + let { dictCode, dictTable, dictText } = item; + if (!dictTable) { + temp = dictCode; + } else { + temp = encodeURI(`${dictTable},${dictText},${dictCode}`); + } + return temp; + } + + // 表字典-下拉搜索 + async function getTableDataText(item, formData) { + let dictCode = getRequestDictCode(item); + let value = formData[item.field]; + if (!value) { + return ''; + } + + let arr: any[] = []; + // update-begin--author:liaozhiyang---date:20250813---for:【issues/8689】online下拉搜索框详情时无法读取数据字典 + // 系统字典 + if (dictCode.indexOf(',') === -1) { + const options = await initDictOptions(dictCode); + if (options && options.length > 0) { + options.forEach((item: any) => { + if (item.value === value) { + arr.push(item.text || item.label); + } + }); + } + } else { + // 表字典 + if (dictOptionsMap[dictCode+value]) { + arr = dictOptionsMap[dictCode+value]; + } else { + //取不到再请求 + arr = (await defHttp.get({ url: `/sys/dict/loadDictItem/${dictCode}`, params: { key: value } })) || []; + } + } + // update-end--author:liaozhiyang---date:20250813---for:【issues/8689】online下拉搜索框详情时无法读取数据字典 + if (arr && arr.length > 0) { + dictOptionsMap[dictCode+value] = arr; + return arr.join(',') + //return filterMultiDictText(arr, value); + } + return ''; + } + + // 分类字典 + async function getCategoryDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let arr = (await loadDictItem({ ids: value })) || []; + if (arr && arr.length > 0) { + return arr.join(','); + } + return ''; + } + + // 部门数据 + async function getDepartDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let extend = getExtendConfig(item); + let storeField = extend.store || 'id'; + let labelKey = extend.text || 'departName'; + let arr = (await queryDepartTreeSync({ ids: value, primaryKey: storeField })) || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + for (let item of arr) { + if (item[labelKey]) { + temp.push(item[labelKey]); + } else { + temp.push(item.title); + } + } + return temp.join(','); + } + return ''; + } + + //用户数据 + async function getUserDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let extend = getExtendConfig(item); + let storeField = extend.store || 'username'; + let params = { + [storeField]: value, + }; + let res = (await getUserList(params)) || {}; + let arr = res.records || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + console.log('getUserDataText', arr); + let textField = extend.text || 'realname'; + for (let item of arr) { + temp.push(item[textField]); + } + return temp.join(','); + } + return ''; + } + + function getExtendConfig(item) { + let extend: FieldExtends = {}; + let { fieldExtendJson } = item; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + try { + let json = JSON.parse(fieldExtendJson); + extend = { ...json }; + } catch (e) { + console.error(e); + } + } + } + return extend; + } + + // 联动组件 + async function getLinkDownDataText(item, formData) { + let { dictTable, field } = item; + let arr: any[] = []; + if (dictOptionsMap[field]) { + arr = dictOptionsMap[field]; + } else { + if (dictTable) { + let json = JSON.parse(dictTable); + if (json) { + let { table, txt, key, linkField } = json; + let dictCode = `${table},${txt},${key}`; + let temp: any[] = (await initDictOptions(dictCode)) || []; + arr = [...temp]; + if (arr && arr.length > 0) { + dictOptionsMap[field] = arr; + if (linkField) { + let fieldArray = linkField.split(','); + for (let item of fieldArray) { + dictOptionsMap[item] = arr; + currentLinkFields.push(item); + } + } + } + } + } + } + if (arr && arr.length > 0) { + let value = formData[field]; + return filterMultiDictText(arr, value); + } + return ''; + } + + //自定义树 + async function getTreeDataText(item, formData) { + let { dict, field } = item; + let arr = []; + if (dictOptionsMap[field]) { + arr = dictOptionsMap[field]; + } else { + if (dict) { + arr = await initDictOptions(dict); + } + } + if (arr && arr.length > 0) { + let value = formData[field]; + return filterMultiDictText(arr, value); + } + return ''; + } + + //开关 + async function getSwitchDataText(item, formData) { + let { fieldExtendJson, field } = item; + let options = ['Y', 'N']; + if (fieldExtendJson) { + //update-begin---author:wangshuai---date:2025-11-03---for:【issues/9036】online 表单开发, 设置字段 控件类型为开关时,查看详情页时 开关字段显示原始值--- + options = JSON.parse(fieldExtendJson)?.switchOptions; + //update-end---author:wangshuai---date:2025-11-03---for:【issues/9036】online 表单开发, 设置字段 控件类型为开关时,查看详情页时 开关字段显示原始值--- + } + let arr: any[] = [ + { value: options[0], text: '是' }, + { value: options[1], text: '否' }, + { value: options[0]+'', text: '是' }, + { value: options[1]+'', text: '否' }, + ]; + let value = formData[field]; + return filterDictText(arr, value); + } + + function getItemSpan(item) { + if (item.span) { + return item.span; + } + return props.span; + } + + function getFileList(item, formData) { + let str = formData[item.field]; + if (!str) { + return []; + } + let arr = str.split(','); + let result: string[] = []; + for (let item of arr) { + let src = getFileAccessHttpUrl(item) || ''; + if (src) { + result.push(src); + } + } + return result; + } + + function handleDownloadFile(url) { + if (url) { + window.open(url); + } + } + + function handleViewImage(field) { + let values = detailFormData.value[field]; + if (!values || values.length == 0) { + createMessage.warning('无图片!'); + return; + } + createImgPreview({ imageList: values }); + } + + function getFilename(url) { + if (!url) { + return ''; + } + return url.substring(url.lastIndexOf('/') + 1); + } + + /** + * VUEN-1772【vue3 online 详情】ai—ai_control_single 开关组件未翻译、字段为一行时,未居左对齐 + */ + const span24ViewArray = ['file', 'image', 'markdown', 'umeditor']; + function getLabelWidthClass(item) { + if(span24ViewArray.indexOf(item.view)>=0){ + if(props.span==12){ + return 'span12'; + }else if(props.span==8){ + return 'span8'; + }else if(props.span==6){ + return 'span6'; + }else{ + return 'span24'; + } + } + return '' + } + + // 关联记录 + async function getLinkTableData(item, formData) { + let value = formData[item.field]; + let extend = getExtendConfig(item); + if(extend.showType=='select'){ + if (!value) { + return ''; + } + return formData[item.field+'_dictText']; + }else{ + if (!value) { + return ''; + } + return formData[item.field]; + } + + let storeField = extend.store || 'id'; + let arr = (await queryDepartTreeSync({ ids: value, primaryKey: storeField })) || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + for (let item of arr) { + temp.push(item.title); + } + return temp.join(','); + } + return ''; + } + + return { + formContainerClass, + detailFormData, + getItemSpan, + handleDownloadFile, + handleViewImage, + getFilename, + getLabelWidthClass + }; +} + +/** + * TODO 尚未实现 + * 获取 DetailFormSchema[自定义开发用] + */ +export function transDetailFormSchemas(formSchemas: FormSchema[]) { + const detailFormSchemas = ref([]); + console.log(formSchemas); + return detailFormSchemas; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/JModalTip.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/JModalTip.vue new file mode 100644 index 000000000..f6c328cd6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/JModalTip.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue new file mode 100644 index 000000000..e20fd8b97 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue new file mode 100644 index 000000000..2647f82ba --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue new file mode 100644 index 000000000..220c6813a --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue new file mode 100644 index 000000000..32fa1d680 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue new file mode 100644 index 000000000..9caa01fe6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue new file mode 100644 index 000000000..c1cc4f33b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts new file mode 100644 index 000000000..e28f31809 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts @@ -0,0 +1,335 @@ +import { defHttp } from '/@/utils/http/axios'; +import { ref, watchEffect, computed, reactive } from 'vue' +import { pick } from 'lodash-es'; +import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; + +function queryTableData(tableName, params){ + const url = '/online/cgform/api/getData/'+tableName; + return defHttp.get({ url, params }); +} + +function queryTableColumns(tableName, params){ + const url = '/online/cgform/api/getColumns/'+tableName; + return defHttp.get({ url, params }); +} + +export function useLinkTable(props) { + + //TODO 目前只支持查询第一页的数据,可以输入关键字搜索 + const pageNo = ref('1'); + // 查询列 + const baseParam = ref({}); + // 搜素条件 + const searchParam = ref({}); + // 第一个文本列 + const mainContentField = ref(''); + //权限数据 + const auths = reactive({ + add: true, + update: true + }); + + //显示列 + const textFieldArray = computed(()=>{ + if(props.textField){ + return props.textField.split(',') + } + return [] + }); + const otherColumns = ref([]); + // 展示的列 配置的很多列,但是只展示三行 + const realShowColumns = computed(()=>{ + let columns = otherColumns.value; + if(props.multi == true){ + return columns.slice(0, 3) + }else{ + return columns.slice(0, 6) + } + }); + + watchEffect(async ()=>{ + let table = props.tableName; + if(table){ + let valueField = props.valueField || ''; + let textField = props.textField || ''; + let arr:any[] = []; + if(valueField){ + arr.push(valueField) + } + if(textField){ + let temp = textField.split(',') + mainContentField.value = temp[0] + for(let field of temp){ + arr.push(field) + } + } + let imageField = props.imageField || ''; + if(imageField){ + arr.push(imageField) + } + baseParam.value = { + linkTableSelectFields: arr.join(',') + }; + await resetTableColumns() + await reloadTableLinkOptions() + } + }); + + const otherFields = computed(()=>{ + let textField = props.textField || ''; + let others:any[] = []; + let labelField = '' + if(textField){ + let temp = textField.split(','); + labelField = temp[0]; + for(let i=0;i0){ + others.push(temp[i]) + } + } + } + return { + others, + labelField + }; + }); + + // 选项 + const selectOptions = ref([]); + const tableColumns = ref([]); + const dictOptions = ref({}); + //const tableTitle = ref('') + + async function resetTableColumns(){ + let params = baseParam.value; + const data = await queryTableColumns(props.tableName, params); + tableColumns.value = data.columns; + if(data.columns){ + let imageField = props.imageField; + let arr = data.columns.filter(c=>c.dataIndex!=mainContentField.value && c.dataIndex!=imageField) + otherColumns.value = arr; + } + dictOptions.value = data.dictOptions; + // 权限数据 + console.log('隐藏的按钮', data.hideColumns); + if(data.hideColumns){ + let hideCols = data.hideColumns; + if(hideCols.indexOf('add')>=0){ + auths.add = false + }else{ + auths.add = true + } + if(hideCols.indexOf('update')>=0){ + auths.update = false + }else{ + auths.update = true + } + } + } + + async function reloadTableLinkOptions(){ + let params = getLoadDataParams(); + const data = await queryTableData(props.tableName, params); + let records = data.records; + //tableTitle.value = data.head.tableTxt; + let dataList:any[] = []; + let { others, labelField } = otherFields.value; + let imageField = props.imageField; + if(records && records.length>0){ + for(let rd of records){ + let temp = {...rd}; + transData(temp); + let result = Object.assign({}, pick(temp, others), {id:temp.id, label: temp[labelField], value: temp[props.valueField]}); + if(imageField){ + result[imageField] = temp[imageField] + } + dataList.push(result); + } + } + //添加一个空对象 为add操作占位 + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮 + props.editBtnShow && dataList.push({}); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮 + selectOptions.value = dataList; + } + + /** + * 数据简单翻译-字典 + * @param data + */ + function transData(data) { + let columns = tableColumns.value; + let dictInfo = dictOptions.value; + for (let c of columns) { + const { dataIndex, customRender } = c; + if (data[dataIndex] || data[dataIndex] === 0) { + if (customRender && customRender == dataIndex) { + //这样的就是 字典数据了 可以直接翻译 + if (dictInfo[customRender]) { + data[dataIndex] = filterMultiDictText(dictInfo[customRender], data[dataIndex]); + continue; + } + } + } + // 兼容后台翻译字段 + let dictText = data[dataIndex + '_dictText']; + if (dictText) { + data[dataIndex] = dictText + } + } + } + + + //获取加载数据的查询条件 + function getLoadDataParams(){ + let params = Object.assign({pageSize: 100, pageNo: pageNo.value}, baseParam.value, searchParam.value); + return params; + } + + //设置查询条件 + function addQueryParams(text){ + if(!text){ + searchParam.value = {} + }else{ + let arr = textFieldArray.value; + let params:any[] = [] + let fields:any[] = [] + for(let i=0;i0){ + for(let item of records){ + let temp = {...item} + transData(temp); + dataList.push(temp); + } + } + return dataList; + } + + + /** + * true:数据一致;false:数据不一致 + * @param arr + * @param value + */ + function compareData(arr, value){ + if(!arr || arr.length==0){ + return false + } + let valueArray = value.split(','); + if(valueArray.length!=arr.length){ + return false; + } + let flag = true; + for(let item of arr){ + let temp = item[props.valueField]; + if(valueArray.indexOf(temp)<0){ + flag = false; + } + } + return flag; + } + + function formatData(formData){ + Object.keys(formData).map(k=>{ + if(formData[k] instanceof Array){ + formData[k] = formData[k].join(',') + } + }) + } + + function initFormData(formData, linkFieldArray, record){ + if(!record){ + record = {} + } + if(linkFieldArray && linkFieldArray.length>0){ + for(let str of linkFieldArray){ + let arr = str.split(',') + //["表单字段,表字典字段"] + let field = arr[0]; + let dictField = arr[1]; + if(!formData[field]){ + let value = record[dictField] || ''; + formData[field] = [value] + }else{ + formData[field].push(record[dictField]) + } + } + } + } + + + // 获取图片地址 + function getImageSrc(item){ + if(props.imageField){ + let url = item[props.imageField]; + // update-begin--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示 + if(typeof url === 'string') { + // 有多张图时默认取第一张 + url = url.split(',')[0] + } + // update-end--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示 + return getFileAccessHttpUrl(url); + } + return '' + } + const showImage = computed(()=>{ + if(props.imageField){ + return true; + }else{ + return false; + } + }); + + + return { + pageNo, + otherColumns, + realShowColumns, + selectOptions, + reloadTableLinkOptions, + textFieldArray, + addQueryParams, + tableColumns, + transData, + mainContentField, + loadOne, + compareData, + formatData, + initFormData, + getImageSrc, + showImage, + auths + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts new file mode 100644 index 000000000..231a85298 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts @@ -0,0 +1,69 @@ +import { FormActionType, JCodeEditor } from '/@/components/Form'; +import { ref } from 'vue'; + +export function useOnlineTest(data, methods, form: Nullable) { + // Online单元测试开关 + const aiTestMode = ref(false); + const aiTestTable = ref([]); + const aiTableList = ref([]); + + function initVirtualData() { + } + + // 自定义按钮 + function genButtons(code) { + } + + // 生成java增强 + function genEnhanceJavaData(code) { + } + + // 生成js增强 + function genEnhanceJsData(tableName, type, codeEditor: InstanceType) { + } + + // 自定义sql增强 + function genEnhanceSqlData(code, tableName) { + } + + /** + * 加载配置信息 + */ + function setTaleConfig() { + } + + function tableJsonGetHelper(pickAfter) { + console.log('表的配置信息', JSON.stringify(pickAfter)); + console.log('---------------------------------------'); + } + + /** + * json 获取小助手 + * @param fields + */ + function fieldsJsonGetHelper(fields) { + } + + function refreshCacheTableName(oldValue, newValue) { + } + + function getCacheTableName(name) { + } + + // noinspection JSUnusedGlobalSymbols + return { + aiTestMode, + aiTestTable, + aiTableList, + initVirtualData, + genButtons, + genEnhanceJavaData, + genEnhanceJsData, + genEnhanceSqlData, + setTaleConfig, + tableJsonGetHelper, + fieldsJsonGetHelper, + refreshCacheTableName, + getCacheTableName, + }; +} \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoForm.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoForm.ts new file mode 100644 index 000000000..af58fea23 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoForm.ts @@ -0,0 +1,1128 @@ +import { ref, watch, reactive, toRaw, nextTick, computed } from 'vue'; +import { FormSchema } from '/@/components/Form'; +import FormSchemaFactory from '../../auto/comp/factory/FormSchemaFactory'; +import IFormSchema from '../../auto/comp/factory/IFormSchema'; +import { JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types'; +import { duplicateCheck } from '/@/views/system/user/user.api'; +import { initDefValueConfig, initSubTableDefValueConfig } from '../../util/FieldDefVal'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { ONL_AUTH_PRE } from '../../types/onlineRender'; +import { pick } from 'lodash-es'; +import { DetailFormSchema } from '../../extend/form/useDetailForm'; +import {useExtendComponent} from './useExtendComponent' +import componentSetting from '/@/settings/componentSetting'; +import { LABELLENGTH } from '../../util/constant'; +import { useAppInject } from '/@/hooks/web/useAppInject'; + +export const LINK_DOWN = 'link_down'; +export const LINK_TABLE_FIELD = 'link_table_field'; +export const LINK_TABLE = 'link_table'; + +export interface OnlSubTab { + key: string; + properties?: any[]; + columns?: any[]; + foreignKey: string; + describe: string; + relationType: number; + requiredFields?: string[]; + order: number; + id?:string; +} +/** + * 获取实际表单的配置信息 + */ +export function useFormItems(props, onlineFormRef) { + // 添加表单组件-专门给 online 表单用 + useExtendComponent(); + + // 下拉框等组件需要通过此class作为父级container + const modalClass = props.modalClass; + // 表单渲染用到的配置 + const formSchemas = ref([]); + // 表名 + const tableName = ref(''); + // 编辑页面 数据库原数据 + const dbData = ref({}); + // 字段展示状态 + const fieldDisplayStatus = reactive({}); + const hasSubTable = ref(false); + + const subTabInfo = ref([]); + const subDataSource = ref({}); + const refMap = {}; + // 联动组件列表 + const linkDownList = ref([]); + /** + * 有表单默认值的字段 + * 表名: [{field, value, type}] + */ + const defaultValueFields = reactive({}); + // 表单栅格 + const baseColProps = ref(''); + baseColProps.value = { sm: 24, xs: 24, md: 12, lg: 12, xl: 12, xxl: 12 }; + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + const labelCol = ref({ xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }); + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + const wrapperCol = ref(null); + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + const labelWidth = ref(6 * 14 + 10); + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + + function createFormSchemas(properties: any[], required, checkOnlyFieldValue, onlineExtConfigJson = {}) { + //let properties:any[] = result.schema.properties + clearObj(defaultValueFields); + defaultValueFields[tableName.value] = []; + let subInfo: OnlSubTab[] = []; + let arr: IFormSchema[] = []; + let hideFields: string[] = []; + let dataSourceObj = {}; + let tableLinkInfo:any = {} + Object.keys(properties).map((key) => { + const item = properties[key]; + // uiSchema 无用 + //const uiItem = this.uiSchema[key];// method、formTemplate、url + if (item.view == 'tab') { + hasSubTable.value = true; + defaultValueFields[key] = []; + let temp: OnlSubTab = { + key, + // 这个foreignKey是主表的字段 + foreignKey: item['foreignKey'], + describe: item.describe, + relationType: item.relationType, + requiredFields: item.required || [], + order: item.order, + id: item.id + }; + if (item.relationType == 1) { + refMap[key] = ref(null); + temp['properties'] = item.properties; + } else { + dealSubProerties(item); + refMap[key] = ref(); + temp['columns'] = item.columns; + dataSourceObj[key] = []; + // TODO 处理子表的新增删除按钮权限 + //this.handleSubTableButtonAuth(item) + } + subInfo.push(temp); + // 记录子表按钮权限 + handleSubTableButtonAuth(key, item); + } else { + initDefValueConfig(key, item, defaultValueFields[tableName.value]); + if (item.view === LINK_DOWN) { + let array = handleLinkDown(item, key); + for (let linkDownItem of array) { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + const fItem = linkDownItem.key == key ? item : item.others?.find((item) => item.field === linkDownItem.key); + fItem && initDefValueConfig(linkDownItem.key, fItem, defaultValueFields[tableName.value]); + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + fieldDisplayStatus[linkDownItem.key] = true; + fieldDisplayStatus[linkDownItem.key + '_load'] = true; + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-75】表单label长度设置,联动组件没生效 + setFieldExtend(onlineExtConfigJson, linkDownItem); + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-75】表单label长度设置,联动组件没生效 + let temp = FormSchemaFactory.createFormSchema(linkDownItem.key, linkDownItem); + // update-begin--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + fieldDisplayStatus[linkDownItem.key + '_disabled'] = temp.disabled ?? false; + // update-end--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + if (checkOnlyFieldValue) { + temp.setOnlyValidateFun(checkOnlyFieldValue); + } + temp.isRequired(required); + temp.setFormRef(onlineFormRef); + // 联动控件 只读由第一个控件的只读状态决定 + temp.handleWidgetAttr(item); + let tempIndex = getFieldIndex(arr, linkDownItem.key); + if (tempIndex == -1) { + arr.push(temp); + } else { + arr[tempIndex] = temp; + } + } + } else { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + initDefValueConfig(key, item, defaultValueFields[tableName.value]); + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + fieldDisplayStatus[key] = true; + fieldDisplayStatus[key + '_load'] = true; + let tempIndex = getFieldIndex(arr, key); + if (tempIndex == -1) { + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + setFieldExtend(onlineExtConfigJson, item); + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + let temp = FormSchemaFactory.createFormSchema(key, item); + // update-begin--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + fieldDisplayStatus[key + '_disabled'] = temp.disabled ?? false; + // update-end--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + if (checkOnlyFieldValue) { + temp.setOnlyValidateFun(checkOnlyFieldValue); + } + temp.isRequired(required); + temp.setFormRef(onlineFormRef); + arr.push(temp); + hideFields.push(...temp.getRelatedHideFields()); + + //update-begin-author:taoyan date:2022-8-5 for: 获取他表字段信息 + //如果是他表字段获取关联信息 + if(item.view === LINK_TABLE_FIELD){ + let tempInfo = temp.getLinkFieldInfo(); + if(tempInfo){ + if(tableLinkInfo[tempInfo[0]]){ + let tableLinkInfoEle:string[] = tableLinkInfo[tempInfo[0]]; + tableLinkInfoEle.push(tempInfo[1]); + }else{ + tableLinkInfo[tempInfo[0]] = [tempInfo[1]] + } + } + } + //update-end-author:taoyan date:2022-8-5 for: 获取他表字段信息 + + } + } + //fp.checkOnlyMethod = this.$Jdebounce(this.checkOnlyFieldValue, 1000); + } + }); + // 1.对arr排序 + arr.sort(function (a, b) { + return a.order - b.order; + }); + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + const oneRowData: any = []; + (() => { + for (let i = 0, len = arr.length; i < len; i++) { + const item = arr[i]; + if (getFieldExtend(item?._data, 'isOneRow')) { + oneRowData.push(arr.splice(i, 1)[0]); + i--; + len--; + } + } + })(); + arr = [...arr,...oneRowData]; + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // 2.获取真实表单配置 + let formSchemaArray: FormSchema[] = []; + formSchemaArray.push(FormSchemaFactory.createIdField()); + let longestLabelComponet: any = null; + let isComponetRequired = false; + for (let a of arr) { + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + const curLabelLen = a.label.length; + if (longestLabelComponet) { + if (longestLabelComponet.label.length < curLabelLen) { + longestLabelComponet = a; + } else if(longestLabelComponet.label.length === curLabelLen) { + // 文字labael相同,则判断是否必填。(必填*占13像素) + if(!longestLabelComponet.required && a.required) { + longestLabelComponet = a; + } + } + } else { + longestLabelComponet = a; + } + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // update-begin--author:liaozhiyang---date:20230105---for:【TV360X-209】表单label长度设置了且字段有必填宽度计算不正确 + if (a.required) { + isComponetRequired = true; + } + // update-end--author:liaozhiyang---date:20230105---for:【TV360X-209】表单label长度设置了且字段有必填宽度计算不正确 + //update-begin-author:taoyan date:2022-8-5 for: 将他表字段的配置信息 添加至 关联字段上 + //关联记录字段设置新的配置 + if(a['view'] && a['view']==LINK_TABLE){ + if(tableLinkInfo[a.field]){ + a.setOtherInfo(tableLinkInfo[a.field]) + } + } + //update-end-author:taoyan date:2022-8-5 for: 将他表字段的配置信息 添加至 关联字段上 + //设置hidden的字段 + if (hideFields.indexOf(a.field) >= 0) { + a.isHidden(); + } + // popModal-下拉框的父级容器需要自定义,否则会被遮挡 + if(modalClass){ + a.setCustomPopContainer(modalClass) + } + // update-begin--author:liaozhiyang---date:20231222---for:【QQYUN-7515】online 下拉字典、单选组件options跟随数据库类型 + const result = a.getFormItemSchema(); + if (result.component === 'JDictSelectTag' && a?._data?.type === 'number') { + result.componentProps.stringToNumber = true; + } + // update-end-author:liaozhiyang---date:20231222---for:【QQYUN-7515】online 下拉字典、单选组件options跟随数据库类型 + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + if (props.formTemplate > 1 && getFieldExtend(a?._data, 'isOneRow')) { + result.colProps = { span: 24 }; + const colGrid = getFormItemColProps(); + const { labelCol = {} } = colGrid; + const itemLabelCol = {}; + const itemWrapperCol = {}; + Object.keys(labelCol).forEach((key) => { + if (['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key)) { + const span = labelCol[key].span; + const value = Math.round(span / props.formTemplate); + itemLabelCol[key] = { span: value }; + itemWrapperCol[key] = { span: 24 - value - 1 }; + } + }); + result.itemProps = { labelCol: itemLabelCol, wrapperCol: itemWrapperCol }; + } + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // update-begin--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + if (result.component === 'JPopup') { + result.changeEvent = 'popUpChange'; + } + // update-end--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + formSchemaArray.push(result); + } + formSchemas.value = formSchemaArray; + //return formSchemaArray; + //update-begin-author:taoyan date:2022-5-31 for: VUEN-1147 主子表 子表顺序并不是按照设置顺序排列 + subInfo.sort(function (a, b) { + return a.order - b.order; + }); + //update-end-author:taoyan date:2022-5-31 for: VUEN-1147 主子表 子表顺序并不是按照设置顺序排列 + // update-begin--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选 + subInfo.forEach((sItem) => { + const columns: any = sItem.columns; + if(sItem.columns){ + columns.forEach((cItem) => { + // update-begin--author:liaozhiyang---date:20240529---for:【TV360X-452】一对多子表popup默认多选没生效 + if (sItem.relationType == 0) { + if (['popup', 'popup_dict'].includes(cItem.type)) { + // 只有1对多才需要处理,1对1或者单表直接组件中处理了。 + let popupMulti = true; + if (cItem.fieldExtendJson) { + const fieldExtendJson = JSON.parse(cItem.fieldExtendJson); + popupMulti = fieldExtendJson.popupMulti; + } + const props = cItem.props ?? {}; + cItem.props = { ...props, multi: popupMulti }; + } + } + // update-end--author:liaozhiyang---date:20240529---for:【TV360X-452】一对多子表popup默认多选没生效 + // update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 + if (cItem.type === 'date' && cItem.fieldExtendJson) { + const fieldExtendJson = JSON.parse(cItem.fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + Object.assign(cItem, { picker: fieldExtendJson.picker }); + } + } + // update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 + }); + } + }); + // update-end--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选 + subTabInfo.value = subInfo; + subDataSource.value = dataSourceObj; + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + if (onlineExtConfigJson.formLabelLengthShow && onlineExtConfigJson.formLabelLength) { + // 14是文字size,24是间隙 + labelWidth.value = onlineExtConfigJson.formLabelLength * 14 + 10 + (+`${isComponetRequired ? 13 : 0}`); + wrapperCol.value = null; + } else { + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // num这个值是label没截取之前真实长度,如果大于初始值则还是得使用初始值 + if (longestLabelComponet) { + let realLabelLen = longestLabelComponet.label.length; + realLabelLen = realLabelLen > LABELLENGTH ? LABELLENGTH : realLabelLen; + const required = longestLabelComponet.required; + const num = realLabelLen * 14 + 10 + (+`${required ? 13 : 0}`); + labelWidth.value = num; + } + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + } + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + } + + watch( + fieldDisplayStatus, + (val) => { + let ref = onlineFormRef.value; + let arr: any[] = []; + let map = toRaw(val); + Object.keys(map).map((k) => { + if (k.endsWith('_load')) { + } else { + let item = { + field: k, + show: map[k], + }; + let loadKey = k + '_load'; + if (map.hasOwnProperty(loadKey)) { + item['ifShow'] = map[loadKey]; + } + // update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-8537】js增强,控制表单字段的禁用 + let disabledKey = k + '_disabled'; + if (map.hasOwnProperty(disabledKey)) { + item['dynamicDisabled'] = () => { + return map[disabledKey]; + }; + } + // update-end--author:liaozhiyang---date:20240321---for:【QQYUN-8537】js增强,控制表单字段的禁用 + arr.push(item); + } + }); + if (ref) { + ref.updateSchema(arr); + } + }, + { immediate: false } + ); + + function dealSubProerties(subInfo) { + useOnlineVxeTableColumns(subInfo, (column)=>{ + initSubTableDefValueConfig(column, defaultValueFields[subInfo.key]); + }) + } + /* + 2024-03-06 + liaozhiyang + 表单中的扩展参数的labelLength设置为onlineExtConfigJson.formLabelLength + */ + function setFieldExtend(onlineExtConfigJson, data, key = 'labelLength') { + const { formLabelLengthShow, formLabelLength } = onlineExtConfigJson; + if (formLabelLengthShow && formLabelLength) { + let fieldExtendJson = data?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + fieldExtendJson[key] = formLabelLength; + } else { + fieldExtendJson = { [key]: formLabelLength }; + } + data.fieldExtendJson = JSON.stringify(fieldExtendJson); + } + } + + /* + 2024-01-05 + liaozhiyang + 获取扩展参数 + */ + function getFieldExtend(data: any = {}, key: string) { + let fieldExtendJson = data?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + return fieldExtendJson[key]; + } + } + + //监听主表表单的formTemplate + watch( + () => props.formTemplate, + () => { + //重新渲染表单 + const result = getFormItemColProps() + baseColProps.value = result.baseColProps; + labelCol.value = result.labelCol; + wrapperCol.value = result.wrapperCol; + }, + { immediate: true } + ); + + function getFormItemColProps() { + let temp = props.formTemplate; + // update-begin--author:liaozhiyang---date:20240105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + // const form: any = componentSetting.form || {}; + // const { labelCol = {} } = form; + // const { wrapperCol = {} } = form; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + if (temp == 2) { + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + return { + baseColProps: { sm: 24, xs: 24, md: 12, lg: 12, xl: 12, xxl: 12 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 19 }, md: { span: 19 }, lg: { span: 19 }, xl: { span: 19 }, xxl: { span: 19 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + } else if (temp == 3) { + return { + baseColProps: { sm: 24, xs: 24, md: 8, lg: 8, xl: 8, xxl: 8 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 6 }, md: { span: 6 }, lg: { span: 6 }, xl: { span: 6 }, xxl: { span: 6 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 17 }, md: { span: 17 }, lg: { span: 17 }, xxl: { span: 17 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + } else if (temp == 4) { + return { + baseColProps: { sm: 24, xs: 24, md: 6, lg: 6, xl: 6, xxl: 6 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 18 }, md: { span: 18 }, lg: { span: 18 }, xl: { span: 18 }, xxl: { span: 18 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + } else { + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + return { + baseColProps: { sm: 24, xs: 24, md: 24, lg: 24, xl: 24, xxl: 24 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol:{ xs: { span: 24 }, sm: { span: 18 }, md: { span: 18 }, lg: { span: 18 }, xl: { span: 18 }, xxl: { span: 18 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + } + // update-end--author:liaozhiyang---date:20240105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + } + // 唯一校验 + function checkOnlyFieldValue(rule, value) { + return new Promise((resolve) => { + if (!value) { + resolve(''); + } + //对于视图 需要将表名后的$+数字 取掉 + let realTableName = tableName.value.replace(/\$\d+/, ''); + let param = { + tableName: realTableName, + fieldName: rule.field, + fieldVal: value, + }; + let formData: any = dbData.value; + if (formData.id) { + param['dataId'] = formData.id; + } + //console.log("唯一校验---》",param) + duplicateCheck(param) + .then((res) => { + if (res.success) { + resolve(''); + } else { + resolve(res.message); + } + }) + .catch((msg) => { + resolve(msg); + }); + }); + } + + /** + * 有些数据是数组格式的 强转成字符串 + */ + function changeDataIfArray2String(data) { + Object.keys(data).map((k) => { + if (data[k]) { + if (data[k] instanceof Array) { + data[k] = data[k].join(','); + } + } + }); + return data; + } + + return { + formSchemas, + defaultValueFields, + tableName, + dbData, + checkOnlyFieldValue, + createFormSchemas, + fieldDisplayStatus, + subTabInfo, + hasSubTable, + subDataSource, + baseColProps, + changeDataIfArray2String, + linkDownList, + refMap: refMap, + labelCol, + wrapperCol, + labelWidth, + }; +} + +/** + * 处理online子表 jvxeTable的列配置信息 + */ +export function useOnlineVxeTableColumns(subInfo, callback?){ + // 新旧jvxetable 列的类型不一致 + const vxeTypeMap = { + inputNumber: 'input-number', + sel_depart: 'depart-select', + sel_user: 'user-select', + list_multi: 'select-multiple', + input_pop: 'textarea', + sel_search: 'select-search', + 'select-dict-search': 'selectDictSearch', + }; + + //一对多子表如果为单选按钮改为下拉框 + subInfo.columns.forEach((column) => { + if (column.type === 'radio') { + column.type = 'select'; + } else if (vxeTypeMap[column.type]) { + column.type = vxeTypeMap[column.type]; + } else if (column.type === 'popup') { + handleSubPopup(column); + } else if (column.type === 'link_table') { + handleSubLinkTable(column, subInfo.columns); + } else if (column.type === 'link_table_field') { + // update-begin--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + column.type = 'input'; + column.flag = 'link-table-field'; + column.props = { + ...(column.props ?? {}), + disabled: true, + }; + // update-end--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + } + // 部门树选择组件需要设置 父子节点不关联 + if (column.type === 'depart-select') { + column['checkStrictly'] = true; + } + // 子表用户选择 控制是否多选 + if (column.type === 'user-select') { + handleSubUserSelect(column); + } + if (column.type === 'pca') { + column.width = '230px'; + } + // update-begin--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + // 自定义树 + if (column.type === 'sel_tree') { + const { dictTable, dictCode, dictText } = column; + const [id, pid, name, child] = dictText.split(','); + column.type = 'sel-tree'; + column.dict = `${dictTable},${name},${id}`; + column.pidField = pid; + column.pidValue = dictCode ?? '0'; + column.hasChildField = child; + delete column.dictText; + delete column.dictCode; + delete column.dictTable; + } + // 分类字典书 + if (column.type === 'cat_tree') { + const { dictCode } = column; + column.type = 'cat-tree'; + column.pcode = dictCode ?? '0'; + delete column.dictCode; + } + // update-end--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + //update-begin-author:taoyan date:2022-4-24 for: VUEN-855 对多子表 文件、图片右侧少个边框 + if ((column.width == 120 || column.width == '120px') && (column.type == 'image' || column.type == 'file')) { + column.width = '130px'; + } + //如果没有宽度 默认设置一个宽度 + if (!column.width) { + column.width = '200px'; + } + if(callback){ + callback(column) + } + //update-end-author:taoyan date:2022-4-24 for: VUEN-855 对多子表 文件、图片右侧少个边框 + }); + + // 子表popup特殊处理 + function handleSubPopup(column) { + let { destFields, orgFields } = column; + let fieldConfig: any[] = []; + if (!destFields || destFields.length == 0) { + } else { + let arr1 = destFields.split(','); + let arr2 = orgFields.split(','); + for (let i = 0; i < arr1.length; i++) { + fieldConfig.push({ + target: arr1[i], + source: arr2[i], + }); + } + } + column.fieldConfig = fieldConfig; + } + + // 子表 用户选择特殊处理 + function handleSubUserSelect(column) { + let str = column.fieldExtendJson; + let isRadioSelection = false; + if (str) { + try { + let json = JSON.parse(str); + if (json.multiSelect === false) { + isRadioSelection = true; + } + } catch (e) { + console.log('子表获取用户组件的扩展配置出现错误', e); + } + } + column.isRadioSelection = isRadioSelection; + } + /** + * 20260317 + * 【QQYUN-9441】online一对多加上关联记录和他表字段 + * liaozhiang + * */ + function handleSubLinkTable(column, columns) { + column.type = 'link-table'; + column.tableName = column.dictTable || ''; + column.valueField = column.dictCode || 'id'; + column.textField = column.dictText || ''; + column.multi = false; + column.linkFields = []; + columns.forEach(item => { + if (item.type === 'link_table_field' && item.dictTable === column.key) { + column.linkFields.push(`${item.key},${item.dictText}`); + } + }); + delete column.dictTable; + delete column.dictCode; + delete column.dictText; + let str = column.fieldExtendJson; + if (str) { + try { + let json = JSON.parse(str); + if (json.multiSelect === true) { + column.multi = true; + } + } catch (e) { + console.log('子表获取关联记录组件的扩展配置出现错误', e); + } + } + if (!column.width || column.width === '200px') { + column.width = '240px'; + } + } + +} + +/*** + * 表单上下文 + */ +export function useOnlineFormContext(props) { + let that = {}; + const CONTEXT_DESCRIPTION = { + addSubRows: ' 一对多子表,新增自定义行', + changeOptions: ' 改变下拉框选项', + clearSubRows: ' 清空一对多子表行', + clearThenAddRows: ' 清空一对多子表行,然后新增自定义行', + executeMainFillRule: ' 刷新主表的增值规制值', + executeSubFillRule: ' 刷新子表的增值规制值', + getFieldsValue: ' 获取表单控件的值', + getSubTableInstance: ' 获取子表实例', + isUpdate: '

判断是否为编辑模式', + loading: '

页面加载状态', + onlineFormRef: '

当前表单ref对象', + refMap: '

子表ref对象map', + setFieldsValue: ' 设置表单控件的值', + sh: '

表单控件的显示隐藏状态', + subActiveKey: '

子表激活tab,对应子表表名', + subFormHeight: '

一对一子表表单高度', + submitFlowFlag: '

是否提交流程状态', + subTableHeight: '

一对多子表表格高度', + tableName: '

当前表名', + triggleChangeValues: ' 修改多个表单值', + triggleChangeValue: ' 修改表单值', + updateSchema: ' 修改表单控件配置', + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8350】js增强根据主表限制子表options + changeSubTableOptions: ' 改变一对多子表下拉框选项', + changeSubFormbleOptions: ' 改变一对一子表下拉框选项', + // update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8350】js增强根据主表限制子表options + // update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options + changeRemoteOptions:' 改变远程下拉框选项', + // update-end--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options + // update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1754】js增强-提交表单并且发起流程 + submitFormAndFlow: ' 提交表单且发起流程', + // update-end--author:liaozhiyang---date:20240705---for:【TV360X-1754】js增强-提交表单并且发起流程 + }; + const onlineFormContext = new Proxy(CONTEXT_DESCRIPTION, { + get(_target: any, prop: string): any { + return Reflect.get(that, prop); + }, + }); + + function addObject2Context(prop, object) { + that[prop] = object; + } + + function resetContext(context) { + Object.keys(context).map((k) => { + that[k] = context[k]; + }); + } + addObject2Context('$nextTick', nextTick); + addObject2Context('addObject2Context', addObject2Context); + + // 自定义按钮 + const createBIButtonCfg = (btnKey, defBtn) => computed(() => { + const {buttonSwitch} = props + const cfg = { + enabled: true, + buttonIcon: defBtn[0], + buttonName: defBtn[1], + } + if (buttonSwitch?.[btnKey] === false) { + cfg.enabled = false + return cfg + } + const {cgBIBtnMap} = props + return cgBIBtnMap?.[btnKey] ? cgBIBtnMap[btnKey] : cfg + }) + + const getSubAddBtnCfg = createBIButtonCfg('form_sub_add', ['ant-design:plus-outlined', '新增']) + const getSubRemoveBtnCfg = createBIButtonCfg('form_sub_batch_delete', ['ant-design:minus-outlined', '删除']) + const getSubOpenAddBtnCfg = createBIButtonCfg('form_sub_open_add', ['ant-design:expand-alt-outlined', '新增']) + const getSubOpenEditBtnCfg = createBIButtonCfg('form_sub_open_edit', ['ant-design:form-outlined', '']) + + return { + onlineFormContext, + addObject2Context, + resetContext, + + getSubAddBtnCfg, + getSubRemoveBtnCfg, + getSubOpenAddBtnCfg, + getSubOpenEditBtnCfg, + }; +} + +/** + * 找联动组件 + * @param properties + */ +export function handleLinkDown(item, field) { + const { + config: { table, key, txt, linkField, idField, pidField, condition }, + others, + order, + title, + } = item; + let commonProp = { + dictTable: table, + dictText: txt, + dictCode: key, + pidField: pidField, + idField: idField, + view: LINK_DOWN, + type: item.type, + }; + let array: any = []; + let main = { + key: field, + title, + order, + condition, + origin: true, + ...commonProp, + }; + + if (linkField && linkField.length > 0) { + let fields = linkField.split(','); + main['next'] = fields[0]; + for (let i = 0; i < fields.length; i++) { + for (let o of others) { + if (o.field == fields[i]) { + let temp = { + key: o.field, + title: o.title, + order: o.order, + origin: false, + ...commonProp, + }; + if (i + 1 < fields.length) { + temp['next'] = fields[i + 1]; + } + array.push(temp); + } + } + } + } + array.push(main); + //let ls = linkDownList.value + // ls.push(...array) + // linkDownList.value = ls; + return array; +} + +/** + * 获取 字段的索引 + * @param arr + * @param key + */ +export function getFieldIndex(arr: IFormSchema[], key: string) { + let index = -1; + for (let i = 0; i < arr.length; i++) { + let item = arr[i]; + if (item.field === key) { + index = i; + break; + } + } + return index; +} + +/** + * 轮询获取 ref 对象的值,获取为true或是真实存在 就执行下一步逻辑,可用于判断状态或组件的加载是否完成 + * @param componentRef + */ +export function getRefPromise(componentRef) { + return new Promise((resolve) => { + (function next() { + let ref = componentRef.value; + if (ref) { + resolve(ref); + } else { + setTimeout(() => { + next(); + }, 100); + } + })(); + }); +} + +function clearObj(obj) { + Object.keys(obj).map((k) => { + delete obj[k]; + }); +} + +//update-begin-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制 +/** + * 记录子表按钮权限-隐藏的按钮编码 + */ +const permissionStore = usePermissionStore(); +function handleSubTableButtonAuth(tableName, item) { + let arr = item.hideButtons; + let code = ONL_AUTH_PRE + tableName + ':'; + if (!arr) { + arr = []; + } + permissionStore.setOnlineSubTableAuth(code, arr); +} +//update-end-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制 + + + +/** + * 获取 DetailFormSchema[online用] + */ +export function getDetailFormSchemas(props) { + const detailFormSchemas = ref([]); + const refMap = {}; + const showStatus = reactive({ + + }); + const hasSubTable = ref(false); + const subTabInfo = ref([]); + const subDataSource = ref({}); + const { getIsMobile } = useAppInject(); + const formSpan = computed(() => { + let temp = props.formTemplate; + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-82】详情页移动端只显示一列 + if (getIsMobile.value) { + return 24; + } + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-82】详情页移动端只显示一列 + if (temp == '2') { + return 12; + } else if (temp == '3') { + return 8; + } else if (temp == '4') { + return 6; + } else { + return 24; + } + }); + + function createFormSchemas(properties: any[]) { + //let properties:any[] = result.schema.properties + let subInfo: OnlSubTab[] = []; + console.log('111', properties); + let arr: DetailFormSchema[] = []; + let dataSourceObj = {}; + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + const tableLinkInfo: any = {}; + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + Object.keys(properties).map((key) => { + const item = properties[key]; + // uiSchema 无用 + //const uiItem = this.uiSchema[key];// method、formTemplate、url + if (item.view == 'tab') { + hasSubTable.value = true; + let temp: OnlSubTab = { + key, + // 这个foreignKey是主表的字段 + foreignKey: item['foreignKey'], + describe: item.describe, + relationType: item.relationType, + requiredFields: item.required || [], + order: item.order, + }; + if (item.relationType == 1) { + refMap[key] = ref(null); + temp['properties'] = item.properties; + } else { + dealSubProerties(item); + refMap[key] = ref(); + temp['columns'] = item.columns; + dataSourceObj[key] = []; + showStatus[key] = false; + } + subInfo.push(temp); + } else { + if (item.view === LINK_DOWN) { + let array = handleLinkDown(item, key); + for (let linkDownItem of array) { + let tempIndex = getFieldIndex(arr, linkDownItem.key); + let temp = { + field: linkDownItem.key, + label: linkDownItem.title, + view: linkDownItem.view, + order: linkDownItem.order, + dictTable: linkDownItem.dictTable, + linkField: linkDownItem.linkField||'', + }; + if (tempIndex == -1) { + arr.push(temp); + } else { + arr[tempIndex] = temp; + } + } + } else if (item.view == 'hidden') { + //隐藏的不处理 + } else { + let tempIndex = getFieldIndex(arr, key); + if (tempIndex == -1) { + let temp = Object.assign( + { + field: key, + label: item.title, + }, + pick(item, ['view', 'order', 'fieldExtendJson', 'dictTable', 'dictText', 'dictCode', 'dict']) + ); + if (item.view == 'file') { + temp['span'] = 24; + temp['isFile'] = true; + } + if (item.view == 'image') { + temp['span'] = 24; + temp['isImage'] = true; + } + if (item.view == 'link_table') { + // 判断是不是卡片 + if(item.fieldExtendJson){ + try{ + let json = JSON.parse(item.fieldExtendJson); + if(json.showType!='select'){ + // temp['span'] = 24; + temp['isCard'] = true; + } + if(json.multiSelect==true){ + temp['multi'] = true; + } + }catch (e) { + console.error('解析json字符串出错', item.fieldExtendJson) + } + } + } + if (item.view == 'umeditor' || item.view == 'markdown') { + temp['isHtml'] = true; + temp['span'] = 24; + } + arr.push(temp); + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + if (item.view === 'link_table_field') { + if (!tableLinkInfo[item.dictTable]) { + tableLinkInfo[item.dictTable] = []; + } + tableLinkInfo[item.dictTable].push(`${key},${item.dictText}`); + } + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + } + } + } + }); + // 1.对arr排序 + arr.sort(function (a, b) { + return a.order - b.order; + }); + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + arr.forEach((item) => { + if (item.view === 'link_table' && tableLinkInfo[item.field]) { + item['linkFields'] = tableLinkInfo[item.field]; + } + }); + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + // 2.对子表排序 + subInfo.sort(function (a, b) { + return a.order - b.order; + }); + subTabInfo.value = subInfo; + for (let i = 0; i < arr.length; i++) { + let temp = arr[i]; + if (temp.isFile === true || temp.isImage === true || temp.isHtml === true) { + if (i > 0) { + let last = arr[i - 1]; + let span = last.span || formSpan.value; + last.span = span; + } + } + } + detailFormSchemas.value = arr; + subDataSource.value = dataSourceObj; + console.log('adadad', arr); + } + + function dealSubProerties(subInfo) { + useOnlineVxeTableColumns(subInfo); + } + + function getFieldIndex(arr: DetailFormSchema[], key: string) { + let index = -1; + for (let i = 0; i < arr.length; i++) { + let item = arr[i]; + if (item.field === key) { + index = i; + break; + } + } + return index; + } + + function handleLinkDown(item, field){ + let all:any[] = [] + const { + config: { table, key, txt, linkField }, + order, + title, + others, + } = item; + let obj = { + table, key, txt + } + let temp = { + view: 'link_down', + order, + title, + dictTable: JSON.stringify(obj) + }; + all.push(Object.assign({}, {linkField, key: field}, temp)); + if(linkField){ + let arr = linkField.split(','); + for(let a of arr){ + let title = '' + for(let o of others){ + if(o.field==a){ + title = o.title + } + } + all.push(Object.assign({}, {key: a}, temp, {title})); + } + } + return all; + } + return { + detailFormSchemas, + hasSubTable, + subTabInfo, + refMap, + showStatus, + createFormSchemas, + formSpan, + subDataSource, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts new file mode 100644 index 000000000..5194a0cfb --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts @@ -0,0 +1,34 @@ + +import { nextTick } from 'vue'; + +/*** + * 表单上下文 + */ +export function useOnlineFormDetailContext() { + const that = {}; + const CONTEXT_DESCRIPTION = { + setFieldsValue: ' 设置表单控件的值', + getFieldsValue: ' 获取表单控件的值', + sh: '

表单控件的显示隐藏状态', + isUpdate: '

判断是否为编辑模式', + isDetail: '

判断是否为详情模式', + }; + const onlineFormDetailContext = new Proxy(CONTEXT_DESCRIPTION, { + get(_target: any, prop: string): any { + return Reflect.get(that, prop); + }, + }); + + function addObject2Context(prop, object) { + that[prop] = object; + } + + function resetContext(context) { + Object.keys(context).map((k) => { + that[k] = context[k]; + }); + } + addObject2Context('$nextTick', nextTick); + addObject2Context('addObject2Context', addObject2Context); + return { onlineFormDetailContext, addObject2Context, resetContext }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoModal.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoModal.ts new file mode 100644 index 000000000..242b6f95e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useAutoModal.ts @@ -0,0 +1,377 @@ +import { useModalInner } from '/@/components/Modal'; +import { computed, nextTick, reactive, ref, unref } from 'vue'; +import { getRefPromise } from '../../hooks/auto/useAutoForm'; +import { defHttp } from '/@/utils/http/axios'; +import {ONL_FORM_TABLE_NAME} from '../../types/onlineRender' +import { useAppInject } from '/@/hooks/web/useAppInject'; +/** + * 创建online表单弹窗用 + */ +export function useAutoModal(isBpm?: boolean, { emit } = {} as any, callback?:any) { + const onlineFormCompRef = ref(null); + // 是否隐藏确认按钮 + const disableSubmit = ref(false); + //表单风格 1列 2列 3列 决定了弹框的宽度 + const formTemplate = ref(1); + // 自定义按钮 + const cgButtonList = ref([]); + // js增强 + //const enhanceJsObject = ref('') + // 表单是否渲染完成 + const formRendered = ref(false); + // 表单弹框最小宽度 取决于扩展配置 + const modalMinWidth = ref(0); + // 判断是否是树的表单- 【VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的】 + const isTreeForm = ref(false); + // 如果是树,父id字段对应的字段名-【VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的】 + const pidFieldName = ref(''); + // VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + const submitLoading = ref(false); + // 是否是修改页面 + const isUpdate = ref(false); + // 是否是单表 + const single = ref(true); + // extConfigJson + const extConfigJson = reactive({}); + // 显示子表- 详情页面modal 也会调用此hook,给详情页面用 + const showSub = ref(true); + const customTitle = ref('') + // 表单保存完后是否关闭modal + const successThenClose = ref(true); + // 提示是否保存 + const topTipVisible = ref(false) + // 弹窗高度控制 + const { popModalFixedWidth, resetBodyStyle, popBodyStyle } = useFixedHeightModal(); + // 没有编辑权限: 默认false + const FORM_DISABLE_UPDATE = ref(false); + // 主题模板类型 + const themeTemplate = ref(''); + + const { getIsMobile } = useAppInject(); + const modalObject = { + handleOpenModal: (_data) => {}, + }; + + //评论区域参数 + const tableId = ref('') + const tableName = ref('') + const formDataId = ref('') + const enableComment = ref(false); + let onlineExtConfig = {} + + //弹框标题 + const title = computed(() => { + let temp = customTitle.value; + if(temp){ + return temp; + } + if (unref(disableSubmit) === true) { + return '详情'; + } + if (unref(isUpdate) === true) { + return '编辑'; + } + return '新增'; + }); + + // 弹框显示 触发onlineFormCompRef---show + const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { + customTitle.value = ''; + topTipVisible.value = false; + if (isBpm === true) { + await modalObject.handleOpenModal(data); + } else { + await handleOpenOnlineModal(data); + } + // 调整modal宽高 + resetBodyStyle(); + if(callback){ + callback(); + } + }); + + /** + * 用于控制关联记录的字段 在列表上的弹窗权限,若无编辑权限,只能打开详情页面 + */ + const loadItemSuccess = ref(false); + async function getFormStatus(){ + await getRefPromise(loadItemSuccess); + return FORM_DISABLE_UPDATE.value; + } + + + async function handleOpenOnlineModal(data) { + setModalProps({ confirmLoading: false }); + isUpdate.value = data.isUpdate; + disableSubmit.value = data.disableSubmit || false; + // href跳转的不需要查看子表 + if(data?.hideSub===true){ + showSub.value = false; + } + // 设置标题,支持从传入参数读取 + if(data?.title){ + customTitle.value = data.title; + } + if(data?.record){ + formDataId.value = data.record.id; + }else{ + formDataId.value = '' + } + await nextTick(async () => { + await getRefPromise(formRendered); + + //必须等formRendered之后才能调用 + handleCommentConfig(); + await onlineFormCompRef.value.show(data?.isUpdate, data?.record, data?.param); + }); + } + + // 渲染完成改变状态 + function renderSuccess(extConfig) { + formRendered.value = true; + modalMinWidth.value = extConfig.modalMinWidth; + if (extConfig.modelFullscreen == 1) { + //如果全屏 + setModalProps({ defaultFullscreen: true }); + } else { + setModalProps({ defaultFullscreen: false }); + } + onlineExtConfig = extConfig + // update-begin--author:liaozhiyang---date:20230327---for:【QQYUN-8644】移动端效果关闭聊天窗口(详情页) + if (getIsMobile.value) { + onlineExtConfig['commentStatus'] = 0; + } + // update-end--author:liaozhiyang---date:20230327---for:【QQYUN-8644】移动端效果关闭聊天窗口(详情页) + } + + // 评论区域相关配置 + function handleCommentConfig(){ + let dataIdValue = formDataId.value; + //如果有评论配置 且 编辑/详情页面 才需要开启评论 + if(onlineExtConfig['commentStatus'] == 1 && dataIdValue){ + enableComment.value = true; + setModalProps({ defaultFullscreen: true }); + }else{ + enableComment.value = false; + } + } + + const singleWidth = 800; + const one2ManyWidth = 1100; + const modalWidth = computed(() => { + // 不同的列数展示不同的宽度 + let diff = 200 * (formTemplate.value - 1); + // 基值加上阈值 + let width = (!unref(single) ? one2ManyWidth : singleWidth) + diff; + // 文本太长时,会遮挡页面【issues/I44F0R】 + width = calcModalMixWidth(width); + let minWidth = modalMinWidth.value; + console.log({ minWidth }); + //判断计算出来的宽度 是不是比扩展配置中的宽度参数小 如果是取扩展配置的参数 + if (minWidth && width < minWidth) { + width = minWidth; + } + console.log({ width }); + return width; + }); + + /** 计算弹窗最小宽度 */ + function calcModalMixWidth(width) { + let minWidth = extConfigJson.modalMinWidth; + if (minWidth != null && minWidth !== '') { + try { + minWidth = Number.parseInt(minWidth); + if (width < minWidth) { + return minWidth; + } + } catch { + console.warn('error modalMinWidth value: ', minWidth); + } + } + return width; + } + + // 自定义按钮 增强触发事件 + function handleCgButtonClick(optType, buttonCode) { + onlineFormCompRef.value.handleCgButtonClick(optType, buttonCode); + } + + function handleSubmit() { + //update-begin-author:taoyan date:2022-5-25 for: VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + submitLoading.value = true; + setTimeout(() => { + submitLoading.value = false; + }, 1500); + //update-end-author:taoyan date:2022-5-25 for: VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + onlineFormCompRef.value.handleSubmit(); + } + + function handleCancel() { + closeModal(); + } + + function loadFormItems(id, params={}) { + let url = `/online/cgform/api/getFormItem/${id}`; + return new Promise((resolve, reject) => { + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('表单结果》》modal:', res); + if (res.success) { + resolve(res.result); + } else { + reject(res.message); + } + }) + .catch(() => { + reject(); + }); + }); + } + + async function handleFormConfig(id, params, callBack?, taskId?, currentTableName?) { + // -update-begin--author:liaozhiyang---date:20240613---for:【TV360X-1000】流程一对多走流程的接口 + let result: any = null; + if (taskId && currentTableName) { + const url = `/online/cgform/api/getFormItemBytbname/${currentTableName}`; + const params = { taskId }; + result = await defHttp.get({ url, params }); + } else { + result = await loadFormItems(id, params); + } + // -update-end--author:liaozhiyang---date:20240613---for:【TV360X-1000】流程一对多走流程的接口 + // modal页面只处理按钮、JS增强、弹框宽度 + let dataFormTemplate = result.head.formTemplate; + formTemplate.value = dataFormTemplate ? Number(dataFormTemplate) : 1; + cgButtonList.value = result.cgButtonList; + isTreeForm.value = result.head.isTree === 'Y'; + pidFieldName.value = result.head.treeParentIdField || ''; + tableId.value = result.head.id; + tableName.value = result.head.tableName; + themeTemplate.value = result.head.themeTemplate; + //enhanceJsObject.value = initCgEnhanceJs(result.enhanceJs) + if(result['form_disable_update']===true){ + FORM_DISABLE_UPDATE.value = true + }else{ + FORM_DISABLE_UPDATE.value = false; + } + loadItemSuccess.value = true; + emit && emit('formConfig', result); + // -update-begin--author:liaozhiyang---date:20230823---for:【QQYUN-6305】tab主题一对多-- + callBack && callBack(result); + // -update-end--author:liaozhiyang---date:20230823---for:【QQYUN-6305】tab主题一对多-- + await nextTick(async () => { + let myForm = (await getRefPromise(onlineFormCompRef)) as any; + await myForm.createRootProperties(result); + }); + } + + /** + * 表单保存完后的事件 + */ + function handleSuccess(formData) { + // 将表名设置到数据中 + formData[ONL_FORM_TABLE_NAME] = tableName.value; + emit('success', formData); + if(successThenClose.value == true){ + closeModal(); + }else{ + // 不关闭弹窗 提示成功 + } + //恢复默认值 + topTipVisible.value = false; + successThenClose.value = true; + } + + /** + * modal关闭事件 + */ + function onCloseEvent(){ + if(onlineFormCompRef.value){ + onlineFormCompRef.value.onCloseModal(); + } + // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-1305】打开评论编辑弹窗会全屏,关闭弹窗时把全屏去掉,否者会影响新增弹窗 + if (isUpdate.value) { + const extConfig: any = onlineExtConfig ?? {}; + if (extConfig.commentStatus == 1) { + setModalProps({ defaultFullscreen: false }); + } + } + // update-end--author:liaozhiyang---date:20240618---for:【TV360X-1305】打开评论编辑弹窗会全屏,关闭弹窗时把全屏去掉,否者会影响新增弹窗 + } + + return { + // modal + title, + modalWidth, + registerModal, + closeModal, + modalObject, + onCloseEvent, + + // 自定义按钮 + cgButtonList, + handleCgButtonClick, + + // 提交/关闭按钮 + disableSubmit, + handleSubmit, + submitLoading, + handleCancel, + successThenClose, + handleSuccess, + topTipVisible, + + //表单 + handleFormConfig, + onlineFormCompRef, + formTemplate, + isTreeForm, + pidFieldName, + renderSuccess, + formRendered, + isUpdate, + showSub, + themeTemplate, + + // 评论区域参数 + tableId, + tableName, + formDataId, + enableComment, + popBodyStyle, + popModalFixedWidth, + getFormStatus + }; +} + + +/** + * 使用固定高度的modal + */ +export function useFixedHeightModal() { + const minWidth = 800; + const popModalFixedWidth = ref(800); + let tempWidth = window.innerWidth - 300; + if(tempWidth < minWidth){ + tempWidth = minWidth; + } + popModalFixedWidth.value = tempWidth; + + // 弹窗高度控制 + const popBodyStyle = ref({}); + function resetBodyStyle(){ + let height = window.innerHeight - 210; + popBodyStyle.value = { + height: height+'px', + overflowY: 'auto' + } + } + + return { + popModalFixedWidth, + popBodyStyle, + resetBodyStyle + } +} + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useCustomHook.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useCustomHook.ts new file mode 100644 index 000000000..ccda281f5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useCustomHook.ts @@ -0,0 +1,110 @@ +import * as vue from 'vue'; +import * as UTIL_CACHE from '/@/utils/cache'; +import * as UTIL_AXIOS from '/@/utils/http/axios'; +import * as HOOK_MESSAGE from '/@/hooks/web/useMessage'; +import { randomString } from '/@/utils/common/compUtils'; +import * as HOOK_USERINFO from '/@/store/modules/user'; +import * as UTIL_AUTH from "/@/utils/auth"; + +// 在这里定义JS增强里可以使用的内容 +const $exports = { + vue, + '@': { + hooks: { + // 调用示例:@/hooks/useMessage + useMessage: HOOK_MESSAGE, + useUserStore: HOOK_USERINFO + }, + utils: { + // 调用示例:@/utils/axios + axios: UTIL_AXIOS, + cache: UTIL_CACHE, + auth: UTIL_AUTH, + }, + }, +}; + +/** + * 用于处理js增强中自定义的hook代码 + * 增强定义方法:useCustomHook(),建议不要使用双引号 + * 其他导出对象 + * @param otherExports + */ +export function useCustomHook(otherExports?: Recordable, context?: any) { + const assignExports = Object.assign({}, $exports, otherExports); + + /** + * 自定义 import 方法 + * @param path 引用路径 + */ + function doImport(path: string) { + if (path != null && path != '') { + let paths = path.toString().split('/'); + let result = assignExports[paths[0]]; + for (let i = 1; i < paths.length; i++) { + result = result[paths[i]]; + } + return result; + } + return null; + } + + function doExport() {} + + /** + * 执行JS增强代码 + * @param code 要执行的代码 + */ + function executeJsEnhanced(code: string, row?) { + // 为了避免方法名冲突,所以使用随机方法名 + let randomKey = randomString(6); + // let importKey = '__import_' + randomKey + //let importKey = 'customImport' + let exportKey = '__export_' + randomKey; + // 替换 import 关键字 + //code = replaceImportKey(code, importKey) + + //update-begin-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + if(row){ + const executeCode = `return function (row, customImport, ${exportKey}) {"use strict"; ${code}}`; + console.group('executeJsEnhanced'); + console.log(executeCode); + console.groupEnd(); + const fun = new Function(executeCode)(); + fun.call(context, row, doImport, doExport); + }else{ + const executeCode = `return function (customImport, ${exportKey}) {"use strict"; ${code}}`; + console.group('executeJsEnhanced'); + console.log(executeCode); + console.groupEnd(); + const fun = new Function(executeCode)(); + fun.call(context, doImport, doExport); + } + //update-end-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + + } + + /** + * 替换 import 关键字 + * @param code + * @param fnKey import 方法的key + */ + /* function replaceImportKey(code: string, fnKey: string) { + let lines = code.split('\n') + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim() + if (line.startsWith('import ')) { + let regexp = /import (.*) from (.*)/g + lines[i] = line.replace(regexp, `const $1 = ${fnKey}($2)`) + } + } + return lines.join('\n') + }*/ + + return { + executeJsEnhanced, + }; +} + +/**获取函数体的内容作为字符串*/ +export const GET_FUN_BODY_REG = /(?:\/\*[\s\S]*?\*\/|\/\/.*?\r?\n|[^{])+\{([\s\S]*)\}$/; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useEnhance.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useEnhance.ts new file mode 100644 index 000000000..e1e28b075 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useEnhance.ts @@ -0,0 +1,142 @@ +/** + * js增强 + */ +import { reactive } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { _eval } from '/@/utils'; +import { useMessage } from '/@/hooks/web/useMessage'; + +export function useEnhance(onlineTableContext, isList = true) { + let EnhanceJS = reactive({}); + + const getAction = (url, params) => { + return defHttp.get({ url: url, params }, { isTransformResponse: false }); + }; + + const postAction = (url, params) => { + return defHttp.post({ url: url, params }, { isTransformResponse: false }); + }; + + const putAction = (url, params) => { + return defHttp.put({ url: url, params }, { isTransformResponse: false }); + }; + + const deleteAction = (url, params) => { + return defHttp.delete({ url: url, params }, { isTransformResponse: false }); + }; + + if (isList === true) { + onlineTableContext['_getAction'] = getAction; + onlineTableContext['_postAction'] = postAction; + onlineTableContext['_putAction'] = putAction; + onlineTableContext['_deleteAction'] = deleteAction; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + onlineTableContext['_useMessage'] = useMessage; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + } else { + onlineTableContext.addObject2Context('_getAction', getAction); + onlineTableContext.addObject2Context('_postAction', postAction); + onlineTableContext.addObject2Context('_putAction', putAction); + onlineTableContext.addObject2Context('_deleteAction', deleteAction); + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + onlineTableContext.addObject2Context('_useMessage', useMessage); + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + } + + /** + * 初始化 + * @param str (res.result.enhanceJs) + */ + function initCgEnhanceJs(str: string) { + //console.log("--onlineList-js增强"+isList,str) + if (str) { + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-338】js增强代码报错不能影响页面渲染 + let Obj: any; + let result; + try { + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + Obj = _eval(str); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + result = new Obj(getAction, postAction, deleteAction); + //return new Function(str)(getAction,postAction,deleteAction); + } catch (error) { + result = {}; + const { createMessage } = useMessage(); + createMessage.warning(`js增强代码有语法错误,请检查代码~ ${error}`); + } + return result; + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-338】js增强代码报错不能影响页面渲染 + } else { + return {}; + } + } + + /** + * 【】 + * 触发js增强方法 + * @param that + * @param formData + */ + function triggerJsFun(that, buttonCode) { + if (EnhanceJS && EnhanceJS[buttonCode]) { + EnhanceJS[buttonCode](that); + } + } + + /** + * 【表单】 + * 处理js增强 自定义 提交前事件 + * @param that + * @param formData + */ + function customBeforeSubmit(that, formData) { + if (EnhanceJS && EnhanceJS['beforeSubmit']) { + return EnhanceJS['beforeSubmit'](that, formData); + } else { + return Promise.resolve(); + } + } + + /** + * 删除前业务处理 + * @param that + * @param record + */ + function beforeDelete(that, record) { + if (EnhanceJS && EnhanceJS['beforeDelete']) { + return EnhanceJS['beforeDelete'](that, record); + } else { + return Promise.resolve(); + } + } + + if (isList === true) { + if (onlineTableContext) { + onlineTableContext['beforeDelete'] = (record) => { + const onlEnhanceJS = onlineTableContext['EnhanceJS']; + if (onlEnhanceJS && onlEnhanceJS['beforeDelete']) { + return onlEnhanceJS['beforeDelete'](onlineTableContext, record); + } else { + return Promise.resolve(); + } + }; + + onlineTableContext['beforeEdit'] = (record) => { + const onlEnhanceJS = onlineTableContext['EnhanceJS']; + if (onlEnhanceJS && onlEnhanceJS['beforeEdit']) { + return onlEnhanceJS['beforeEdit'](onlineTableContext, record); + } else { + return Promise.resolve(); + } + }; + } + } + + return { + EnhanceJS, + initCgEnhanceJs, + customBeforeSubmit, + beforeDelete, + triggerJsFun, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts new file mode 100644 index 000000000..fe78b5717 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts @@ -0,0 +1,41 @@ +import {add} from "/@/components/Form/src/componentMap"; +import LinkTableSelect from '../../extend/linkTable/LinkTableSelect.vue'; +import LinkTableCard from '../../extend/linkTable/LinkTableCard.vue'; +import OnlineSelectCascade from '../../auto/comp/OnlineSelectCascade.vue'; + +const componentKeyMap = {}; + +/** + * 用于往form中添加组件 + */ +export function useExtendComponent() { + + addComponent('OnlineSelectCascade', OnlineSelectCascade); + addComponent('LinkTableSelect', LinkTableSelect); + addComponent('LinkTableCard', LinkTableCard); + + /** + * 避免重复添加 + */ + function addComponent(key, comp) { + if(!componentKeyMap[key]){ + add(key, comp) + componentKeyMap[key] = 1; + } + } + + /** + * 关联记录的查询控件不宜用卡片模式 统一使用下拉模式 + */ + function linkTableCard2Select(schema) { + if("LinkTableCard"==schema.component){ + schema.component = 'LinkTableSelect'; + schema.componentProps.popContainer = 'body'; + } + } + + return { + addComponent, + linkTableCard2Select + } +} \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useFormUrl.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useFormUrl.ts new file mode 100644 index 000000000..04a671c1f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useFormUrl.ts @@ -0,0 +1,42 @@ +import { ref, unref } from 'vue'; +import { useRoute } from 'vue-router'; +import { useUserStore } from '/@/store/modules/user'; +import { message } from 'ant-design-vue'; + +export function useFormUrl() { + const route = useRoute(); + const token = ref(route.query.token); + const createToken = ref(''); + const userStore = useUserStore(); + if (unref(token)) { + // 校验token是否合法 + } else { + if (userStore.getToken) { + // 登录了系统弄,有了token + token.value = userStore.getToken; + } else { + // 既没登录,url也没token + getToken(); + } + } + // 没有token,通过接口去获取token,再缓存到本地并设置到url上重新渲染 + function getToken() { + const hide = message.loading('获取token中...', 0); + setTimeout(() => { + createToken.value = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTAwMTc2NzUsInVzZXJuYW1lIjoiYWRtaW4ifQ.0q5ywkLF144SaqumsSgEXO5ERtqaYp8jqouIUxxhELc'; + + setToken(); + setUrlToken(); + hide(); + }, 3e3); + } + function setToken() { + userStore.setToken(createToken.value); + // userStore.setTenant(1); + } + function setUrlToken() { + window.location.replace(`${route.fullPath}?token=${createToken.value}`); + } + return { token }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useListButton.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useListButton.ts new file mode 100644 index 000000000..f301cb9d5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useListButton.ts @@ -0,0 +1,717 @@ +import type { Ref } from 'vue'; +import type { ExtConfigType } from '../../types'; +import { computed, reactive, toRaw, ref } from 'vue'; +import { CgFormButton } from '../../types/onlineRender'; +import { pick } from 'lodash-es'; +import { useModal } from '/@/components/Modal'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { filterObj } from '/@/utils/common/compUtils'; +import { useMethods } from '/@/hooks/system/useMethods'; +import { getToken } from '/@/utils/auth'; +import { goJmReportViewPage } from '/@/utils' +import { Tree } from '../../util/constant'; + +/**工作流编码前缀*/ +const FLOW_CODE_PRE = 'onl_'; +/** + * 负责列表页面按钮及其事件--在initAutoList之后执行,必须先获取按钮信息 + * - 这个是所有风格都通用的且必须要有的 + */ +export function useListButton(onlineTableContext, extConfigJson: Ref, extraParameter = {}) { + const buttonStatus = { + add: true, + addSub: true, + // edit = 编辑按钮的code + edit: true, + // update = 编辑按钮的老code + update: true, + delete: true, + batch_delete: true, + import: true, + export: true, + detail: true, + query: true, + reset: true, + super_query: true, + bpm: true, + form_confirm: true, + // 子表新增 + form_sub_add: true, + // 子表删除 + form_sub_batch_delete: true, + // 子表新增 + form_sub_open_add: true, + // 子表编辑 + form_sub_open_edit: true, + // 生成测试数据 + aigc_mock_data: true, + }; + + // 弹框事件 + const [registerModal, { openModal }] = useModal(); + const [registerImportModal, { openModal: openImportModal }] = useModal(); + const [registerDetailModal, { openModal: openDetailModal }] = useModal(); + const [registerBpmModal, { openModal: openBpmModal }] = useModal(); + const { createMessage: $message } = useMessage(); + + // 按钮相关 + const buttonSwitch = reactive(buttonStatus); + const cgLinkButtonList = reactive([]); + const cgTopButtonList = reactive([]); + + interface CgBIBtnType extends CgFormButton { + enabled: boolean, + } + + // Online表单内置按钮列表 + const cgBIBtnMap = reactive>({}); + + // 创建内置按钮配置(用于控制按钮权限) + const createBIButtonCfg = (btnKey: string) => computed(() => buttonSwitch[btnKey] === true ? cgBIBtnMap[btnKey] : {enabled: false}) + // 查询按钮配置 + const getQueryButtonCfg = createBIButtonCfg('query') + // 重置按钮配置 + const getResetButtonCfg = createBIButtonCfg('reset') + // 表单弹窗的确定按钮配置 + const getFormConfirmButtonCfg = createBIButtonCfg('form_confirm') + + const testDataLoading = ref(false); + const testDataBtnShow = ref(true); + setTimeout(() => { + testDataBtnShow.value = false; + }, 4e3); + /** + * 根据配置获取 button和link + */ + function initButtonList(btnList) { + cgLinkButtonList.length = 0; + cgTopButtonList.length = 0; + if (btnList && btnList.length > 0) { + for (let i = 0; i < btnList.length; i++) { + let temp = pick(btnList[i], 'buttonCode', 'buttonName', 'buttonStyle', 'optType', 'exp', 'buttonIcon', 'buttonStatus', 'enabled'); + if (temp.buttonStyle == 'button') { + cgTopButtonList.push(temp); + } else if (temp.buttonStyle == 'link') { + cgLinkButtonList.push(temp); + } else if (temp.buttonStyle == 'built-in') { + if (temp.buttonIcon) { + temp.buttonIcon = 'ant-design:' + temp.buttonIcon; + } + temp.enabled = temp.buttonStatus === '1' + cgBIBtnMap[temp.buttonCode] = temp; + } + } + } + } + + /** + * 根据配置设置按钮的 显示/隐藏 状态 + */ + function initButtonSwitch(hideColumns) { + Object.keys(buttonSwitch).forEach((key) => { + buttonSwitch[key] = true; + }); + if (hideColumns && hideColumns.length > 0) { + Object.keys(buttonSwitch).forEach((key) => { + if (hideColumns.indexOf(key) >= 0) { + buttonSwitch[key] = false; + } + }); + } + } + + // 增加事件 + function handleAdd(param) { + let data = { isUpdate: false }; + if (param) { + data['param'] = param; + } + openModal(true, data); + } + + // 修改事件 + function handleEdit(record) { + onlineTableContext + .beforeEdit(record) + .then(() => { + openModal(true, { + isUpdate: true, + record, + }); + }) + .catch((msg) => { + $message.warning(msg); + }); + } + + /** + * + * [更多]下拉项中的 [删除] + */ + const getDeleteButton = (record) => { + return { + label: cgBIBtnMap['delete'].buttonName, + ifShow: () => cgBIBtnMap['delete'].enabled, + popConfirm: { + title: '是否删除?', + confirm: handleDeleteOne.bind(null, record), + }, + }; + }; + + // 删除事件 + function handleDeleteOne(record) { + onlineTableContext + .beforeDelete(record) + .then(() => { + handleDelete(record.id, false); + }) + .catch((msg) => { + $message.warning(msg); + }); + } + + /** + * 操作列定义 + * @param record + */ + function getActions(record) { + //update-begin-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + let bpmStatusValue = getBpmStatusValue(record); + // 允许编辑的情况--> bpm有值且值为1,bpm没有值, + //update-begin-author:taoyan date:2023-2-6 for: QQYUN-4135【online】审批完成的流程和取回作废的流程,可以编辑 + let canEdit = (bpmStatusValue && (bpmStatusValue=='1' || bpmStatusValue=='3' || bpmStatusValue=='4')) || !bpmStatusValue; + //update-end-author:taoyan date:2023-2-6 for: QQYUN-4135【online】审批完成的流程和取回作废的流程,可以编辑 + if ((toRaw(buttonSwitch.edit) === true && toRaw(buttonSwitch.update) === true) && canEdit) { + //update-end-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + return [ + { + label: cgBIBtnMap['edit'].buttonName, + ifShow: () => cgBIBtnMap['edit'].enabled, + onClick: (e) => { + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + // erp主表点击编辑时需要防止当前选中数据反选 + extraParameter['editClickCallback'] && extraParameter['editClickCallback'](record.id, e); + handleEdit(record); + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + }, + }, + ]; + } + return []; + } + + /** + * 操作列[提交流程] + */ + function getSubmitFlowButton(record) { + return { + label: cgBIBtnMap['bpm'].buttonName, + ifShow: () => cgBIBtnMap['bpm'].enabled, + popConfirm: { + title: '确认提交流程吗?', + confirm: handleSubmitFlow.bind(null, record), + }, + }; + } + + /** + * 操作列[审批进度] + * @param record + */ + function getViewBpmGraphicButton(record){ + return { + label: '审批进度', + onClick: handleViewGraphic.bind(null, record), + }; + } + + /** + * 查看流程图 + * @param record + */ + function handleViewGraphic(record){ + const { currentTableName } = onlineTableContext; + + //判断 currentTableName如果是视图,截掉后缀 + let currentTableNameVariable = currentTableName; + if (currentTableName.includes('$')) { + currentTableNameVariable = currentTableName.split('$')[0]; + } + + let flowCode = FLOW_CODE_PRE + currentTableNameVariable; + let dataId = record.id; + openBpmModal(true, { + flowCode, + dataId + }) + } + + /** + * 操作列[更多下拉项] + */ + function getDropDownActions(record, params = {} ) { + let arr: any = []; + if (toRaw(buttonSwitch.detail) === true) { + arr.push({ + label: cgBIBtnMap['detail'].buttonName, + ifShow: () => cgBIBtnMap['detail'].enabled, + onClick: handleDetail.bind(null, record), + }); + } + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1101】树表放开提交流程按钮且加上审批详情弹窗 + if (onlineTableContext['hasBpmStatus'] === true && toRaw(buttonSwitch.bpm) === true) { + // 提交流程按钮显示条件: 有bpm_status字段,并且有操作bpm按钮的权限 + let bpmStatusValue = getBpmStatusValue(record); + if (!bpmStatusValue || bpmStatusValue == '1') { + //并且 bpm_status的值为1 或者为空 + arr.push(getSubmitFlowButton(record)); + }else{ + arr.push(getViewBpmGraphicButton(record)) + } + } + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1101】树表放开提交流程按钮且加上审批详情弹窗 + // 对接积木报表打印 + if (extConfigJson.value) { + let { reportPrintShow, reportPrintUrl } = extConfigJson.value; + if (reportPrintShow && reportPrintUrl) { + arr.push({ + label: '打印', + onClick() { + //跳转至积木报表页面 + let url = reportPrintUrl; + let id = record.id; + let token = getToken(); + goJmReportViewPage(url, id, token); + }, + }); + } + } + //update-begin-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + // 允许删除的情况--> bpm有值且值为1,bpm没有值 + let bpmStatusValue = getBpmStatusValue(record); + let canDelete = (bpmStatusValue && bpmStatusValue=='1') || !bpmStatusValue; + if (toRaw(buttonSwitch.delete) === true && canDelete) { + //update-end-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + arr.push(getDeleteButton(record)); + } + let buttonList = cgLinkButtonList; + if (buttonList && buttonList.length > 0) { + for (let item of buttonList) { + if (showLinkButtonOfExpression(item.exp || '', record) === true) { + arr.push({ + label: item.buttonName, + onClick: cgButtonLinkHandler.bind(null, record, item.buttonCode, item.optType), + }); + } + } + } + return arr; + } + + /** + * 获取bpm_status的值 大小写都获取一遍 + * @param record + */ + function getBpmStatusValue(record) { + const key = 'bpm_status'; + let value = record[key]; + if (!value) { + value = record[key.toUpperCase()]; + } + return value; + } + + /** + * 查看详情 + * @param record + */ + function handleDetail(record) { + openDetailModal(true, { + isUpdate: true, + disableSubmit: true, + record, + }); + } + + /** + * 提交流程请求 + * @param record + */ + function startProcess(record) { + const { + currentTableName, + onlineUrl: { startProcess }, + } = onlineTableContext; + + //判断 currentTableName如果是视图,截掉后缀 + let currentTableNameVariable = currentTableName; + if (currentTableName.includes('$')) { + currentTableNameVariable = currentTableName.split('$')[0]; + } + + let postConfig = { + url: startProcess, + params: { + flowCode: FLOW_CODE_PRE + currentTableNameVariable, + id: record.id, + // TODO 流程表单没有 + formUrl: 'modules/bpm/task/form/OnlineFormDetail', + formUrlMobile: 'check/onlineForm/detail', + }, + }; + let postOption = { isTransformResponse: false }; + return new Promise((resolve, reject) => { + defHttp.post(postConfig, postOption).then((res) => { + if (res.success) { + resolve(res); + $message.success(res.message); + } else { + reject(); + $message.warning(res.message); + } + }); + }); + } + + /** + * 提交流程按钮触发事件 + * @param record + */ + async function handleSubmitFlow(record) { + await startProcess(record); + onlineTableContext.loadData(); + } + + //删除请求 + function handleDelete(dataId: String, isBatch = true) { + console.log('删除数据id值', dataId); + let url = `${onlineTableContext.onlineUrl.optPre}${onlineTableContext.ID}/${dataId}`; + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + if (onlineTableContext['isErpSubTable'] === true) { + url = `${url}?tabletype=3`; + } + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + return new Promise((resolve, reject) => { + defHttp + .delete( + { + url, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + $message.success(res.message); + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + onlineTableContext.loadData({ delNum: dataId.split(',').length }); + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + // erp主表记录选中时被删除需要清空选中的key + if (!isBatch) { + extraParameter['singleDelCallback'] && extraParameter['singleDelCallback'](dataId); + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + resolve(true); + } else { + $message.warning(res.message); + reject(); + } + }); + }); + } + + // 批量删除事件 + function handleBatchDelete() { + let arr = onlineTableContext['selectedRowKeys']; + if (arr.length <= 0) { + $message.warning('请选择一条记录!'); + return false; + } else { + let idSet: any = []; + arr.forEach(function (val) { + let temp = val; + //树形列表 key后面会带有_loadChild + if (temp && temp.endsWith('_loadChild')) { + temp = temp.replace('_loadChild', ''); + } + // 去重 + if (idSet.indexOf(temp) < 0) { + idSet.push(temp); + } + }); + let ids = idSet.join(','); + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: async () => { + await handleDelete(ids); + onlineTableContext.clearSelectedRow(); + }, + }); + } + } + /* + * liaozhiyang + * 20250403 + * 【QQYUN-11801】生成测试数据 + * */ + const handleAddTestData = (currentTableName, reload) => { + testDataLoading.value = true; + defHttp + .post({ url: `/online/cgform/api/aigc/mock/data/${currentTableName}`, timeout: 120000 }, { isTransformResponse: false }) + .then((res) => { + if (res.code == 200) { + $message.success('生成测试数据成功~'); + reload(); + } else { + $message.warn(res.message); + } + testDataLoading.value = false; + }) + .catch((err) => { + testDataLoading.value = false; + console.log(err); + }); + } + + /** + * 自定义按钮触发事件-link按钮 + * @param record + * @param buttonCode + * @param optType js/bus + */ + function cgButtonLinkHandler(record, buttonCode, optType) { + if (optType == 'js') { + onlineTableContext['execButtonEnhance'](buttonCode, record); + } else if (optType == 'action') { + let params = { + formId: onlineTableContext['ID'], + buttonCode: buttonCode, + dataId: record.id, + }; + //console.log("自定义按钮link请求后台参数:",params) + let url = `${onlineTableContext.onlineUrl.buttonAction}`; + defHttp + .post( + { + url, + params, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + onlineTableContext.loadData(); + $message.success('处理完成!'); + } else { + $message.warning(res.message); + } + }); + } + } + + /** + * 列表上方按钮 -js事件 + * @param buttonCode + */ + function cgButtonJsHandler(buttonCode) { + // 待测 + onlineTableContext['execButtonEnhance'](buttonCode); + } + + /** + * 列表上方按钮 -action事件 + * @param buttonCode + */ + function cgButtonActionHandler(buttonCode) { + let arr = onlineTableContext['selectedRowKeys']; + if (!arr || arr.length == 0) { + $message.warning('请先选中一条记录'); + return false; + } + let dataId = arr.join(','); + let params = { + formId: onlineTableContext['ID'], + buttonCode: buttonCode, + dataId: dataId, + }; + //console.log("自定义按钮请求后台参数:",params) + let url = `${onlineTableContext.onlineUrl.buttonAction}`; + defHttp + .post( + { + url, + params, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + onlineTableContext.loadData(); + onlineTableContext.clearSelectedRow(); + $message.success('处理完成!'); + } else { + $message.warning(res.message); + } + }); + } + + // 导入事件 + function onImportExcel() { + // update-begin--author:liaozhiyang---date:20240429---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权导入从表数据 + if (onlineTableContext['foreignKeyField'] && onlineTableContext['foreignKeyValue']) { + openImportModal(true, { + [onlineTableContext['foreignKeyField']]: onlineTableContext['foreignKeyValue'], + }); + } else { + openImportModal(true); + } + // update-end--author:liaozhiyang---date:20240429---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权导入从表数据 + } + + // 导入地址 + const importUrl = () => { + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + let url = `${onlineTableContext.onlineUrl.importXls}${onlineTableContext.ID}`; + if (onlineTableContext['isErpSubTable'] === true) { + url = `${url}?tabletype=3`; + } + return url; + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + }; + + // 导出事件 + const { handleExportXlsx } = useMethods(); + function onExportExcel() { + let params = onlineTableContext.getLoadDataParams(); + let selections = onlineTableContext['selectedRowKeys']; + if (selections && selections.length > 0) { + params['selections'] = selections.join(','); + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + let tabletype = {}; + if (onlineTableContext['isErpSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + tabletype = { tabletype: 3 }; + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + if (onlineTableContext['foreignKeyField'] && onlineTableContext['foreignKeyValue']) { + params[onlineTableContext['foreignKeyField']] = onlineTableContext['foreignKeyValue']; + } + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //console.log("导出参数",params) + let paramsStr = JSON.stringify(filterObj(params)); + let url = `${onlineTableContext.onlineUrl.exportXls}${onlineTableContext.ID}`; + const description = onlineTableContext.description; + return handleExportXlsx(description, url, { paramsStr: paramsStr, ...tabletype }); + } + + /** + * liaozhiyang + * 20231008 + * 先把自定义表单是转成布尔值,再利用new Function换算真正的规则 + */ + function multipleLinkButtonOfExpression(expression, row) { + const gather: any = []; + expression.split('||').forEach(oItem => { + const arr: any = []; + oItem + .trim() + .split('&&') + .forEach(nItem => { + arr.push(oneLinkButtonOfExpression(nItem.trim(), row)); + }); + gather.push(arr.join('&&')); + }); + const r = gather.join('||'); + console.log('---多个表达式---', r); + return new Function(`return ${r}`)(); + } + /** + * liaozhiyang + * 20231008 + * 区分有是否有表达式 + */ + function showLinkButtonOfExpression(expression, row) { + if (!expression || expression == '') { + return true; + } + if (expression.indexOf('||') == -1 && expression.indexOf('&&') == -1) { + return oneLinkButtonOfExpression(expression, row); + } else { + return multipleLinkButtonOfExpression(expression, row); + } + } + + /** + * 用于处理 link按钮的表达式 返回布尔值 + * @param expression 表达式 + * @param row 所在行的数据 + */ + function oneLinkButtonOfExpression(expression: string, row: any): boolean { + if (!expression || expression == '') { + return true; + } + // 字段名#条件#值 + let arr = expression.split('#'); + //获取字段值 + let fieldValue = row[arr[0]]; + //获取表达式 + let exp = arr[1].toLowerCase(); + //判断表达式 + if (exp === 'eq') { + return fieldValue == arr[2]; + } else if (exp === 'ne') { + return !(fieldValue == arr[2]); + } else if (exp === 'empty') { + if (arr[2] === 'true') { + return !fieldValue || fieldValue == ''; + } else { + return fieldValue && fieldValue.length > 0; + } + } else if (exp === 'in') { + let arr2 = arr[2].split(','); + return arr2.indexOf(String(fieldValue)) >= 0; + } + return false; + } + + return { + buttonSwitch, + cgLinkButtonList, + cgBIBtnMap, + getQueryButtonCfg, + getResetButtonCfg, + getFormConfirmButtonCfg, + cgTopButtonList, + importUrl, + registerModal, + handleAdd, + handleEdit, + handleBatchDelete, + handleAddTestData, + testDataLoading, + testDataBtnShow, + registerImportModal, + onImportExcel, + onExportExcel, + getDropDownActions, + getActions, + cgButtonJsHandler, + cgButtonActionHandler, + cgButtonLinkHandler, + initButtonList, + initButtonSwitch, + getDeleteButton, + handleSubmitFlow, + getSubmitFlowButton, + registerDetailModal, + registerBpmModal, + openDetailModal + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts new file mode 100644 index 000000000..29524e0bb --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts @@ -0,0 +1,109 @@ +import type { InjectionKey } from 'vue'; +import type { Emitter } from '/@/utils/mitt'; +import { createContext, useContext } from '/@/hooks/core/useContext'; +import mitt from '/@/utils/mitt'; +import {onMounted, onUnmounted} from 'vue' + +export interface OnlineEmitterContextProps { + /*activeName?: 'onlineEvent'*/ + onlineEmitter: Emitter +} +const key: InjectionKey = Symbol(); + +/** + * 事件编码-打开弹窗 + */ +export const EVENT_OPEN_CODE: string = 'openpopmodal'; +/** + * 事件编码-关闭弹窗,获取表单数据 + */ +const EVENT_SUCCESS_CODE: string = 'successpopmodal'; + +/** + * 设置弹框事件-online列表 + * @param callback + */ +export function useOnlineListPopEvent(callback){ + const emitter = mitt(); + function openPopModal(params){ + callback(params); + console.log('事件触发完成,', params) + } + + emitter.on(EVENT_OPEN_CODE, openPopModal) +/* onUnmounted(()=>{ + emitter.off(EVENT_OPEN_CODE, openPopModal) + console.log('事件解绑完成-createOpenPopModalEvent') + }); + onMounted(()=>{ + + console.log('事件绑完成-createOpenPopModalEvent') + });*/ + createOnlineEventContext({ + onlineEmitter: emitter + }); + console.log('事件绑完成,') +} + +/** + * 关闭弹窗,返回表单数据 + * @param params + */ +export function useOnlinePopFormEvent(){ + const { onlineEmitter } = useOnlineEventContext(); + function emitFormData(data){ + onlineEmitter.emit(EVENT_SUCCESS_CODE, data) + } + return { + emitFormData + } +} + + +/** + * 关闭弹窗,返回表单数据 + * @param params + */ +export function useOnlineFormEvent(callback){ + const context = useOnlineEventContext(); + const { onlineEmitter } = context; + function emitData(data){ + callback(data); + console.log('useOnlineFormEvent事件触发完成,', data) + } + onUnmounted(()=>{ + onlineEmitter && onlineEmitter.off(EVENT_SUCCESS_CODE, emitData) + console.log('事件解绑完成-createOpenPopModalEvent') + }); + onMounted(()=>{ + onlineEmitter && onlineEmitter.on(EVENT_SUCCESS_CODE, emitData) + console.log('事件绑完成-createOpenPopModalEvent') + }); + function openPopModal(emitData){ + console.log('openPopModal', emitData) + onlineEmitter && onlineEmitter.emit(EVENT_OPEN_CODE, emitData) + } + return { + openPopModal + } +} + +/** + * 触发弹框事件 + * @param params + */ +/*export function getOnlinePopEvent(){ + const { onlineEmitter } = useOnlineEventContext(); + return { + onlineEmitter, + eventCode: EVENT_OPEN_CODE + }; +}*/ + +function createOnlineEventContext(context: OnlineEmitterContextProps) { + return createContext(context, key, { readonly: false, native: true }); +} + +export function useOnlineEventContext() { + return useContext(key); +} \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts new file mode 100644 index 000000000..de5c9041f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts @@ -0,0 +1,903 @@ +import type { ExtConfigType } from '../../types'; +import { Page, SpecialConfig, SETUP, ENHANCEJS } from '../../../cgform/types/onlineRender'; +import { useRoute } from 'vue-router'; +import { router } from '/@/router'; +import { onBeforeUnmount, ref, toRaw, nextTick, provide } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { filterObj } from '/@/utils/common/compUtils'; +import { useCustomHook, GET_FUN_BODY_REG } from './useCustomHook'; +import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; +import { useModal } from '/@/components/Modal'; +import { ERP } from "../../util/constant"; +import {useMultipleTabStore} from "/@/store/modules/multipleTab"; +import {useCgformStore} from "../../store/cgformState"; +import { getMenus } from '/@/router/menus'; +/** + * context对象属性的描述 + * 控制台写js增强的时候 打印console.log(this)可以获取到该对象说明 + */ +const CONTEXT_PROP_DESCRIPTION = { + acceptHrefParams: '

跳转时获取的参数信息', + currentPage: '

当前页数', + currentTableName: '

当前表名', + description: '

当前表描述', + hasChildrenField: '

是否有子节点的字段名,仅树形表单下有效', + isDesForm: '

xx', + isTree: ' 是否是树形表单 ', + loadData: ' 加载列表数据', + pageSize: '

每一页显示条数', + queryParam: '

查询条件对象,每次点击查询后才会更新此数据', + selectedRowKeys: '

选中的行的id数组', + sortField: '

排序字段', + sortType: '

排序规则', + total: '

总页数', + foreignKeyValue: '

Erp一对多子表外键选中对应主表字段的值', + isErpSubTable: '

是否Erp一对多子表', + foreignKeyField: '

Erp一对多子表外键字段', + themeTemplate: '

主题模板', + isInnerSubTable: '

是否内嵌一对多子表', + innerSubTableId: '

内嵌一对多子表ID', + innerSubTableName: '

内嵌一对多子表名', + mTableSelectedRcordId: '

内嵌主表展开行的id', + innerSubTableFk: '

内嵌子表的外键字段', + loading: '

设置/获取loading', +}; + +/** + * online地址-常量 + */ +const onlineUrl = { + getColumns: '/online/cgform/api/getColumns/', + getQueryInfo: '/online/cgform/api/getQueryInfo/', + getData: '/online/cgform/api/getData/', + getTreeData: '/online/cgform/api/getTreeData/', + optPre: '/online/cgform/api/form/', + buttonAction: '/online/cgform/api/doButton', + exportXls: '/online/cgform/api/exportXlsOld/', + importXls: '/online/cgform/api/importXls/', + startProcess: '/act/process/extActProcess/startMutilProcess', + getErpColumns: '/online/cgform/api/getErpColumns/', + // 内嵌主题一对多子表数据请求接口 + list: '/online/cgform/api/subform/list/', +}; + +// 没一张表配置的初始值 +let config: SpecialConfig = { + sortField: 'id', + sortType: 'asc', + currentPage: 1, + pageSize: 10, + total: 0, + selectedRowKeys: [], + queryParam: {}, + acceptHrefParams: {}, + description: '', + currentTableName: '', + isDesForm: false, + desFormCode: '', + cache: false, + isTree: false, + hasChildrenField: '', +}; + +/** + * 分页配置 + */ +const metaPagination = { + current: 1, + pageSize: 10, + pageSizeOptions: ['10', '20', '30'], + showTotal: (total, range) => { + return range[0] + '-' + range[1] + ' 共' + total + '条'; + }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, +}; + +/** + * 获取online 列表上下文 + * 1.常量,全局使用的- 请求url + * 2.特殊参数-查询条件,排序方式,分页信息,选中行的keys(待测试,是否只和key有关,如果和row有关需去掉此配置) + * 3.loadData方法,这个方法很多地方调用 + * setup最开头执行一次即可 + */ +const { createMessage: $message, createErrorModal } = useMessage(); + +export function useOnlineTableContext(params: any = {}) { + console.log('-------------------------useOnlineTableContext----------------------->'); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + const tableId = params.code ?? ''; + const ID = ref(tableId); + provide('tableId', ID); + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + const route = useRoute(); + // 列表页面查询表单的ref + const onlineQueryFormOuter = ref(); + // 高级查询按钮 + const superQueryButtonRef = ref(); + // 分页配置 + const pagination = ref(false); + // table 数据 切换路由即发生数据改变 正常情况下会走一遍setup 缓存情况下 走事件onActivated----问题是不用重新请求column吗? 只需要加载数据? 待测试 + const dataSource = ref>([]); + // 表格是否重载 + const tableReloading = ref(true); + // online表单扩展配置 + const onlineExtConfigJson = ref(); + // Online表单全局状态 + const cgformStore = useCgformStore(); + // 多Tab状态 + const tabStore = useMultipleTabStore(); + + const isConfigCurRoute = ref(false); + const pageLoading = ref(false); + + let specialConfigMap: { [key: string | symbol ]: SpecialConfig } = {}; + const methods = { + execButtonEnhance: function (code, record) { + if (onlineTableContext[ENHANCEJS][code]) { + if (SETUP === code) { + executeEnhanceJsHook(code); + } else { + let row = toRaw(record); + return onlineTableContext[ENHANCEJS][code].call(onlineTableContext, onlineTableContext, row); + } + } else if (onlineTableContext[ENHANCEJS][code + '_hook']) { + + //update-begin-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + if(record){ + let row = toRaw(record); + executeEnhanceJsHook(code + '_hook', row); + }else{ + executeEnhanceJsHook(code + '_hook'); + } + //update-end-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + + } else { + console.log('增强没找到!', code); + } + }, + /** + * get 是否是树形表单 + * @param status 如果有值 则视为set方法 + */ + isTree: function (status?) { + if (typeof status === 'boolean') { + //传了参数则设置值 + onlineTableContext['isTreeTable'] = status; + return status; + } else { + return onlineTableContext['isTreeTable']; + } + }, + }; + + function executeEnhanceJsHook(code, row?) { + let str = onlineTableContext[ENHANCEJS][code].toLocaleString(); + let arr = str.match(GET_FUN_BODY_REG); + if (arr.length > 1) { + let temp = arr[1]; + executeJsEnhanced(temp, row); + } + } + /** + * 定义数据代理 取值方便 onlineTableContext.queryParam + * 直接读取onlineTableContext 取到的是{} + */ + const onlineTableContext: any = new Proxy(CONTEXT_PROP_DESCRIPTION, { + get(_target: any, prop: string): any { + //console.log('从SpecialConfig中读取属性:'+prop) + if (typeof methods[prop] === 'function') { + return methods[prop]; + } else { + let temp = specialConfigMap[ID.value]; + if (temp == null) { + return temp; + } + return Reflect.get(temp, prop); + } + }, + set(_target: any, prop: string, value: any): boolean { + // console.log('设置SpecialConfig属性:'+ prop, value) + let temp = getCurrentPageSpecialConfigMap(); + if (typeof value === 'function') { + // 如果是函数放到methods中去 + return Reflect.set(methods, prop, value); + } else { + return Reflect.set(temp, prop, value); + } + }, + deleteProperty(_target, key) { + // 在路由切换、关闭页面的时候需要调用一下这个方法清除配置 + if (key === ID.value) { + delete specialConfigMap[key]; + return true; + } else { + return false; + } + }, + }); + + // 新的js增强 + const { executeJsEnhanced } = useCustomHook({}, onlineTableContext); + + /** + * 获取路由地址上的表单ID + */ + function getTableId() { + let idValue = route.params.id as string; + if (!idValue) { + idValue = ''; + } + return idValue; + } + + onMountedOrActivated(({type}) => { + // 缓存路由走Activated,没缓存的走Mounted,均需走一次 + console.log('-------------------onMountedOrActivated-------------------'); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //erp一对多子表的id不能从页面路由获取(当tableId存在时,不从页面路由获取) + !tableId && handlePageChange(); + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + if (type === 'activated') { + // 【QQYUN-7151】表单修改后,已经打开的功能测试页面不会自动刷新 + if (cgformStore.checkIsChanged(ID.value)) { + tabStore.refreshPage(router) + } + } + + if (ID.value) { + cgformStore.removeChangedTable(ID.value); + } + + }); + + // 路由关闭前 清空map里面的配置 + onBeforeUnmount(() => { + console.log('-------------------onBeforeUnmount-------------------'); + delete specialConfigMap[ID.value]; + // 如果缓存了 关闭时会调用 + // 没有缓存 切换路由就会调用--这个没关系 + // 但是测试online无此效果 + }); + + /** + * 获取当前页面配置 + */ + function getCurrentPageSpecialConfigMap() { + let temp = specialConfigMap[ID.value]; + if (!temp) { + let obj = Object.assign({}, config, { onlineUrl }); + temp = JSON.parse(JSON.stringify(obj)); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + if (params['themeTemplate'] == ERP) { + // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8387】Erp风格和其他风格同时在,导致其他风格当前页码不正常 + temp.pageSize = 5; + // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8387】Erp风格和其他风格同时在,导致其他风格当前页码不正常 + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + // update-begin--author:liaozhiyang---date:20250423---for:【issues/8117】js增强可设置获取loading + // @ts-ignore + temp.loading = pageLoading; + // update-end--author:liaozhiyang---date:20250423---for:【issues/8117】js增强可设置获取loading + specialConfigMap[ID.value] = temp; + } + return temp; + } + + //接受URL参数 + function handleAcceptHrefParams() { + let acceptHrefParams = {}; + let hrefParam = route.query; + if (hrefParam) { + Object.keys(hrefParam).map((key) => { + acceptHrefParams[key] = hrefParam[key]; + }); + // queryParam.value raw对象 + onlineTableContext['acceptHrefParams'] = acceptHrefParams; + } + } + + /** + * 查询table列信息 及其他配置 + */ + function getColumnList(themeTemplate = '') { + let url; + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + if (themeTemplate == ERP) { + // Erp一对多获取主子表columns + url = `${onlineTableContext.onlineUrl.getErpColumns}${ID.value}`; + } else { + url = `${onlineTableContext.onlineUrl.getColumns}${ID.value}`; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + //update-begin---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + if(onlineTableContext['isInnerSubTable'] === true){ + url = url+ '?tabletype=3'; + } + //update-end---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + return new Promise((resolve, reject) => { + defHttp + .get( + { + url, + }, + { isTransformResponse: false } + ) + .then((res) => { + // console.log(res) + if (res.success) { + resolve(res.result); + } else { + $message.warning(res.message); + reject(); + } + }) + .catch(() => { + reject(); + }); + }); + } + + //查询数据 + /** + * @param delNum number 删除的条数【批量删除,删除】调用会传 + */ + function loadData(options = {}) { + const { delNum } = options; + return new Promise((resolve, reject) => { + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + if (delNum != null) { + const { total, pageSize, current } = pagination.value; + const lastPage = Math.ceil(total / pageSize); + // 只有当前页是最后一页时删除数据才判断是否要跳到前一页 + if (current === lastPage) { + pagination.value.current = Math.ceil((total - delNum) / pageSize); + } + } + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + let params = getLoadDataParams(); + let url = `${onlineTableContext.onlineUrl.getData}${ID.value}`; + if (onlineTableContext.isTree() === true) { + url = `${onlineTableContext.onlineUrl.getTreeData}${ID.value}`; + } else if (onlineTableContext['isInnerSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20230822---for:【QQYUN-6305】内嵌主题一对多 + url = `${onlineTableContext.onlineUrl.getData}${onlineTableContext['innerSubTableId']}`; + params = {pageSize: -521, } + // update-begin--author:liaozhiyang---date:20240514---for:【QQYUN-9340】内嵌子表数据都查出来了 + if (onlineTableContext['innerSubTableFk'] && onlineTableContext['mTableSelectedRcordId']) { + params[onlineTableContext['innerSubTableFk']] = onlineTableContext['mTableSelectedRcordId']; + } + // update-end--author:liaozhiyang---date:20240514---for:【QQYUN-9340】内嵌子表数据都查出来了 + // update-end--author:liaozhiyang---date:20230822---for:【QQYUN-6305】内嵌主题一对多 + //update-begin---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + url = url+ '?tabletype=3'; + //update-end---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + // erp一对多子表查询需加参数 + if (onlineTableContext['isErpSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20250722---for:【issues/8575】erp默认选中第一个及没选中主表时子表不查询 + if (onlineTableContext['foreignKeyValue'] == undefined) { + return; + } + // update-end--author:liaozhiyang---date:20250722---for:【issues/8575】erp默认选中第一个及没选中主表时子表不查询 + params[onlineTableContext['foreignKeyField']] = onlineTableContext['foreignKeyValue']; + // 【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权查看从表的数据 + params['tabletype'] = 3; + delete params.hasQuery; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + console.log('------查询参数-----', params); + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('--onlineList-查询列表数据', res); + if (res.success) { + handleDataResult(res.result); + resolve(true); + } else { + if (res.message === 'NO_DB_SYNC') { + createErrorModal({ + title: '数据库未同步', + content: '请先同步数据库再查看此页面!', + // 点击确定后自动返回上一页 + onOk: () => router.back(), + }); + } else { + $message.warning(res.message); + } + reject(false); + } + }) + .catch(() => { + let error = '请求列表数据异常!'; + $message.warning(error); + reject(false); + }); + }); + } + + /** + * 获取查询条件 + */ + function getLoadDataParams() { + const { sortField, sortType, acceptHrefParams, queryParam } = onlineTableContext; + // 树用到的参数 + const treeParam = { + hasQuery: 'true', + }; + if (onlineTableContext.isTree() === true) { + // update-begin--author:liaozhiyang---date:20231205---for:【issues/888】online树表子节点搜索不生效且有警告 + if (!!queryParam || Object.keys(queryParam).length <= 0) { + treeParam['hasQuery'] = 'false'; + } + // update-end--author:liaozhiyang---date:20231205---for:【issues/888】online树表子节点搜索不生效且有警告 + } + let params = Object.assign({}, treeParam, acceptHrefParams, queryParam, { column: sortField, order: sortType }); + // TODO 范围查询 原固定值需要清楚 待删除 + /*let queryFields = queryFieldArray.value; + for(let item of queryFields){ + if(item.mode!='single'){ + params[item.field] = '' + } + }*/ + if (pagination.value) { + //如果分页 + params.pageNo = pagination.value.current; + params.pageSize = pagination.value.pageSize; + } else { + // 不分页传一个固定值 + params['pageSize'] = -521; + } + + let superQueryData = getSuperQueryData(); + //高级查询 + params.superQueryMatchType = superQueryData.matchType || ''; + params.superQueryParams = superQueryData.params || ''; + return filterObj(params); + } + + // 查询玩数据后 获取页面数据、数据总数 + function handleDataResult(result) { + let total = 0; + if (Number(result.total) > 0) { + if (onlineTableContext.isTree() === true) { + dataSource.value = getTreeDataByResult(result.records); + nextTick(() => { + loadDataByExpandedRows(dataSource.value); + }); + } else { + // update-begin--author:liaozhiyang---date:20250508---for:【issues/8168】id重复排序数据重了 + dataSource.value = []; + nextTick(() => { + dataSource.value = result.records; + }); + // update-end--author:liaozhiyang---date:20250508---for:【issues/8168】id重复排序数据重了 + } + total = Number(result.total); + } else { + dataSource.value = []; + } + if (pagination.value) { + pagination.value = { ...pagination.value, total }; + } + } + + //分页、排序、筛选变化时触发 + function handleChangeInTable($pagination, _filters, sorter) { + if (sorter && sorter.order) { + // 需要排序,先获取排序规则 + onlineTableContext['sortField'] = sorter.field; + onlineTableContext['sortType'] = 'ascend' == sorter.order ? 'asc' : 'desc'; + } else { + // 没有规则 走默认排序 + onlineTableContext['sortField'] = 'id'; + onlineTableContext['sortType'] = 'asc'; + } + if (pagination.value) { + //console.log('$pagination111', $pagination) + pagination.value = $pagination; + } + loadData(); + } + + /** + * 页面id改变后,执行查询loadData之前会执行该方法 + * 根据查询的结果设置当前表信息、设置查询条件、设置高级查询条件、分页信息、排序信息 + * @param result + */ + function handleSpecialConfig(result) { + //1.根据查询的结果设置当前表信息 + onlineTableContext['description'] = result.description; + onlineTableContext['currentTableName'] = result.currentTableName; + onlineTableContext['isDesForm'] = result.isDesForm; + onlineTableContext['desFormCode'] = result.desFormCode; + onlineTableContext['ID'] = ID.value; + //2.设置查询条件 + let { acceptHrefParams, queryParam, superQuery, currentPage, pageSize } = onlineTableContext; + handleAcceptHrefParams(); + if (!queryParam) { + onlineTableContext['queryParam'] = {}; + } else { + // 加强判断,防止没有查询 + if (onlineQueryFormOuter.value) { + onlineQueryFormOuter.value.initDefaultValues(queryParam, acceptHrefParams); + } + } + //3.设置高级查询条件 + if (!superQuery) { + onlineTableContext['superQuery'] = { params: '', matchType: '' }; + } else { + // erp一对多子表没有高级查询按钮 + if (superQueryButtonRef.value) { + superQueryButtonRef.value.initDefaultValues(superQuery); + } + } + //4.分页信息 + if (result.paginationFlag == 'Y') { + // update-begin--author:liaozhiyang---date:20240527---for:【TV360X-332】erp默认每页5条,切换之后每页5条没了 + let pageSizeOptions: any = metaPagination.pageSizeOptions; + if (params['themeTemplate'] == ERP) { + pageSizeOptions = ['5', '10', '30']; + } + // update-end--author:liaozhiyang---date:20240527---for:【TV360X-332】erp默认每页5条,切换之后每页5条没了 + pagination.value = { ...metaPagination, ...{ current: currentPage, pageSize, pageSizeOptions } }; + } else { + pagination.value = false; + } + //5.排序信息 不需要设置 没有显示声明,所以缺点是:界面上看不出来哪一列被排序了 + } + + /** 重载表格,在columns等信息变化时需要调用 */ + async function reloadTable() { + tableReloading.value = true; + await nextTick(); + tableReloading.value = false; + } + + const add2Context = { + loadData, + getLoadDataParams, + reloadTable, + }; + Object.keys(add2Context).map((key) => { + onlineTableContext[key] = add2Context[key]; + }); + + //----------------------------------- 以下为查询相关的-------------------------------- + + // 查询加载状态 + let loading = ref(false); + // 查询首页 + async function reload(parameter:any = {}) { + if (pagination.value) { + // update-begin--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + pagination.value = { ...pagination.value, current: parameter.mode == 'search' || !pagination.value.current ? 1 : pagination.value.current }; + // update-end--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + } + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + if (params['themeTemplate'] !== ERP) { + onlineTableContext.clearSelectedRow(); + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + //loading.value = true + await loadData(); + //loading.value = false + } + + //------------------------树形列表-------------------------- + function getTreeDataByResult(result) { + if (result) { + return result.map((item) => { + //判断是否标记了带有子节点 + let hasChildrenField = onlineTableContext['hasChildrenField']; + if (item[hasChildrenField] == '1') { + let loadChild = { id: item.id + '_loadChild', name: 'loading...', isLoading: true }; + loadChild['jeecg_row_key'] = loadChild.id; + item.children = [loadChild]; + } + return item; + }); + } + } + + const expandedRowKeys = ref([]); + + function handleExpandedRowsChange(expandedRowKeysValue) { + //console.log(a,b) + //console.log('handleExpandedRowsChange', expandedRowKeysValue, toRaw(expandedRowKeys.value)) + expandedRowKeys.value = expandedRowKeysValue; + } + + // 根据已展开的行查询数据(用于保存后刷新时异步加载子级的数据) + function loadDataByExpandedRows(dataList) { + let expandedRowKeysValue = expandedRowKeys.value; + if (expandedRowKeysValue.length > 0) { + const { sortField, sortType, pidField } = onlineTableContext; + let params = Object.assign({}, { column: sortField, order: sortType }); + params['hasQuery'] = 'in'; + //已展开节点批量查询子节点 + let superParams = Object.assign({}); + superParams.rule = 'in'; + superParams.type = 'text'; + superParams.val = expandedRowKeysValue.join(','); + superParams.field = pidField; + superParams = [superParams]; + params['superQueryParams'] = encodeURI(JSON.stringify(superParams)); + params['superQueryMatchType'] = 'and'; + params['batchFlag'] = 'true'; + let url = `${onlineTableContext.onlineUrl.getTreeData}${ID.value}`; + console.log('--onlineList-查询子节点参数', superParams); + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('--onlineList-查询子节点列表数据', res); + if (res.success && res.result.records && res.result.records.length > 0) { + //已展开的数据批量子节点 + let records = res.result.records; + const listMap = new Map(); + for (let item of records) { + let pid = item[pidField]; + if (expandedRowKeysValue.join(',').includes(pid)) { + let mapList = listMap.get(pid); + if (mapList == null) { + mapList = []; + } + mapList.push(item); + listMap.set(pid, mapList); + } + } + let childrenMap = listMap; + let fn = (list) => { + if (list) { + list.forEach((data) => { + if (expandedRowKeysValue.includes(data.id)) { + data.children = getTreeDataByResult(childrenMap.get(data.id)); + fn(data.children); + } + }); + } + }; + fn(dataList); + } + }) + .catch(() => { + let error = 'loadDataByExpandedRows请求列表数据异常!'; + $message.warning(error); + }); + } else { + return Promise.resolve(); + } + } + + /** + * 获取高级查询条件 + */ + function getSuperQueryData() { + if (!onlineTableContext.superQuery) { + return {}; + } + const { + superQuery: { params, matchType }, + currentTableName, + } = onlineTableContext; + let pre = currentTableName + '@'; + let arr: any[] = []; + if (params.length > 0) { + for (let data of params) { + let item = { ...data }; + let field = item.field; + if (field.startsWith(pre)) { + item.field = field.replace(pre, ''); + } + arr.push(item); + } + } + let str = arr.length > 0 ? JSON.stringify(arr) : ''; + console.log('高级查询条件', arr, matchType); + return { + params: encodeURIComponent(str), + matchType, + }; + } + + /**高级查询状态-是否有查询条件*/ + const superQueryStatus = ref(false); + + /** + * 高级查询对象 + * 1.执行高级查询组件的search事件,需要将值赋值给context + * 2.查询的时候从context中获取参数值 + * 3.id发生改变需要做点什么?nothing,状态值需要改变 + */ + function handleSuperQuery(params, matchType) { + // params一定是个数组,可能size为0 + onlineTableContext['superQuery'] = { + params, + matchType, + }; + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7309】online高级查询第二次按钮没动画 + if (params.length == 0 || params.length == undefined) { + superQueryStatus.value = false; + } else { + superQueryStatus.value = true; + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7309】online高级查询第二次按钮没动画 + // update-begin--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + pagination.value.current = 1; + // update-end--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + loadData(); + } + + /*------------------------自定义弹窗------------------------------*/ + const [registerCustomModal, { openModal: doOpenCustomModal }] = useModal(); + /** + * 自定义按钮 触发弹框 + * @param param + */ + function openCustomModal(param) { + if (!param) { + param = {}; + } + if (!param.row) { + let rows = onlineTableContext['selectedRows']; + if (!rows || rows.length == 0 || rows.length > 1) { + $message.warning('请选择一条数据'); + return; + } + param.row = rows[0]; + } + param['code'] = ID.value; + doOpenCustomModal(true, param); + } + onlineTableContext['openCustomModal'] = openCustomModal; + /*------------------------自定义弹窗------------------------------*/ + + /** + * 页面发生改变的时候触发 + */ + function handlePageChange() { + let idValue = getTableId(); + ID.value = idValue; + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //erp子表的id不能从页面路由获取(当tableId存在时,不从页面路由获取) + if (!tableId && !ID.value) { + handlePageChange() + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + /** 表单配置查询成功后触发的事件(不用等表单打开才触发) */ + function handleFormConfig(formConfig) { + // 处理扩展参数 + let extConfigJson = formConfig.head.extConfigJson; + if (extConfigJson) { + onlineExtConfigJson.value = JSON.parse(extConfigJson); + } + } + /** + * liaozhiyang + * 20250407 + * 【QQYUN-11801】生成测试数据 + * 判断是配置的菜单还是功能测试打开的 + * */ + async function isConfigUrl() { + const getMatchingisConfigUrl = (menus, path) => { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path && !item.redirect && !item.paramPath) { + return true; + } else if (item.children?.length) { + const result = getMatchingisConfigUrl(item.children, path); + if (result) { + return result; + } + } + } + return false; + }; + const path = route.path; + const menus = await getMenus(); + const result = getMatchingisConfigUrl(menus, path); + isConfigCurRoute.value = result; + } + isConfigUrl(); + return { + ID, + onlineQueryFormOuter, + superQueryButtonRef, + loading, + reload, + dataSource, + pagination, + tableReloading, + handleSpecialConfig, + onlineTableContext, + handleChangeInTable, + getColumnList, + getTreeDataByResult, + expandedRowKeys, + handleExpandedRowsChange, + onlineExtConfigJson, + handleFormConfig, + superQueryStatus, + handleSuperQuery, + registerCustomModal, + isConfigCurRoute, + pageLoading, + ...add2Context, + }; +} + +/** + * 兼容老版js增强 封装对象--暂不支持 + * + */ +export function useCompatibleOldVersion(context) { + Object.defineProperty(context, 'table', { + get() { + const arr = context['selectedRowKeys']; + const arr2 = context['selectedRows']; + return { + selectedRowKeys: arr, + selectedRows: arr2, + }; + }, + }); +} + +/** + * 链式调用?? + */ +export class AopSetup { + before: Function = () => {}; + after: Function = () => {}; + + constructor(before, after) { + if (typeof before == 'function') { + this.before = before; + } + if (typeof after == 'function') { + this.after = after; + } + } + + addTarget(prop, context) { + let key; + if (typeof prop == 'function') { + key = prop.name; + } else if (typeof prop == 'string') { + key = prop; + } else { + return; + } + if (typeof context == 'object') { + context[key] = this.around(context[key], context); + } + } + addTargets(array, context) { + for (let item of array) { + this.addTarget(item, context); + } + } + + around(targetFunction, context) { + const _that = this; + return async function () { + //console.log('this2', this) + let res1 = await _that.before(arguments); + console.log('before返回值', res1); + if (res1) { + console.log('错误信息', res1); + return; + } + let result = await targetFunction.apply(context, arguments); + await _that.after(arguments); + return result; + }; + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts new file mode 100644 index 000000000..cc6ed1a49 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts @@ -0,0 +1,494 @@ +import { useModalInner } from '/@/components/Modal'; +import { randomString } from '/@/utils/common/compUtils'; +import { reactive, ref, toRaw, watch } from 'vue'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { createLocalStorage } from '/@/utils/cache'; +import { useRoute } from 'vue-router'; +import FormSchemaFactory from '../../auto/comp/factory/FormSchemaFactory'; +import { FORM_VIEW_TO_QUERY_VIEW } from '../../types/onlineRender'; + +// 查询条件存储编码前缀 +const SAVE_CODE_PRE = 'JSuperQuerySaved_'; + +/** + * 查询项 + * */ +interface SuperQueryItem { + field: string | undefined; + rule: string | undefined; + val: string | number; + key: string; +} +/** + * 查询项-第一个控件树model + * */ +interface TreeModel { + title: string; + value: string; + isLeaf?: boolean; + disabled?: boolean; + children?: TreeModel[]; + order?: number; +} + +/** + * 查询信息保存结构 + * */ +interface SaveModel { + title: string; + content: string; + type: string; +} + +export function useSuperQuery() { + const { createMessage: $message } = useMessage(); + /** 表单ref*/ + const formRef = ref(); + + /** 数据*/ + const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({ + values: [], + }); + /** and/or */ + const matchType = ref('and'); + + // 弹框显示 + const [registerModal, { setModalProps }] = useModalInner(() => { + setModalProps({ confirmLoading: false }); + }); + + // 高级查询类型不支持联动组件,需要额外设置联动组件的view为text + const view2QueryViewMap = Object.assign({}, { link_down: 'text' }, FORM_VIEW_TO_QUERY_VIEW); + + /** + * 确认按钮事件 + */ + function handleSubmit() { + console.log('handleSubmit', dynamicRowValues.values); + } + + /** + * 关闭按钮事件 + */ + function handleCancel() { + //closeModal(); + } + + /** + * val组件赋值 + */ + function setFormModel(key: string, value: any, item: any) { + console.log('setFormModel', key, value); + // formModel[key] = value; + item['val'] = value; + } + + // 字段-Properties + const fieldProperties = ref({}); + // 字段-左侧查询项-树控件数据 + const fieldTreeData = ref([]); + + /** + * 初始化数据-最开始的方法 + * 1.获取 表名@字段名-->配置 这样的一个map + * 2.获取树形结构的数据 显示:文本; 存储:表名@字段名 + * 当树改变时,及时获取配置更新表单 + * @param json + */ + function init(json) { + let { allFields, treeData } = getAllFields(json); + fieldProperties.value = allFields; + fieldTreeData.value = treeData; + } + + /** + * 左侧查询项 添加一行 + * @param index + */ + function addOne(index) { + let item = { + field: undefined, + rule: 'eq', + val: '', + key: randomString(16), + }; + if (index === false) { + // 重置后需要调用 + dynamicRowValues.values = []; + dynamicRowValues.values.push(item); + } else if (index === true) { + // 打开弹框是需要调用 + if (dynamicRowValues.values.length == 0) { + dynamicRowValues.values.push(item); + } + } else { + // 其余就是 正常的点击加号增加行 + dynamicRowValues.values.splice(++index, 0, item); + } + } + + /** + * 左侧查询项 删除一行 + */ + function removeOne(item: SuperQueryItem) { + let arr = toRaw(dynamicRowValues.values); + let index = -1; + for (let i = 0; i < arr.length; i++) { + if (item.key == arr[i].key) { + index = i; + break; + } + } + if (index != -1) { + dynamicRowValues.values.splice(index, 1); + } + } + + // 默认的输入框 + const defaultInput = { + field: 'val', + label: '测试', + component: 'Input', + }; + + /** + * 左侧查询项 val组件 schema获取, 替代左侧字段树的change事件 + * @param item + * @param index + */ + function getSchema(item, index) { + let map = fieldProperties.value; + let prop = map[item.field]; + if (!prop) { + return defaultInput; + } + if (view2QueryViewMap[prop.view]) { + // 如果出现查询条件联动组件出来的场景,请跟踪此处 + prop.view = view2QueryViewMap[prop.view]; + } + let temp = FormSchemaFactory.createFormSchema(item.field, prop); + // temp.setFormRef(formRef) + temp.noChange(); + // 查询条件中的 下拉框popContainer为parentNode + temp.asSearchForm(); + temp.updateField(item.field + index); + const setFieldValue = (values) => { + item['val'] = values[item.field]; + }; + temp.setFunctionForFieldValue(setFieldValue); + let schema = temp.getFormItemSchema(); + //schema['valueField'] = 'val' + return schema; + } + + /*-----------------------右侧保存信息相关-begin---------------------------*/ + + /** + * 右侧树 的 数据 + */ + const saveTreeData = ref(''); + // 本地缓存 + const $ls = createLocalStorage(); + //需要保存的信息(一条) + const saveInfo = reactive({ + visible: false, + title: '', + content: '', + saveCode: '', + }); + //按钮loading + const loading = ref(false); + + // 当前页面路由 + const route = useRoute(); + // 监听路由信息,路由发生改变,则重新获取保存的查询信息-->currentPageSavedArray + watch( + () => route.fullPath, + (val) => { + console.log('fullpath', val); + initSaveQueryInfoCode(); + } + ); + + // 当前页面存储的 查询信息 + const currentPageSavedArray = ref([]); + // 监听当前页面是否有新的数据保存了,然后更新右侧数据->saveTreeData + watch( + () => currentPageSavedArray.value, + (val) => { + let temp: any[] = []; + if (val && val.length > 0) { + val.map((item) => { + let key = randomString(16); + temp.push({ + title: item.title, + slots: { icon: 'custom' }, + value: key, + }); + }); + } + saveTreeData.value = temp; + }, + { immediate: true, deep: true } + ); + + // 重新获取保存的查询信息 + function initSaveQueryInfoCode() { + let code = SAVE_CODE_PRE + route.fullPath; + saveInfo.saveCode = code; + let list = $ls.get(code); + if (list && list instanceof Array) { + currentPageSavedArray.value = list; + } + } + + // 执行一次 获取保存的查询信息 + initSaveQueryInfoCode(); + + /** + * 保存按钮事件 + */ + function handleSave() { + // 获取实际数据转成字符串 + let fieldArray = getQueryInfo(); + if (!fieldArray) { + $message.warning('空条件不能保存'); + return; + } + let content = JSON.stringify(fieldArray); + openSaveInfoModal(content); + } + + // 输入保存标题 弹框显示 + function openSaveInfoModal(content) { + saveInfo.visible = true; + saveInfo.title = ''; + saveInfo.content = content; + } + + /** + * 确认保存查询信息 + */ + function doSaveQueryInfo() { + let { title, content, saveCode } = saveInfo; + let index = getTitleIndex(title); + if (index >= 0) { + // 已存在是否覆盖 + Modal.confirm({ + title: '提示', + content: `${title} 已存在,是否覆盖?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + currentPageSavedArray.value.splice(index, 1, { + content, + title, + type: matchType.value, + }); + $ls.set(saveCode, currentPageSavedArray.value); + saveInfo.visible = false; + }, + }); + } else { + currentPageSavedArray.value.push({ + content, + title, + type: matchType.value, + }); + $ls.set(saveCode, currentPageSavedArray.value); + saveInfo.visible = false; + } + } + + // 根据填入的 title找本地存储的信息,如果有需要询问是否覆盖 + function getTitleIndex(title) { + let savedArray = currentPageSavedArray.value; + let index = -1; + for (let i = 0; i < savedArray.length; i++) { + if (savedArray[i].title == title) { + index = i; + break; + } + } + return index; + } + + /** + * 获取左侧所有查询条件,如果没有/或者条件无效则返回false + */ + function getQueryInfo(isEmit = false) { + let arr = dynamicRowValues.values; + if (!arr || arr.length == 0) { + return false; + } + let fieldArray: any = []; + let fieldProps = fieldProperties.value; + for (let item of arr) { + if (item.field && (item.val || item.val === 0) && item.rule) { + let tempVal: any = toRaw(item.val); + if (tempVal instanceof Array) { + tempVal = tempVal.join(','); + } + let obj = { + field: item.field, + rule: item.rule, + val: tempVal, + }; + if (isEmit === true) { + //如果当前数据用于emit事件,需要设置dbtype和type + let prop = fieldProps[item.field]; + if (prop) { + obj['type'] = prop.view; + obj['dbType'] = prop.type; + } + } + fieldArray.push(obj); + } + } + if (fieldArray.length == 0) { + return false; + } + return fieldArray; + } + + /** + * 右侧数据 点击事件,重新将数据显示到左侧 + * @param key + * @param node + */ + function handleTreeSelect(key, { node }) { + console.log(key, node); + let title = node.dataRef.title; + let arr = currentPageSavedArray.value.filter((item) => item.title == title); + if (arr && arr.length > 0) { + // 拿到数据渲染 + let { content, type } = arr[0]; + let data = JSON.parse(content); + let rowsValues: SuperQueryItem[] = []; + for (let item of data) { + rowsValues.push(Object.assign({}, { key: randomString(16) }, item)); + } + dynamicRowValues.values = rowsValues; + matchType.value = type; + } + } + + /** + * 右侧数据 删除事件 + */ + function handleRemoveSaveInfo(title) { + console.log(title); + let index = getTitleIndex(title); + if (index >= 0) { + currentPageSavedArray.value.splice(index, 1); + $ls.set(saveInfo.saveCode, currentPageSavedArray.value); + } + } + + /*-----------------------右侧保存信息相关-end---------------------------*/ + + // 获取所有字段配置信息 + function getAllFields(json) { + // 获取所有配置 查询字段 是否联合查询 + const { properties, table, title } = json; + let allFields = {}; + let order = 1; + let treeData: TreeModel[] = []; + let mainNode: TreeModel = { + title, + value: table, + disabled: true, + children: [], + }; + treeData.push(mainNode); + Object.keys(properties).map((field) => { + let item = properties[field]; + if (item.view == 'table') { + // 子表字段 + // 联合查询开启才需要子表字段作为查询条件 + let subProps = item['properties']; + let subTableOrder = order * 100; + let subNode: TreeModel = { + title: item.title, + value: field, + disabled: true, + children: [], + }; + Object.keys(subProps).map((subField) => { + let subItem = subProps[subField]; + // 保证排序统一 + subItem['order'] = subTableOrder + subItem['order']; + // 子表的分隔符要改成逗号,后台才能识别 + let subFieldKey = field + ',' + subField; + allFields[subFieldKey] = subItem; + subNode.children!.push({ + title: subItem.title, + value: subFieldKey, + isLeaf: true, + order: subItem['order'], + }); + }); + orderField(subNode); + treeData.push(subNode); + order++; + } else { + // 主表字段 + let fieldKey = table + '@' + field; + allFields[fieldKey] = item; + mainNode.children!.push({ + title: item.title, + value: fieldKey, + isLeaf: true, + order: item.order, + }); + } + }); + orderField(mainNode); + return { allFields, treeData }; + } + + //根据字段的order重新排序 + function orderField(data) { + let arr = data.children; + arr.sort(function (a, b) { + return a.order - b.order; + }); + } + + function initDefaultValues(values) { + const { params, matchType } = values; + if (params) { + let rowsValues: SuperQueryItem[] = []; + for (let item of params) { + rowsValues.push(Object.assign({}, { key: randomString(16) }, item)); + } + dynamicRowValues.values = rowsValues; + matchType.value = matchType; + } + } + + return { + formRef, + init, + dynamicRowValues, + matchType, + registerModal, + handleSubmit, + handleCancel, + handleSave, + doSaveQueryInfo, + saveInfo, + saveTreeData, + handleRemoveSaveInfo, + handleTreeSelect, + fieldTreeData, + addOne, + removeOne, + setFormModel, + getSchema, + loading, + getQueryInfo, + initDefaultValues, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useTableColumns.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useTableColumns.ts new file mode 100644 index 000000000..439078213 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/auto/useTableColumns.ts @@ -0,0 +1,648 @@ +import type { Ref } from 'vue'; +import type { ExtConfigType } from '../../types'; +import { HrefSlots, OnlineColumn } from '/@/components/jeecg/OnLine/types/onlineConfig'; +import { filterMultiDictObjs } from '/@/utils/dict/JDictSelectUtil'; +import { computed, defineAsyncComponent, h, reactive, ref, toRaw, unref, watch, markRaw } from 'vue'; +import { useRouter } from 'vue-router'; +import { Tag as ATag } from 'ant-design-vue'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { getAreaTextByCodeAnyLevel } from '/@/components/Form/src/utils/Area'; +import { createImgPreview } from '@/components/Preview'; +import { importViewsFile, _eval } from '/@/utils'; +import { useModal } from '/@/components/Modal'; +import LinkTableListPiece from '../../extend/linkTable/LinkTableListPiece.vue' +import { getToken } from "/@/utils/auth"; +import { downloadFile } from '/@/api/common/api'; +import { getWeekMonthQuarterYear, split } from '/@/utils'; +import { getItemColor } from "@/utils/dict/DictColors"; + +/** + * 获取实际列表需要的column配置 + * @param onlineTableContext 从数据库中查出来的数据 + * @param extConfigJson 扩展配置JSON + */ +export function useTableColumns(onlineTableContext, extConfigJson: Ref) { + // 获取路由器对象 href跳转用到 + let router = useRouter(); + + // 列信息 + const columns = ref>([]); + /** + * 20260309 + * liaozhiyang + * 【issues/9336】列宽拖动不了 + * */ + function applyResizableColumns(cols: OnlineColumn[]) { + cols.forEach((column) => { + if (!column.width) { + if (column.fieldType === 'date' || column.fieldType === 'Date') { + column.width = 120; + } else if (column.fieldType === 'link_table') { + column.width = 180; + } else { + column.width = 150; + } + } + column.resizable = true; + }); + } + + // 是否有bpm_status + //const hasBpmStatus = ref(false) + // 字典信息 + const dictOptionInfo = ref({}); + //已选择的值 + const selectedKeys = ref([]); + //选择的行记录 + //const selectRows = ref>([]); + // 选择列配置 --computed有问题 + const rowSelection = ref(null); + // 是否有滚动条 + let enableScrollBar = ref(true); + // table属性scroll + let tableScroll = computed(() => { + if (enableScrollBar.value == true) { + return undefined; + } else { + // X轴没有滚动条 + return { x: false }; + } + }); + + //用于 online列表的 某列的点击弹窗事件-弹窗显示其他表单 + const [registerOnlineHrefModal, { openModal: openOnlineHrefModal }] = useModal(); + const hrefMainTableId = ref('') + // 用于 online表单中 弹出别的表单 + const [registerPopModal, { openModal: openPopModal }] = useModal(); + const popTableId = ref('') + + // 对查询列信息的请求结果 处理方法 + function handleColumnResult(result, type = 'checkbox') { + // 字典设置 + dictOptionInfo.value = result.dictOptions; + // rowSelection设置 + if (result.checkboxFlag == 'Y') { + rowSelection.value = { + selectedRowKeys: selectedKeys, + onChange: onSelectChange, + type, + }; + } else { + rowSelection.value = null; + } + // 是否允许滚动条 + enableScrollBar.value = result.scrollFlag == 1; + + let dataColumns = result.columns; + dataColumns.forEach((column) => { + if (extConfigJson?.value?.canResizeColumn === 1) { + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + applyResizableColumns([column]); + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + } + + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + if (column.fieldExtendJson) { + const json = JSON.parse(column.fieldExtendJson); + if (!!json.isFixed) { + column.fixed = 'left'; + } + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + if (column.hrefSlotName && column.scopedSlots) { + const obj = result.fieldHrefSlots?.find((item) => item.slotName === column.hrefSlotName); + if (obj) { + column.fieldHref = obj; + } + } + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + Object.keys(column).map((key) => { + // 删掉空值的字段(不删除 空字符串('') 或 数字 0 ) + if (column[key] == null) { + delete column[key]; + } + }); + }); + + // href 跳转 + let fieldHrefSlots: HrefSlots[] = result.fieldHrefSlots; + const fieldHrefSlotKeysMap = {}; + fieldHrefSlots.forEach((item) => (fieldHrefSlotKeysMap[item.slotName] = item)); + + let tableColumns: OnlineColumn[] = []; + // 处理列中的 href 跳转和 dict 字典,使两者可以兼容存在 + tableColumns = handleColumnHrefAndDict(dataColumns, fieldHrefSlotKeysMap); + // 是否有 bpm_status字段 如果有,列表操作按钮需要增加提交流程按钮 + bpmStatusFilter(tableColumns); + + console.log('-----列表列配置----', tableColumns); + // 如果是树列表 需要设置第一列字段 及 第一列align + if (onlineTableContext.isTree() === true) { + // 找到第一列的配置 + let firstField = result.textField; + let index = -1; + for (let i = 0; i < tableColumns.length; i++) { + if (tableColumns[i].dataIndex == firstField) { + index = i; + break; + } + } + if (index > 0) { + //如果是0或是-1不需要处理 + let deleteColumns = tableColumns.splice(index, 1); + tableColumns.unshift(deleteColumns[0]); + } + //第一列居左 + if (tableColumns.length > 0) { + tableColumns[0].align = 'left'; + } + } + columns.value = tableColumns; + // 列发生了变化,需要重新渲染表格 + onlineTableContext.reloadTable(); + } + + /** + * 表格选择事件 [expose] + * @param selectedRowKeys + * @param selectRow + */ + function onSelectChange(selectedRowKeys, selectedRows) { + selectedKeys.value = selectedRowKeys; + onlineTableContext['selectedRows'] = toRaw(selectedRows); + onlineTableContext['selectedRowKeys'] = toRaw(selectedRowKeys); + } + + /** + * 处理列的href和字典翻译 + */ + function handleColumnHrefAndDict(columns: OnlineColumn[], fieldHrefSlotKeysMap: {}): OnlineColumn[] { + for (let column of columns) { + let { customRender, hrefSlotName, fieldType } = column; + // online 报表中类型配置为日期(yyyy-MM-dd ),但是实际展示为日期时间格式(yyyy-MM-dd HH:mm:ss) issues/3042 + if (fieldType == 'date' || fieldType == 'Date') { + column.customRender = ({ text }) => { + if (!text) { + return ''; + } + if (text.length > 10) { + return text.substring(0, 10); + } + return text; + }; + } else if (fieldType == 'link_table') { + // 关联记录列表展示 + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + const fieldExtendJson = column.fieldExtendJson ?? '{}'; + const json = JSON.parse(fieldExtendJson); + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + column.customRender = ({ text, record }) => { + if (!text) { + return ''; + } + if(onlineTableContext.isPopList===true){ + // 如果是弹窗的列表,关联记录的列只支持数据翻译,不需要跳转逻辑 + return record[column.dataIndex+"_dictText"] + }else{ + let tempIdArray = (text+'').split(','); + //update-begin-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let tempLabelArray = []; + if(record[column.dataIndex+"_dictText"]){ + tempLabelArray = record[column.dataIndex+"_dictText"].split(','); + } + //update-end-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let renderResult:any = [] + for(let i=0;ihandleClickLinkTable(id, hrefSlotName, json.isListReadOnly) + } + ); + renderResult.push(renderObj) + } + if(renderResult.length==0){ + return '' + } + //如果需要显示全,但是会换行:display: flex;width: 100%;flex-wrap: wrap;flex-direction: row; + return h('div',{style:{'overflow':'hidden'}}, renderResult); + } + }; + } else if (fieldType === 'popup_dict') { + // update-begin--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + column.customRender = ({ text, record }) => { + const dict = record[column.dataIndex + '_dictText']; + if (dict != undefined) { + return record[column.dataIndex + '_dictText']; + } + return text; + }; + // update-end--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + } else { + if (!hrefSlotName && column.scopedSlots && column.scopedSlots.customRender) { + //【Online报表】字典和href互斥 这里通过fieldHrefSlotKeysMap 先找到是href的列 + if (fieldHrefSlotKeysMap.hasOwnProperty(column.scopedSlots.customRender)) { + hrefSlotName = column.scopedSlots.customRender; + } + } + // 如果 customRender 有值则代表使用了字典 + // 如果 hrefSlotName 有值则代表使用了href跳转 + // 两者可以兼容。兼容的具体思路为:先获取到字典替换的值,再添加href链接跳转 + if (customRender || hrefSlotName) { + let dictCode = customRender as string; + let replaceFlag = '_replace_text_'; + // 自定义渲染函数的列 需要手动配置ellipsis + column.ellipsis = true; + column.customRender = ({ text, record }) => { + let value = text; + const valueSpan: any[] = []; + const getValue = () => valueSpan.length ? valueSpan : value; + // 如果 dictCode 有值,就进行字典转换 + if (dictCode) { + if (dictCode.startsWith(replaceFlag)) { + let textFieldName = dictCode.replace(replaceFlag, ''); + value = record[textFieldName]; + } else { + const dictItems = filterMultiDictObjs(unref(dictOptionInfo)[dictCode], text); + value = dictItems.map((item) => { + if (item.hasColor) { + //获取字体颜色 + const fontColor = getItemColor(item.color); + valueSpan.push(h(ATag, { + color: item.color, + style: { + 'color': fontColor, + 'margin-left': '5px', + }, + }, () => item.text)) + } + return item.text; + }).join(','); + } + } + // 扩展参数设置列的内容长度 + if (column.showLength) { + if (value && value.length > column.showLength) { + value = value.substr(0, column.showLength) + '...'; + } + } + // 如果 hrefSlotName 有值,就生成一个 a 标签,包裹住字典替换后(或原生)的值 + if (hrefSlotName) { + let field = fieldHrefSlotKeysMap[hrefSlotName]; + if (field) { + return h( + 'a', + { + onClick: () => handleClickFieldHref(field, record), + }, + getValue(), + ); + } + } + return h('span', {}, getValue()); + }; + } + + // 老版本叫scopedSlots 新版叫slots + if (column.scopedSlots) { + // slot的列 需要手动配置ellipsis + column.ellipsis = true; + let slots = column.scopedSlots; + column['slots'] = slots; + delete column.scopedSlots; + } + } + } + return columns; + } + + /** + * href 点击事件 + * @param field + * @param record + */ + function handleClickFieldHref(field, record) { + let href = field.href; + let urlPattern = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?/; + let compPattern = /\.vue(\?.*)?$/; + let jsPattern = /{{([^}]+)}}/g; // {{ xxx }} + if (typeof href === 'string') { + if(href.startsWith('ONLINE:')){ + // ONLINE:tableId:fieldName + let arr = href.split(':') + hrefMainTableId.value = arr[1]; + let fieldName = arr[2]; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: record[fieldName]}, + }) + }else{ + href = href.trim().replace(/\${([^}]+)?}/g, (_s1, s2) => record[s2]); + // 执行 {{...}} JS增强语句 + if (jsPattern.test(href)) { + href = href.replace(jsPattern, function (text, s0) { + try { + // 支持 {{ ACCESS_TOKEN }} 占位符 + if (s0.trim() === 'ACCESS_TOKEN') { + return getToken() + } + + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + return _eval(s0); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + } catch (e) { + console.error(e); + return text; + } + }); + } + if (urlPattern.test(href)) { + window.open(href, '_blank'); + } else if (compPattern.test(href)) { + // 处理弹框 + openHrefCompModal(href); + } else { + router.push(href); + } + } + } + } + + // 样式 + const dialogStyle = { + top: 0, + left: 0, + height: '100%', + margin: 0, + padding: 0, + }; + + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + // 弹窗属性配置 + const hrefComponent = reactive({ + model: { + title: '', + okText: '关闭', + width: '100%', + open: false, + destroyOnClose: true, + style: dialogStyle, + // dialogStyle: dialogStyle, + bodyStyle: { padding: '8px', height: 'calc(100vh - 108px)', overflow: 'auto', overflowX: 'hidden' }, + // 隐藏掉取消按钮 + cancelButtonProps: { style: { display: 'none' } }, + }, + on: { + ok: () => (hrefComponent.model.open = false), + cancel: () => (hrefComponent.model.open = false), + }, + is: null, + params: {}, + }); + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + + // 超链点击事件--> 打开一个modal窗口 + function openHrefCompModal(href) { + // 解析 href 参数 + let index = href.indexOf('?'); + let path = href; + if (index !== -1) { + path = href.substring(0, index); + let paramString = href.substring(index + 1, href.length); + let paramArray = paramString.split('&'); + let params = {}; + paramArray.forEach((paramObject) => { + let paramItem = paramObject.split('='); + params[paramItem[0]] = paramItem[1]; + }); + hrefComponent.params = params; + } else { + hrefComponent.params = {}; + } + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.open = true; + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.title = '操作'; + hrefComponent.is = markRaw(defineAsyncComponent(() => importViewsFile(path))); + } + + //如果是树列表 操作列只能右侧固定 + let fixedAction:any = 'right'; + if(onlineTableContext.isTree()){ + fixedAction = 'right' + } + const actionColumn = reactive({ + title: '操作', + dataIndex: 'action', + slots: { customRender: 'action' }, + fixed: fixedAction, + align: 'center', + width: 150, + }); + + // 监听扩展参数的固定列配置,动态改变操作列的固定方式 +watch(() => extConfigJson?.value, () => { + if (extConfigJson?.value?.tableFixedAction === 1) { + actionColumn.fixed = extConfigJson?.value?.tableFixedActionType || 'right'; + if (onlineTableContext.isTree()) { + actionColumn.fixed = 'right'; + } + } + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + if (extConfigJson?.value?.canResizeColumn === 1 && columns.value.length > 0) { + applyResizableColumns(columns.value); + onlineTableContext.reloadTable(); + } + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 +}); + + // 流程按钮状态 + function bpmStatusFilter(tableColumns: OnlineColumn[]): boolean { + let flag = false; + for (let i = 0; i < tableColumns.length; i++) { + let item = tableColumns[i]; + let fieldName = item.dataIndex; + if (fieldName!.toLowerCase() == 'bpm_status') { + flag = true; + break; + } + } + onlineTableContext['hasBpmStatus'] = flag; + return flag; + } + + /** + * 文件 + * @param text + */ + function downloadRowFile(text, record, column, id) { + if (!text) { + return; + } + // update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + if (text.indexOf(',') > 0) { + downloadFile(`/online/cgform/field/download/${id}/${record.id}/${column.dataIndex}`, `文件_${record.id}.zip`); + } else { + const url = getFileAccessHttpUrl(text); + window.open(url); + } + // update-end--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + } + + /** + * 图片 + * @param text + */ + function getImgView(text) { + if (text && text.indexOf(',') > 0) { + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + text = split(text)[0]; + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + } + return getFileAccessHttpUrl(text); + } + + /** + * 根据编码获取省市区文本 + * @param code + */ + function getPcaText(code, column) { + if (!code) { + return ''; + } + // update-begin--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + let includeParent = true; + let fieldExtendJson = column?.fieldExtendJson; + let level = 3; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (['province', 'city', 'region'].includes(fieldExtendJson.displayLevel)) { + if (fieldExtendJson.displayLevel === 'province') { + level = 1; + } else if (fieldExtendJson.displayLevel === 'city') { + level = 2; + } else if (fieldExtendJson.displayLevel === 'region') { + level = 3; + } + includeParent = false; + } + } + return getAreaTextByCodeAnyLevel(code, includeParent, level as 1 | 2 | 3); + // update-end--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + } + + /** + * 日期格式化 + * @param text + */ + function getFormatDate(text, column) { + if (!text) { + return ''; + } + let a = text; + if (a.length > 10) { + a = a.substring(0, 10); + } + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + let fieldExtendJson = column?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + const result = getWeekMonthQuarterYear(a); + return result[fieldExtendJson.picker]; + } + } + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + return a; + } + + watch(selectedKeys, () => { + onlineTableContext['selectedRowKeys'] = toRaw(selectedKeys.value); + }); + + onlineTableContext['clearSelectedRow'] = () => { + selectedKeys.value = []; + onlineTableContext['selectedRows'] = []; + onlineTableContext['selectedRowKeys'] = []; + }; + + /** + * 预览列表 cell 图片 + * @param text + */ + function viewOnlineCellImage(text) { + if (text) { + let imgList: any = []; + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + const arr = split(text); + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + for (let str of arr) { + if (str) { + imgList.push(getFileAccessHttpUrl(str)); + } + } + createImgPreview({ imageList: imgList }); + } + } + + /** + * link table控件在列表上显示 支持点击跳转表单 + * @param id + * @param hrefTableName + */ + const onlinePopModalRef = ref(); + async function handleClickLinkTable(id, hrefTableName, isListReadOnly){ + popTableId.value = hrefTableName; + let formStatus = await onlinePopModalRef.value.getFormStatus(); + // 判断当前表单是否支持编辑,不能编辑跳详情表单 + if(formStatus==true){ + hrefMainTableId.value = hrefTableName; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: id}, + }) + }else{ + openPopModal(true, { + isUpdate: true, + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + disableSubmit: isListReadOnly ? true : false, + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + record: { + id: id + } + }); + } + } + + return { + columns, + actionColumn, + selectedKeys, + rowSelection, + enableScrollBar, + tableScroll, + downloadRowFile, + getImgView, + getPcaText, + getFormatDate, + handleColumnResult, + onSelectChange, + hrefComponent, + viewOnlineCellImage, + hrefMainTableId, + registerOnlineHrefModal, + registerPopModal, + openPopModal, + openOnlineHrefModal, + onlinePopModalRef, + popTableId, + handleClickFieldHref, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformList.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformList.ts new file mode 100644 index 000000000..e9f195ced --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformList.ts @@ -0,0 +1,503 @@ +import { h, ref, nextTick } from 'vue'; +import { useRouter } from 'vue-router'; +import { Input, Radio, RadioGroup } from 'ant-design-vue'; +import { ActionItem, BasicColumn, FormSchema } from '/@/components/Table'; +import { useModal } from '/@/components/Modal'; +import { useDrawer } from '/@/components/Drawer'; +import { CgformPageType } from '../types'; +import { useListPage } from '/@/hooks/system/useListPage'; +import { + doBatchDelete, + doBatchRemove, + doSingleDelete, + doSingleRemove, + doCopyOnlineView, + doDatabaseSync, + doCopyTable, + list, +} from '../cgform.api'; +// import { useCopyModal } from './useCopyCgformModal'; +import { isArray } from '/@/utils/is'; +import {useCgformStore} from "../store/cgformState"; +import {showListDeleteConfirm} from "./useCgformWidgets.jsx"; + +interface IOptions { + // 页面类型 + pageType: CgformPageType; + designScope: string; + columns: BasicColumn[]; + formSchemas: FormSchema[]; +} + +export function useCgformList(options: IOptions) { + const isNormalPage = options.pageType === CgformPageType.normal; + const router = useRouter(); + const cgformStore = useCgformStore(); + const tableRef = ref(); + // 列表页面公共参数、方法 + const pageContext = useListPage({ + designScope: options.designScope, + tableProps: { + api: list, + columns: options.columns, + formConfig: { + //labelWidth: 200, + schemas: options.formSchemas, + }, + beforeFetch: (params) => { + let copyType = isNormalPage ? 0 : 1; + let physicId = isNormalPage ? undefined : router.currentRoute.value.params.code; + // TODO 等字典组件支持逗号分割后删除改代码【LOWCOD-2371】 + if (isArray(params.tableType_MultiString)) { + params.tableType_MultiString = params.tableType_MultiString.join(','); + } + return Object.assign(params, { copyType, physicId }); + }, + }, + }); + const { tableContext, createMessage: $message, createConfirm: $confirm } = pageContext; + // 注册table数据 + const [, { reload, setLoading }, { selectedRowKeys, selectedRows }] = tableContext; + + // 注册编辑弹窗 e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + const [registerCgformModal, cgformModal] = useModal(); + // 注册从数据库导入表单 + const [registerDbToOnlineModal, dbToOnlineModal] = useModal(); + // 注册ai建表弹窗 + const [registerAiToOnlineModal, aiToOnlineModal] = useModal(); + // 注册代码生成弹窗 + const [registerCodeGeneratorModal, codeGeneratorModal] = useModal(); + // 注册自定义按钮弹窗 + const [registerCustomButtonModal, customButtonModal] = useModal(); + // 注册JS增强弹窗 + const [registerEnhanceJsModal, enhanceJsModal] = useModal(); + // 注册SQL增强弹窗 + const [registerEnhanceSqlModal, enhanceSqlModal] = useModal(); + // 注册Java增强弹窗 + const [registerEnhanceJavaModal, enhanceJavaModal] = useModal(); + // 注册权限管理抽屉 + const [registerAuthManagerDrawer, authManagerDrawer] = useDrawer(); + // 注册角色授权弹窗 + const [registerAuthSetterModal, authSetterModal] = useModal(); + + function onAdd() { + cgformModal.openModal(true, { isUpdate: false }); + } + function onAiCreateTable(){ + aiToOnlineModal.openModal(true); + } + function onCreateAiTable() { + reload(); + } + let thatRecord: Nullable = null + + function onEdit(record) { + thatRecord = record + cgformModal.openModal(true, { isUpdate: true, record }); + } + + function onSuccess() { + if (thatRecord?.id) { + cgformStore.addChangedTable(thatRecord.id) + thatRecord = null + } + reload(); + } + + /** + * 删除事件 + */ + async function handleDelete(id) { + await doSingleDelete(id); + reload(); + } + + /** + * 移除事件 + */ + async function handleRemove(id) { + await doSingleRemove(id); + reload(); + } + + /** + * 删除单条数据 + * @param record + */ + function onDeleteRecord(record: Recordable) { + return showListDeleteConfirm(() => handleDelete(record.id), () => handleRemove(record.id)) + } + + // 批量删除 + function onDeleteBatch() { + let idList = selectedRowKeys.value as string[]; + if (idList.length <= 0) { + $message.warning('请先选择一条记录!'); + return; + } + showListDeleteConfirm( + () => executeDelete(doBatchDelete, idList, true), + () => executeDelete(doBatchRemove, idList, true) + ) + } + + /** + * 执行删除操作 + * @param fn 删除方法 + * @param idList 删除参数 + * @param clearSelected 清空选择 + */ + async function executeDelete(fn: Fn, idList: string[], clearSelected = false) { + try { + setLoading(true); + const res = await fn(idList); + reload(); + if (clearSelected) { + selectedRowKeys.value = []; + } + return res; + } finally { + setLoading(false); + } + return Promise.reject(); + } + + // 显示自定义按钮弹窗 + function onShowCustomButton() { + getSelectedRows(([row]) => customButtonModal.openModal(true, { row })); + } + + // 显示 js 增强弹窗 + function onShowEnhanceJs() { + getSelectedRows(([row]) => enhanceJsModal.openModal(true, { row })); + } + + // 显示 sql 增强弹窗 + function onShowEnhanceSql() { + getSelectedRows(([row]) => enhanceSqlModal.openModal(true, { row })); + } + + // 显示 java 增强弹窗 + function onShowEnhanceJava() { + getSelectedRows(([row]) => enhanceJavaModal.openModal(true, { row })); + } + + // 显示导入数据库表弹窗 + function onImportDbTable() { + dbToOnlineModal.openModal(true, {}); + } + + function getSelectedRows(fn: Fn, min = 1, max = 1) { + if (selectedRows.value.length < min) { + $message.warning(`请先至少选中 ${min} 条记录`); + } else if (selectedRows.value.length > max) { + $message.warning(`最多只能选中 ${min} 条记录`); + } else { + fn(selectedRows.value); + } + } + + // 显示代码生成弹窗 + function onGenerateCode() { + if (selectedRows.value.length === 0) { + $message.warning('请先选中一条记录'); + } else if (selectedRows.value.length > 1) { + $message.warning('代码生成只能选中一条记录'); + } else { + let row = selectedRows.value[0]; + if (!row) { + $message.warning('请选中当前页的数据!'); + } else if (row.isDbSynch != 'Y') { + $message.warning('请先同步数据库!'); + } else if (row.tableType == 3) { + $message.warning('请选中该表对应的主表'); + } else { + codeGeneratorModal.openModal(true, { code: row.id }); + } + } + } + + // 功能测试 + function onGoToTest(record) { + console.log(record); + if (record.isTree == 'Y') { + router.push({ path: '/online/cgformTreeList/' + record.id }); + } else { + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + switch (record.themeTemplate) { + case 'erp': + router.push({ path: '/online/cgformErpList/' + record.id }); + break; + case 'tab': + router.push({ path: '/online/cgformTabList/' + record.id }); + break; + case 'innerTable': + router.push({ path: '/online/cgformInnerTableList/' + record.id }); + break; + default: + router.push({ path: '/online/cgformList/' + record.id }); + break; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + } + } + + // 同步数据库 + function onSyncDatabase(record) { + const syncMethod = ref('normal'); + const disabled = ref(false); + const modalFunc = $confirm({ + iconType: 'info', + title: '同步数据库', + content: () => + h( + 'div', + { + style: 'margin: 20px 0;', + }, + h( + RadioGroup, + { + value: syncMethod.value, + disabled: disabled.value, + 'onUpdate:value': (v) => (syncMethod.value = v), + }, + () => [h(Radio, { value: 'normal' }, () => '普通同步(保留表数据)'), h(Radio, { value: 'force' }, () => '强制同步(删除表,重新生成)')] + ) + ), + maskClosable: true, + okText: '开始同步', + async onOk() { + disabled.value = true; + modalFunc.update({ + maskClosable: false, + keyboard: false, + okText: '同步中…', + okButtonProps: { loading: disabled.value }, + cancelButtonProps: { disabled: disabled.value }, + }); + try { + await doDatabaseSync(record.id, syncMethod.value); + } catch (e: any) { + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-244】同步数据库没权限时提示两次 + // $message.warn(e.message || e); + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-244】同步数据库没权限时提示两次 + } finally { + await reload(); + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7931】勾选后再同步数据库,再点击代码生成无法生成代码 + nextTick(() => { + if (selectedRows.value.length) { + selectedRows.value.forEach((item) => { + const dataSource = tableRef.value.getDataSource() ?? []; + const findItem = dataSource.find((o) => o['id'] === item['id']); + if (findItem) { + Object.assign(item, findItem); + } + }); + } + }); + // update-end--author:liaozhiyang---date:20250318---for:【issues/7931】勾选后再同步数据库,再点击代码生成无法生成代码 + } + }, + }); + } + + // const { createCopyModal } = useCopyModal(); + const [registerAddressModal, addressModal] = useModal(); + + // 显示online地址弹窗 + function onShowOnlineUrl(record) { + let onlineUrl: string; + if (record.themeTemplate === 'erp') { + onlineUrl = `/online/cgformErpList/${record.id}`; + } else if (record.themeTemplate === 'innerTable') { + onlineUrl = `/online/cgformInnerTableList/${record.id}`; + } else if (record.themeTemplate === 'tab') { + onlineUrl = `/online/cgformTabList/${record.id}`; + } else if (record.isTree == 'Y') { + onlineUrl = `/online/cgformTreeList/${record.id}`; + } else { + onlineUrl = `/online/cgformList/${record.id}`; + } + addressModal.openModal(true, { + title: `菜单链接【${record.tableTxt}】`, + content: onlineUrl, + copyText: onlineUrl, + copyTitle: `${record.tableTxt}`, + record, + }); + } + + /** + * 显示复制表弹窗 + * @param record + */ + function onCopyTable(record) { + const tableName = ref(record.tableName + '_copy'); + $confirm({ + title: '复制表', + content: () => + h( + 'div', + { + style: 'margin: 20px 0;', + }, + [ + '请输入新表名:', + h(Input, { + value: tableName.value, + 'onUpdate:value': (v) => (tableName.value = v), + }), + ] + ), + iconType: 'info', + closable: true, + okText: '复制', + onOk() { + if (!tableName.value) { + $message.warning('请输入新表名'); + } else if (tableName.value === record.tableName) { + $message.warning('新表名和旧表名不能一致'); + } else { + doCopyTable(record.id, tableName.value).then(reload); + } + }, + }); + } + + /** + * 删除视图 + */ + function doDeleteView(record) { + $confirm({ + title: '删除', + content: '确定要删除该视图吗?', + iconType: 'warning', + closable: true, + maskClosable: true, + onOk: () => { + handleRemove(record.id) + } + }) + } + + /** + * 操作栏 + */ + function getTableAction(record) { + return [ + { + label: '编辑', + onClick: () => onEdit(record), + }, + ]; + } + + /** + * 下拉操作栏 + */ + function getDropDownAction(record): ActionItem[] { + return [ + { + label: '同步数据库', + onClick: () => onSyncDatabase(record), + ifShow: () => isNormalPage && record.isDbSynch != 'Y', + }, + { + // TODO 功能测试 + label: '功能测试', + class: ['low-app-hide'], + onClick: () => onGoToTest(record), + ifShow: () => (isNormalPage ? record.isDbSynch == 'Y' && record.tableType !== 3 : true), + }, + { + label: '配置地址', + class: ['low-app-hide'], + onClick: () => onShowOnlineUrl(record), + ifShow: () => (isNormalPage ? record.isDbSynch == 'Y' && record.tableType !== 3 : true), + }, + { + label: '权限控制', + onClick: () => authManagerDrawer.openDrawer(true, { cgformId: record.id, tableType: record.tableType}), + }, + { + label: '角色授权', + onClick: () => authSetterModal.openModal(true, { cgformId: record.id }), + }, + { + label: '视图管理', + class: ['low-app-hide'], + onClick: () => router.push(`/online/copyform/${record.id}`), + ifShow: () => isNormalPage && record.hascopy == 1, + }, + { + label: '生成视图', + class: ['low-app-hide'], + // @ts-ignore + popConfirm: { + title: '确定生成视图吗?', + placement: 'left', + confirm: () => { + setLoading(true); + doCopyOnlineView(record.id) + .then(() => { + $message.success('已成功生成视图'); + }) + .finally(() => { + setLoading(false); + reload(); + }); + }, + }, + ifShow: () => isNormalPage, + }, + { + label: '复制表', + onClick: () => onCopyTable(record), + ifShow: () => isNormalPage, + }, + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8485】online删除提示优化 + { + label: '删除', + onClick: () => onDeleteRecord(record), + ifShow: () => isNormalPage, + }, + // update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8485】online删除提示优化 + { + label: '删除视图', + onClick: () => doDeleteView(record), + ifShow: () => !isNormalPage, + }, + ]; + } + + return { + router, + pageContext, + onAdd, + onAiCreateTable, + onSuccess, + onDeleteBatch, + onImportDbTable, + onGenerateCode, + onShowCustomButton, + onShowEnhanceJs, + onShowEnhanceSql, + onShowEnhanceJava, + onCreateAiTable, + getTableAction, + getDropDownAction, + registerCustomButtonModal, + registerEnhanceJsModal, + registerEnhanceSqlModal, + registerEnhanceJavaModal, + registerAuthManagerDrawer, + registerAuthSetterModal, + registerCgformModal, + registerDbToOnlineModal, + registerCodeGeneratorModal, + registerAiToOnlineModal, + registerAddressModal, + tableRef, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformWidgets.jsx b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformWidgets.jsx new file mode 100644 index 000000000..70bb3ff71 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCgformWidgets.jsx @@ -0,0 +1,42 @@ +import {Button, Space} from 'ant-design-vue' +import {useMessage} from "@/hooks/web/useMessage"; + +const {createConfirm} = useMessage() + +/** + * 创建列表删除时的提示弹窗 + * @param deleteFn 删除方法 + * @param removeFn 移出方法 + */ +export function showListDeleteConfirm(deleteFn, removeFn) { + const {destroy} = createConfirm({ + title: '确认删除表单吗?', + content: () => ( +

+
移除:仅删除配置,保留数据库表和数据
+
删除:同时删除数据库表和数据(不可恢复)
+
+ ), + iconType: 'info', + closable: true, + maskClosable: true, + width: 400, + footer: () => ( +
+ + + + + +
+ ), + }); + + function getFn(func) { + return async () => { + await func() + destroy() + } + } + +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCopyCgformModal.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCopyCgformModal.ts new file mode 100644 index 000000000..b211e8b44 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useCopyCgformModal.ts @@ -0,0 +1,94 @@ +import { isRef, unref, watch, Ref, ComputedRef } from 'vue'; +import Clipboard from 'clipboard'; +import { ModalOptionsEx, useMessage } from '/@/hooks/web/useMessage'; +import {buildUUID} from "/@/utils/uuid"; + +/** 带复制按钮的弹窗 */ +interface IOptions extends ModalOptionsEx { + // 要复制的文本,可以是一个 ref 对象,动态更新 + copyText: string | Ref | ComputedRef; + copyTitle: string; + componentName: string; +} + +const COPY_CLASS = 'copy-this-text'; +const CLIPBOARD_TEXT = 'data-clipboard-text'; + +export function useCopyModal() { + return { createCopyModal }; +} + +const { createMessage, createConfirm } = useMessage(); + +/** 创建复制弹窗 */ +function createCopyModal(options: Partial) { + const url = unref(options.copyText); + let menuComponentName = options.componentName? unref(options.componentName): null; + const insertMenuSql = `INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external) + VALUES ('${buildUUID()}', NULL, '${options.copyTitle}', '${url}', '1', '${menuComponentName}', NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', null, NULL, NULL, 0)`; + + + let modal = createConfirm({ + ...options, + iconType: options.iconType ?? 'info', + width: options.width ?? 500, + title: options.title ?? '复制', + closable: true, + maskClosable: options.maskClosable ?? true, + cancelText: '复制SQL', + okText: options.okText ?? '复制URL', + cancelButtonProps: { + class: 'copy-this-sql', + 'data-clipboard-text': insertMenuSql, + } as any, + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: url, + } as any, + onOk() { + return new Promise((resolve: any) => { + const clipboard = new Clipboard('.' + COPY_CLASS); + clipboard.on('success', () => { + clipboard.destroy(); + createMessage.success('复制URL成功'); + resolve(); + }); + clipboard.on('error', () => { + createMessage.error('该浏览器不支持自动复制'); + clipboard.destroy(); + resolve(); + }); + }); + }, + onCancel() { + return new Promise((resolve: any) => { + const clipboard = new Clipboard('.copy-this-sql'); + clipboard.on('success', () => { + clipboard.destroy(); + createMessage.success('复制插入菜单SQL成功'); + resolve(); + }); + clipboard.on('error', () => { + createMessage.error('该浏览器不支持自动复制'); + clipboard.destroy(); + resolve(); + }); + }); + }, + }); + + // 动态更新 copyText + if (isRef(options.copyText)) { + watch(options.copyText, (copyText) => { + modal.update({ + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: copyText, + } as any, + }); + }); + } + return modal; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useGuide.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useGuide.ts new file mode 100644 index 000000000..e80ff0c47 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useGuide.ts @@ -0,0 +1,95 @@ +import { onMounted, ref } from 'vue'; +import { useDesign } from '/@/hooks/web/useDesign'; +import intro from 'intro.js'; +import 'intro.js/minified/introjs.min.css'; + +export const useGuide = () => { + const { prefixVar } = useDesign(''); + const aiCreateTable = ref(`${prefixVar}-online-aiCreateTable`); + const newAddBtn = ref(`${prefixVar}-online-newAddBtn`); + const customBtn = ref(`${prefixVar}-online-customBtn`); + const enhanceJsBtn = ref(`${prefixVar}-online-enhanceJsBtn`); + const enhanceSqlBtn = ref(`${prefixVar}-online-enhanceSqlBtn`); + const enhanceJavaBtn = ref(`${prefixVar}-online-enhanceJavaBtn`); + const exportDbBtn = ref(`${prefixVar}-online-exportDbBtn`); + const codeGeneratorBtn = ref(`${prefixVar}-online-codeGenerator`); + const key = `${prefixVar}-online-guide`; + const guide = () => { + let boot = intro(); + boot.setOptions({ + nextLabel: '下一步', + prevLabel: '上一步', + //skipLabel: '跳过', + doneLabel: '完成', + steps: [ + { + title: '第一步', + element: document.querySelector(`.${newAddBtn.value}`)!, + intro: '点击新增按钮,新建一个表。', + }, + { + title: '第二步', + intro: `在列表中找到刚才新建数据,在操作列点击"更多",选择"同步数据库"。`, + }, + { + title: '第三步', + intro: `在列表中找到刚才新建数据,在操作列点击"更多",选择"功能测试"。`, + }, + { + title: 'AI建表', + element: document.querySelector(`.${aiCreateTable.value}`)!, + intro: `输入修饰词即可通过AI创建工作表`, + }, + { + title: '代码生成', + element: document.querySelector(`.${codeGeneratorBtn.value}`)!, + intro: `选中一条记录,通过代码生成可将已配置好的表单,一键生成前后端代码,复杂需求可在此基础上进行二次开发。`, + }, + { + title: '自定义按钮', + element: document.querySelector(`.${customBtn.value}`)!, + intro: `选中一条记录,点击自定义按钮,配置按钮相关信息即可在当前记录的"功能测试"页面新增一个按钮`, + }, + { + title: 'JS强增', + element: document.querySelector(`.${enhanceJsBtn.value}`)!, + intro: `选中一条记录,通过js增强可为"自定义按钮"添加不同操作,可操作列表和表单数据等,也可以添加表单前置事件。`, + }, + { + title: 'SQL增强', + element: document.querySelector(`.${enhanceSqlBtn.value}`)!, + intro: `选中一条记录,通过增强SQL,可以关联修改业务数据。`, + }, + { + title: 'java增强', + element: document.querySelector(`.${enhanceJavaBtn.value}`)!, + intro: `选中一条记录,通过Java增强可在表单的增加、修改、和删除数据时实现额外的功能,类似spring中的AOP切面编程。`, + }, + { + title: '导入数据库表', + element: document.querySelector(`.${exportDbBtn.value}`)!, + intro: `可将已有数据库中的表,直接导入生成表单。`, + }, + ], + }); + boot.start(); + }; + onMounted(() => { + if (!localStorage.getItem(key)) { + setTimeout(() => { + guide(); + localStorage.setItem(key, '1'); + }, 2e3); + } + }); + return { + newAddBtn, + customBtn, + enhanceJsBtn, + enhanceSqlBtn, + exportDbBtn, + enhanceJavaBtn, + codeGeneratorBtn, + aiCreateTable + }; +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useSchemas.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useSchemas.ts new file mode 100644 index 000000000..56d4d4350 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useSchemas.ts @@ -0,0 +1,928 @@ +import { computed, h, Ref } from 'vue'; +import { FormSchema, RenderCallbackParams } from '/@/components/Form'; +import { Input, Button } from 'ant-design-vue'; +import { FolderOpenOutlined } from '@ant-design/icons-vue'; +import { bindMapFormSchema } from '/@/utils/common/compUtils'; +import { usePermission } from '/@/hooks/web/usePermission'; +import { rules } from '/@/utils/helper/validator'; + +const { isDisabledAuth } = usePermission(); + +export function useFormSchemas(_props, expandingConfig, handlers) { + type SpanType = 'one' | 'tow' | 'three'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 2 }, + wrapperCol: { xs: 24, sm: 22 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 4 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 三列 + three: { + colProps: { xs: 24, sm: 8 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 18 }, + }, + }, + }, + 'three' + ); + + // 表单 FormSchemas + const formSchemas: FormSchema[] = [ + { label: '', field: 'id', component: 'Input', show: false }, + { label: '', field: 'tableVersion', component: 'Input', show: false }, + mapFormSchema({ + label: '表名', + field: 'tableName', + component: 'Input', + required: true, + // 如果版本号为1 表示未曾修改 未曾同步 可以修改表名 + dynamicDisabled: ({ model }) => model.tableVersion && model.tableVersion != 1, + dynamicRules: ({ model, schema }) => { + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8492】online表名校验不允许输入中文 + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + let reg = /[\u4E00-\u9FA5]/g; + if (reg.test(value)) { + reject('不允许输入中文'); + } + resolve(); + }); + }, + }, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 50) { + reject('表名最长50个字符'); + } + resolve(); + }); + }, + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + ...rules.duplicateCheckRule('onl_cgform_head', 'table_name', model, schema, true), + ]; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8492】online表名校验不允许输入中文 + }, + }), + mapFormSchema({ + label: '表描述', + field: 'tableTxt', + component: 'Input', + required: true, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + dynamicRules: ({ model, schema }) => { + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 200) { + reject('表描述最长200个字'); + } + resolve(); + }); + }, + }, + ]; + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + }), + mapFormSchema({ + label: '表类型', + field: 'tableType', + component: 'Select', + defaultValue: 1, + componentProps: { + options: [ + { label: '单表', value: 1 }, + { label: '主表', value: 2 }, + { label: '附表', value: 3 }, + ], + onChange: handlers.onTableTypeChange, + allowClear: false, + }, + }), + // 此处为占位符,用于将 relationType 顶到最右边 + { + label: '', + field: 'relationType', + component: 'InputNumber', + render: () => '', + colProps: { xs: 0, sm: 17 }, + ifShow: fieldIfShow, + }, + mapFormSchema({ + label: '', + field: 'relationType', + component: 'RadioGroup', + defaultValue: 0, + componentProps: { + options: [ + { label: '一对多', value: 0 }, + { label: '一对一', value: 1 }, + ], + allowClear: false, + onChange: handlers.onRelationTypeChange, + }, + colProps: { xs: 24, sm: 4 }, + itemProps: { + colon: false, + labelCol: { xs: 0, sm: 0 }, + wrapperCol: { xs: 24, sm: 24 }, + }, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '序号', + field: 'tabOrderNum', + component: 'InputNumber', + componentProps: { + style: { + width: '100%', + }, + }, + colProps: { xs: 24, sm: 3 }, + itemProps: { + labelCol: { xs: 24, sm: 7 }, + wrapperCol: { xs: 24, sm: 24 - 7 }, + }, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '表单分类', + field: 'formCategory', + component: 'JDictSelectTag', + defaultValue: 'temp', + componentProps: { + dictCode: 'ol_form_biz_type', + allowClear: false, + }, + }), + mapFormSchema({ + label: '主键策略', + field: 'idType', + component: 'Select', + defaultValue: 'UUID', + componentProps: { + options: [ + { label: 'ID_WORKER(分布式自增)', value: 'UUID' }, + // { label: 'NATIVE(自增长方式)', value: 'NATIVE' }, + // { label: 'SEQUENCE(序列方式)', value: 'SEQUENCE' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '序号名称', + field: 'idSequence', + component: 'Input', + componentProps: {}, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '显示复选框', + field: 'isCheckbox', + component: 'Select', + defaultValue: 'Y', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '主题模板', + field: 'themeTemplate', + component: 'Select', + defaultValue: 'normal', + componentProps: { + options: [ + { label: '默认主题', value: 'normal' }, + { label: 'ERP主题(一对多)', value: 'erp' }, + { label: '内嵌子表主题(一对多)', value: 'innerTable' }, + { label: 'TAB主题(一对多)', value: 'tab' }, + ], + allowClear: false, + }, + // 单表时禁用该字段 + dynamicDisabled: ({ model }) => model.tableType === 1, + // update-begin--author:liaozhiyang---date:20231123---for:【QQYUN-7073】提示ERP、内嵌子表不支持联合查询 + dynamicRules() { + return [ + { + validator({}, value) { + const data = expandingConfig.value; + if (value === 'erp') { + if (data.joinQuery) { + return Promise.reject('ERP不支持联合查询功能'); + } + } else if (value === 'innerTable') { + if (data.joinQuery) { + return Promise.reject('内嵌子表不支持联合查询功能'); + } + } + return Promise.resolve(); + }, + }, + ]; + }, + // update-end--author:liaozhiyang---date:20231123---for:【QQYUN-7073】提示ERP、内嵌子表不支持联合查询 + }), + mapFormSchema({ + label: '表单风格', + field: 'formTemplate', + component: 'Select', + defaultValue: '1', + componentProps: { + options: [ + { label: '一列', value: '1' }, + { label: '两列', value: '2' }, + { label: '三列', value: '3' }, + { label: '四列', value: '4' }, + ], + placeholder: '请选择PC表单风格', + allowClear: false, + }, + }), + mapFormSchema({ + label: '移动表单风格', + field: 'formTemplateMobile', + component: 'Select', + defaultValue: '1', + componentProps: { + options: [ + { label: 'AntDesign模板', value: '1' }, + { label: 'Bootstrap模板', value: '2' }, + ], + placeholder: '请选择移动表单风格', + }, + // 暂时先隐藏 + ifShow: false, + }), + mapFormSchema({ + label: '滚动条', + field: 'scroll', + component: 'Select', + defaultValue: 1, + componentProps: { + options: [ + { label: '有', value: 1 }, + { label: '无', value: 0 }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '是否分页', + field: 'isPage', + component: 'Select', + defaultValue: 'Y', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '是否树', + field: 'isTree', + component: 'Select', + defaultValue: 'N', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + onChange: handlers.onIsTreeChange, + allowClear: false, + }, + dynamicRules({ model }) { + return [ + { + validator({}, value) { + if (value === 'Y' && (model.tableType == 2 || model.tableType == 3)) { + return Promise.reject('主表和附表不支持树类型!'); + } else { + } + return Promise.resolve(); + }, + }, + ]; + }, + // update-begin--author:liaozhiyang---date:20240604---for:【TV360X-125】选择主表和附表时隐藏树表配置 + show({ model, values }) { + if (model.tableType == 2 || model.tableType == 3) { + model.isTree = 'N'; + handlers.onIsTreeChange('N'); + return false; + } + return true; + }, + // update-end--author:liaozhiyang---date:20240604---for:【TV360X-125】选择主表和附表时隐藏树表配置 + }), + mapFormSchema({ + // 空格不要删,否则布局会乱 + label: ' ', // 扩展配置 + field: 'extConfigJson', + component: 'Input', + slot: 'extConfigButton', + itemProps: { colon: false }, + // 一对多子表时隐藏扩展配置按钮 + ifShow: ({ model }) => !(model.tableType === 3 && model.relationType === 0), + }), + mapFormSchema({ + label: '树表单父ID', + field: 'treeParentIdField', + component: 'Input', + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '是否有子节点字段', + field: 'treeIdField', + component: 'Input', + show: false, + }), + mapFormSchema({ + label: '树开表单列', + field: 'treeFieldname', + required: true, + component: 'Input', + ifShow: fieldIfShow, + }), + mapFormSchema( + { + label: '附表', + field: 'subTableStr', + component: 'Input', + componentProps: { + disabled: true, + placeholder: ' ', + allowClear: false, + }, + ifShow: handlers.ifShowOfSubTableStr, + }, + 'one' + ), + ]; + + // 控制字段是否显示 + function fieldIfShow({ field, model }: RenderCallbackParams) { + switch (field) { + case 'relationType': + case 'tabOrderNum': + return model.tableType === 3; + case 'treeParentIdField': + case 'treeIdField': + case 'treeFieldname': + return model.isTree === 'Y'; + case 'idSequence': + return model.idType === 'SEQUENCE'; + } + return true; + } + + return { formSchemas }; +} + +/** 获取 扩展参数 FormSchemas */ +export function useExtendConfigFormSchemas(_props, handlers) { + type SpanType = 'left' | 'right'; + // formItem 的绑定值,统一布局 + const mapFormSchema = bindMapFormSchema( + { + left: { + colProps: { xs: 24, sm: 7 }, + itemProps: { + labelCol: { xs: 24, sm: 11 }, + wrapperCol: { xs: 24, sm: 13 }, + }, + style: { width: '100%' }, + }, + right: { + colProps: { xs: 24, sm: 17 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + style: { width: '100%' }, + }, + }, + 'left' + ); + + // 一对一子表时只显示固定操作列、列宽拖动、表单Label长度 + function isNotOneToOneSub() { + const { tableType, relationType } = _props.parentForm.getFieldsValue(['tableType', 'relationType']); + return !(tableType === 3 && relationType === 1); + } + + const formSchemas: FormSchema[] = [ + // 弹窗 + mapFormSchema( + { + label: '弹窗默认全屏', + field: 'modelFullscreen', + ifShow: isNotOneToOneSub, + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + }, + 'left' + ), + mapFormSchema( + { + label: '弹窗宽度', + field: 'modalMinWidth', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + componentProps: { + style: 'width: 80%', + placeholder: '弹窗最小宽度(单位:px)', + }, + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-77】弹窗全屏后,输入宽度框禁用 + dynamicDisabled: ({ model }) => model.modelFullscreen, + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-77】弹窗全屏后,输入宽度框禁用 + }, + 'right' + ), + //----------------------------表单评论 begin----------------------------------------- + mapFormSchema( + { + label: '开启表单评论', + field: 'commentStatus', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'commentStatus', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + render: () => '', + }, + 'right' + ), + // 启用联合查询 + mapFormSchema( + { + label: '启用联合查询', + field: 'joinQuery', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onJoinQueryChange, + }, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'joinQuery', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + render: () => '', + }, + 'right' + ), + // 积木报表打印 + mapFormSchema( + { + label: '集成积木报表', + field: 'reportPrintShow', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onReportPrintShowChange, + }, + }, + 'left' + ), + mapFormSchema( + { + label: '报表地址', + field: 'reportPrintUrl', + component: 'Input', + ifShow: isNotOneToOneSub, + componentProps: { + style: 'width: 80%', + }, + dynamicDisabled: ({ model }) => !model.reportPrintShow, + dynamicRules: ({ model }) => { + return [ + { required: !!model.reportPrintShow, message: '请输入报表地址!' }, + { + validator({}, value) { + if (/\/jmreport\/view\/{积木报表ID}/.test(value)) { + return Promise.reject('请将{积木报表ID}替换为真实的积木报表ID!'); + } + return Promise.resolve(); + }, + }, + ]; + }, + }, + 'right' + ), + // update-begin--author:liaozhiyang---date:20231213---for:【QQYUN-7421】vue3先注释集成设计表单功能 + // mapFormSchema( + // { + // label: '集成设计表单', + // field: 'isDesForm', + // component: 'RadioButtonGroup', + // componentProps: { + // options: [ + // { label: '开启', value: 'Y' }, + // { label: '关闭', value: 'N' }, + // ], + // buttonStyle: 'solid', + // onChange: handlers.onIsDesformChange, + // }, + // }, + // 'left' + // ), + // mapFormSchema( + // { + // label: '表单编码', + // field: 'desFormCode', + // component: 'Input', + // componentProps: { + // style: 'width: 80%', + // }, + // dynamicDisabled: ({ model }) => model.isDesForm !== 'Y', + // dynamicRules: ({ model }) => { + // return [{ required: model.isDesForm === 'Y', message: '请输入表单编码!' }]; + // }, + // }, + // 'right' + // ), + // update-end--author:liaozhiyang---date:20231213---for:【QQYUN-7421】vue3先注释集成设计表单功能 + // 列表操作列 + mapFormSchema( + { + label: '固定操作列', + field: 'tableFixedAction', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + defaultValue: 1, + }, + 'left' + ), + mapFormSchema( + { + label: '固定方式', + field: 'tableFixedActionType', + component: 'Select', + componentProps: { + options: [ + { label: '固定到右侧', value: 'right' }, + { label: '固定到左侧', value: 'left' }, + ], + style: 'width: 80%', + }, + defaultValue: 'right', + dynamicDisabled: ({ model }) => !model.tableFixedAction, + dynamicRules: ({ model }) => { + return [{ required: !!model.tableFixedAction, message: '请选择固定方式!' }]; + }, + }, + 'right' + ), + // 列宽拖动调整 + mapFormSchema( + { + label: '列宽拖动', + field: 'canResizeColumn', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + defaultValue: 0, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'canResizeColumn', + component: 'InputNumber', + render: () => '', + }, + 'right' + ), + //--------------------------表单评论 end----------------------------------------- + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + mapFormSchema( + { + label: '表单Label长度', + field: 'formLabelLengthShow', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onFormLabelLengthShow, + }, + }, + 'left' + ), + mapFormSchema( + { + label: 'Label长度', + field: 'formLabelLength', + component: 'InputNumber', + componentProps: { + style: 'width: 80%', + placeholder: '自定义表单Label长度', + }, + dynamicDisabled: ({ model }) => !model.formLabelLengthShow, + dynamicRules: ({ model }) => { + return [{ required: !!model.formLabelLengthShow, message: '请填写表单label长度' }]; + }, + }, + 'right' + ), + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + mapFormSchema( + { + label: '启用外部链接', + field: 'enableExternalLink', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + {label: '开启', value: 1}, + {label: '关闭', value: 0}, + ], + buttonStyle: 'solid', + defaultValue: 0, + // onChange: handlers.onFormLabelLengthShow, + }, + }, + 'left' + ), + mapFormSchema( + { + label: '允许的操作', + field: 'externalLinkActions', + component: 'JCheckbox', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + {label: '新增', value: 'add'}, + {label: '编辑', value: 'edit'}, + {label: '详情', value: 'detail'}, + ], + }, + dynamicDisabled: ({model}) => !model.enableExternalLink, + }, + 'right' + ), + ]; + + return { formSchemas }; +} + +/** 获取 代码生成 FormSchemas */ +export function useCodeGeneratorFormSchemas(_, handlers, single: Ref) { + type SpanType = 'one' | 'tow' | 'towOne'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 5 }, + wrapperCol: { xs: 24, sm: 16 }, + }, + }, + // 两列中的一列 + towOne: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 16 }, + }, + }, + }, + 'one' + ); + const getColSize = computed(() => (single.value ? 'one' : 'tow')); + // 由于需要动态改变布局,所以使用 computed + // e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + const formSchemas = computed(() => [ + mapFormSchema( + { + label: '代码生成目录', + field: 'projectPath', + render: ({ model, field }) => + h( + Input.Search, + { + value: model[field], + onChange: (e) => { + model[field] = e.target.value; + handlers.onProjectPathChange(e); + }, + onSearch: handlers.onProjectPathSearch, + disabled: isDisabledAuth('online:codeGenerate:projectPath'), + }, + { + enterButton: () => + h( + Button, + { + preIcon: 'ant-design:folder-open', + disabled: isDisabledAuth('online:codeGenerate:projectPath'), + }, + { + default: () => '浏览', + icon: () => h(FolderOpenOutlined), + } + ), + } + ), + component: 'InputSearch', + required: true, + // 如果版本号为1 表示未曾修改 未曾同步 可以修改表名 + }, + single.value ? 'one' : 'towOne' + ), + mapFormSchema( + { + label: '页面风格', + field: 'jspMode', + component: 'Select', + componentProps: { + options: handlers.jspModeOptions.value, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-895】页面风格去掉x + allowClear: false, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-895】页面风格去掉x + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '功能说明', + field: 'ftlDescription', + component: 'Input', + }, + getColSize.value + ), + { label: '数据模型', field: 'jformType', component: 'Input', show: false }, + mapFormSchema( + { + label: '表名', + field: 'tableName_tmp', + required: true, + dynamicDisabled: true, + component: 'Input', + }, + getColSize.value + ), + mapFormSchema( + { + label: '实体类名', + field: 'entityName', + required: true, + component: 'Input', + componentProps: { + placeholder: '请输入实体类名(首字母大写)', + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '包名(小写)', + field: 'entityPackage', + component: 'Input', + rules: [{ required: true, pattern: /^[a-zA-Z0-9._]*$/, message: '包名必填,且只允许字母、数字、下划线、小数点组合' }], + }, + getColSize.value + ), + mapFormSchema( + { + label: '代码分层样式', + field: 'packageStyle', + component: 'Select', + componentProps: { + disabled: true, + options: [ + { label: '业务分层', value: 'service' }, + { label: '代码分层', value: 'project' }, + ], + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '页面代码', + field: 'vueStyle', + required: true, + component: 'Input', + defaultValue: 'vue3', + slot: 'pageCode', + // componentProps: { + // options: [ + // { label: 'Vue3(BasicForm)', value: 'vue3' }, + // { label: 'Vue3原生(a-form)', value: 'vue3Native' }, + // { label: 'Vue2', value: 'vue' }, + // ], + // }, + // update-begin--author:liaozhiyang---date:20240612---for:【TV360X-1057】代码生成页面代码鼠标移入给说明 + // dynamicPropskey: 'options', + // dynamicPropsVal: ({ model }) => { + // if (model.jspMode && (model.jspMode == 'innerTable' || model.jspMode == 'tab')) { + // return [ + // { value: 'vue3', label: '封装表单(BasicForm)' }, + // ]; + // } else { + // return [ + // { value: 'vue3', label: '封装表单(BasicForm)' }, + // { value: 'vue3Native', label: '原生表单(a-form)' }, + // ]; + // } + // }, + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1057】代码生成页面代码鼠标移入给说明 + }, + getColSize.value + ), + { label: '需要生成的代码', field: 'codeTypes', component: 'Input', show: false }, + ]); + + return { formSchemas }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/hooks/useTableSync.ts b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useTableSync.ts new file mode 100644 index 000000000..aee2f5af3 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/hooks/useTableSync.ts @@ -0,0 +1,167 @@ +import type { Ref, ComputedRef } from 'vue'; +import { ref, computed, nextTick, inject } from 'vue'; +import { CgformModal } from '../types'; +import { JVxeColumn, JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types'; +import { VALIDATE_FAILED } from '../cgform.data'; +import { pick } from 'lodash-es'; + +export function useTableSync(columns: Ref) { + const tables = inject('tables'); + const fullScreenRef = inject>('fullScreenRef'); + const vxetableHeight = inject>('vxetableHeight'); + const tableRef = ref(); + const loading = ref(false); + const dataSource = ref([]); + // 表格动态高度 + const tableHeight = computed(() => ({ + // 正常表格高度 + normal: fullScreenRef?.value ? vxetableHeight?.value : 260, + // 没有 toolbar 的表格高度 + noToolbar: fullScreenRef?.value ? vxetableHeight?.value : 320, + })); + + // 当前表的所有列字段key + const columnKeys = computed(() => ['id'].concat(columns.value.map((col) => col.key))); + + // 表格其他props + const tableProps = computed(() => { + return { + // 针对Online表单对虚拟滚动做出优化 + // 虚拟滚动配置,y轴(行数)大于xx条数据时启用虚拟滚动 + // update-begin--author:liaozhiyang---date:20231025---for:【QQYUN-6808】online编辑字段多了卡顿 + scrollY: { + enabled: true, + gt: 15, + }, + // 列数 + scrollX: { + enabled: true, + gt: 20, + }, + // update-begin--author:liaozhiyang---date:20231025---for:【QQYUN-6808】online编辑字段多了卡顿 + }; + }); + + // 校验并获取表格数据 + async function validateData(activeKey: string) { + let instance = tableRef.value!; + let errMap = await instance.fullValidateTable(); + if (errMap) { + throw { code: VALIDATE_FAILED, activeKey }; + } + // 过滤掉当前表中不存在的字段,以防止多个表冲突 + let tableData = instance.getTableData().map((data) => pick(data, columnKeys.value)); + // 获取被删除的ID + let deleteIds = instance.getDeleteData().map((d) => d.id); + return { tableData, deleteIds }; + } + + /** + * 设置数据源 + * @param data + * @param insert + */ + async function setDataSource(data, insert = false) { + if (insert) { + dataSource.value = []; + await nextTick(); + await tableRef.value!.addOrInsert(data, 0, null, { setActive: false }); + await nextTick(); + tableRef.value!.recalcDisableRows(); + } else { + dataSource.value = data; + // update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1762】解决编辑时id可删除 + await nextTick(); + tableRef.value!.recalcDisableRows(); + // update-end--author:liaozhiyang---date:20240705---for:【TV360X-1762】解决编辑时id可删除 + } + } + + /** + * 同步列表,可以同步新增、修改、删除 + * @param dbTable + */ + function syncTable(dbTable: Ref) { + let targetTable = tableRef.value!; + let sourceTable = dbTable.value!.tableRef!; + + let removeIds = dbTable.value!.getRemoveIds(); + let sourceData = sourceTable.getXTable().internalData.tableFullData; + let targetData = targetTable.getXTable().internalData.tableFullData; + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + // 用 Map 索引 targetData,将查找从 O(N) 降到 O(1) + const targetMap = new Map(); + targetData.forEach((targetValue) => { + if (targetValue.id) { + targetMap.set(targetValue.id, targetValue); + } + }); + // 用 Set 索引 removeIds,将查找从 O(K) 降到 O(1) + const removeIdSet = new Set(removeIds); + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // 先收集需要删除的ID,最后统一删除,避免在遍历中修改数组 + const toRemoveIds: string[] = []; + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // 批量收集需要修改的值,最后一次性调用 setValues + const batchSetValues: { rowKey: string; values: Recordable }[] = []; + // update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + // 提前缓存列默认值映射,避免新增每行时重复遍历 columns + const columnDefaults: { key: string; defaultValue: any }[] = []; + columns.value.forEach((column) => { + if (column.key !== 'dbFieldName' && column.key !== 'dbFieldTxt') { + columnDefaults.push({ key: column.key, defaultValue: column.defaultValue }); + } + }); + // update-end--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + sourceData.forEach((sourceValue) => { + const targetValue = targetMap.get(sourceValue.id); + if (targetValue) { + // 判断是否修改了值 + let dbFieldName = targetValue['dbFieldName']; + let dbFieldTxt = targetValue['dbFieldTxt']; + if (sourceValue.dbFieldName !== dbFieldName || sourceValue.dbFieldTxt !== dbFieldTxt) { + // 收集修改字段,稍后批量同步 + batchSetValues.push({ + rowKey: targetValue.id, + values: { + dbFieldName: sourceValue.dbFieldName, + dbFieldTxt: sourceValue.dbFieldTxt, + }, + }); + } + } else { + // target中不存在,说明是新增的 + let record = Object.assign({}, sourceValue); + // update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + for (const { key, defaultValue } of columnDefaults) { + if (record[key] == undefined) { + record[key] = defaultValue; + } + } + // update-end--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + targetTable.addRows(record); + } + }); + // 批量同步修改 + if (batchSetValues.length > 0) { + targetTable.setValues(batchSetValues); + } + // 处理删除:target中存在但已被删除的行 + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + targetData.forEach((targetValue) => { + if (targetValue.id && removeIdSet.has(targetValue.id)) { + toRemoveIds.push(targetValue.id); + } + }); + if (toRemoveIds.length > 0) { + setTimeout(() => { + toRemoveIds.forEach((id) => targetTable.removeRowsById(id)); + }, 0); + } + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + return nextTick(); + } + + return { tables, tableRef, loading, dataSource, columnKeys, tableHeight, tableProps, syncTable, validateData, setDataSource }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/index.vue new file mode 100644 index 000000000..bbbbd07ff --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/router/cgformRouter.ts b/jeecgboot-vue3/src/views/super/online/cgform/router/cgformRouter.ts new file mode 100644 index 000000000..a919a0c04 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/router/cgformRouter.ts @@ -0,0 +1,48 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerCgformRouter() { + router.addRoute({ + path: '/online-auto-cgform-router', + name: 'onl-auto-cgform-router', + component: LAYOUT, + redirect: '/online/cgform', + meta: { + title: 'OnlCgformAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/cgformList/:id', + name: 'OnlineAutoList', + component: () => import('../auto/default/OnlineAutoList.vue'), + meta: {title: 'AUTO在线表单'}, + }, + { + path: '/online/cgformTreeList/:id', + name: 'DefaultOnlineList', + component: () => import('../auto/tree/OnlineAutoTreeList.vue'), + meta: {title: 'AUTO在线树表单'}, + }, + { + path: '/online/cgformErpList/:id', + name: 'CgformErpList', + component: () => import('../auto/erp/OnlCgformErpList.vue'), + meta: {title: 'AUTO在线ERP表单'}, + }, + { + path: '/online/cgformInnerTableList/:id', + name: 'OnlCgformInnerTableList', + component: () => import('../auto/innerTable/OnlCgformInnerTableList.vue'), + meta: {title: 'AUTO在线一对多内嵌'}, + }, + { + path: '/online/cgformTabList/:id', + name: 'OnlCgformTabList', + component: () => import('../auto/tab/OnlCgformTabList.vue'), + meta: {title: 'AUTO在线Tab风格'}, + }, + ], + }) +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/README.md b/jeecgboot-vue3/src/views/super/online/cgform/share/README.md new file mode 100644 index 000000000..c8b3b41c4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/README.md @@ -0,0 +1,3 @@ +# 目录说明 + +本目录为 Online表单 的外部链接功能目录 diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/components/ShareView.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/components/ShareView.vue new file mode 100644 index 000000000..54a0486a6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/components/ShareView.vue @@ -0,0 +1,157 @@ + + + + + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/components/SingleView.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/components/SingleView.vue new file mode 100644 index 000000000..ea6ecb6af --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/components/SingleView.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/components/add/ShareAddView.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/components/add/ShareAddView.vue new file mode 100644 index 000000000..afa21ab60 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/components/add/ShareAddView.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/components/edit/ShareEditView.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/components/edit/ShareEditView.vue new file mode 100644 index 000000000..0b902058d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/components/edit/ShareEditView.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/hooks/useCgformShare.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/hooks/useCgformShare.ts new file mode 100644 index 000000000..a6d6c5de4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/hooks/useCgformShare.ts @@ -0,0 +1,120 @@ +import {ref} from 'vue'; +import {router} from "@/router"; + +import { + SHARE_ADD_ROUTER_NAME, + SHARE_DETAIL_ROUTER_NAME, + SHARE_LOGIN__ROUTER_NAME, + SHARE_UPDATE_ROUTER_NAME +} from "../route"; +import {useShareStore} from "../store/shareStore"; +import {useUserStore} from "@/store/modules/user"; +import {getCgformById, getCgformRecordById} from "../share.api"; +import {parseExtConfigJson} from "../../util/utils"; + +export function useCgformShare() { + const userStore = useUserStore() + const shareStore = useShareStore() + + const pageLoading = ref(true) + const pageErrorTip = ref('') + + async function initCgformShare() { + try { + // 检查url中的token + await shareStore.checkUrlToken() + const route = router.currentRoute.value + // 检查缓存中的token + if (!userStore.getToken) { + // 跳转到登录页 + router.push({ + name: SHARE_LOGIN__ROUTER_NAME, + query: { + redirect: encodeURIComponent(route.path), + } + }); + return + } + // 获取 Online表单 的信息 + const {id: formId} = route.params + if (!formId) { + pageErrorTip.value = '参数错误' + return + } + let res = await getCgformById(formId as string); + if (!res.success) { + pageErrorTip.value = res.message + return + } + const record = res.result + // 判断是否开启了外部链接 + const extJson = parseExtConfigJson(record); + if (!extJson?.enableExternalLink) { + pageErrorTip.value = '当前表单未开启外部链接' + return + } + // 判断是否支持当前操作 + let externalLinkActions = extJson.externalLinkActions.split(','); + if (route.name === SHARE_ADD_ROUTER_NAME) { + if (!externalLinkActions.includes('add')) { + pageErrorTip.value = '当前表单不支持外部新增' + return + } + } else if (route.name === SHARE_UPDATE_ROUTER_NAME) { + if (!externalLinkActions.includes('edit')) { + pageErrorTip.value = '当前表单不支持外部编辑' + return + } + } else if (route.name === SHARE_DETAIL_ROUTER_NAME) { + if (!externalLinkActions.includes('detail')) { + pageErrorTip.value = '当前表单不支持外部详情' + return + } + } else { + pageErrorTip.value = '未知的页面'; + return; + } + + // 判断表单类型 + if (record.tableType == 3) { + pageErrorTip.value = '不支持附表外部链接'; + return; + } + + shareStore.setCgformRecord(record); + + // 查询数据 + if (route.name === SHARE_UPDATE_ROUTER_NAME || route.name === SHARE_DETAIL_ROUTER_NAME) { + const {dataId} = route.params; + if (!dataId) { + pageErrorTip.value = '参数错误' + return + } + res = await getCgformRecordById(formId as string, dataId as string); + if (!res.success) { + pageErrorTip.value = res.message + return + } + const dataRecord = res.result + if (dataRecord?.id !== dataId) { + pageErrorTip.value = '数据不存在或已删除' + return + } + shareStore.setDataRecord(dataRecord); + } + + } catch (e: any) { + pageErrorTip.value = e?.message || e + console.error(e) + } finally { + pageLoading.value = false + } + } + + return { + pageLoading, + pageErrorTip, + + initCgformShare, + } +} \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/index.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/index.ts new file mode 100644 index 000000000..9c30024f0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/index.ts @@ -0,0 +1,10 @@ +import {router} from "@/router"; + +import {routerBeforeEach, SHARE_LOGIN_ROUTE, SHARE_ROUTE} from "./route"; + +export function register() { + router.addRoute(SHARE_LOGIN_ROUTE); + router.addRoute(SHARE_ROUTE); + + router.beforeEach(routerBeforeEach); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue new file mode 100644 index 000000000..24c1f9787 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue @@ -0,0 +1,44 @@ + + + + + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/index.vue new file mode 100644 index 000000000..a41171234 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts new file mode 100644 index 000000000..f12e77b79 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey, ComputedRef } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface ContentContextProps { + contentHeight: ComputedRef; + setPageHeight: (height: number) => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createContentContext(context: ContentContextProps) { + return createContext(context, key, { native: true }); +} + +export function useContentContext() { + return useContext(key); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts new file mode 100644 index 000000000..b55b7e8e9 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts @@ -0,0 +1,42 @@ +import { ref, computed, unref } from 'vue'; +import { createPageContext } from '/@/hooks/component/usePageContext'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; + +const headerHeightRef = ref(0); +const footerHeightRef = ref(0); + +export function useLayoutHeight() { + function setHeaderHeight(val) { + headerHeightRef.value = val; + } + function setFooterHeight(val) { + footerHeightRef.value = val; + } + return { headerHeightRef, footerHeightRef, setHeaderHeight, setFooterHeight }; +} + +export function useContentViewHeight() { + const contentHeight = ref(window.innerHeight); + const pageHeight = ref(window.innerHeight); + const getViewHeight = computed(() => { + return unref(contentHeight) - unref(headerHeightRef) - unref(footerHeightRef) || 0; + }); + + useWindowSizeFn( + () => { + contentHeight.value = window.innerHeight; + }, + 100, + { immediate: true } + ); + + async function setPageHeight(height: number) { + pageHeight.value = height; + } + + createPageContext({ + contentHeight: getViewHeight, + setPageHeight, + pageHeight, + }); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/index.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/index.ts new file mode 100644 index 000000000..4d3daefbe --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/index.ts @@ -0,0 +1,5 @@ +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; + +export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { + loading: true, +}); diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue new file mode 100644 index 000000000..7bc9d7604 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue @@ -0,0 +1,34 @@ + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue new file mode 100644 index 000000000..15ced9d8e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.less b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.less new file mode 100644 index 000000000..f3e15f97c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.less @@ -0,0 +1,200 @@ +@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; +@header-prefix-cls: ~'@{namespace}-online-share-layout-header'; +@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb'; +@logo-prefix-cls: ~'@{namespace}-app-logo'; + +.@{header-prefix-cls} { + display: flex; + height: @header-height; + padding: 0; + margin-left: -1px; + line-height: @header-height; + color: @white; + background-color: @white; + align-items: center; + justify-content: space-between; + + &--mobile { + .@{breadcrumb-prefix-cls}, + .error-action, + .notify-item, + .lock-item, + .fullscreen-item { + display: none; + } + + .@{logo-prefix-cls} { + min-width: unset; + padding-right: 0; + + &__title { + display: none; + } + } + + .@{header-trigger-prefix-cls} { + padding: 0 4px 0 8px !important; + } + + .@{header-prefix-cls}-action { + padding-right: 4px; + } + } + + &--fixed { + position: fixed; + top: 0; + left: 0; + z-index: @layout-header-fixed-z-index; + width: 100%; + } + + &-logo { + height: @header-height; + min-width: 192px; + padding: 0 10px; + font-size: 14px; + + img { + width: @logo-width; + height: @logo-width; + margin-right: 2px; + } + } + + &-left { + display: flex; + height: 100%; + align-items: center; + + .@{header-trigger-prefix-cls} { + display: flex; + height: 100%; + padding: 1px 10px 0 10px; + cursor: pointer; + align-items: center; + + .anticon { + font-size: 22px; + } + + &.light { + &:hover { + background-color: @header-light-bg-hover-color; + } + + svg { + fill: #000; + } + } + + &.dark { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } + + &-menu { + height: 100%; + min-width: 0; + flex: 1; + align-items: center; + } + + &-action { + display: flex; + min-width: 180px; + // padding-right: 12px; + align-items: center; + + &__item { + display: flex !important; + height: @header-height; + padding: 0 2px; + font-size: 1.2em; + cursor: pointer; + align-items: center; + + .ant-badge { + height: @header-height; + line-height: @header-height; + } + + .ant-badge-dot { + top: 10px; + right: 2px; + } + } + + span[role='img'] { + padding: 0 8px; + } + } + + &--light { + background-color: @white !important; + border-bottom: 1px solid @header-light-bottom-border-color; + border-left: 1px solid @header-light-bottom-border-color; + + .@{header-prefix-cls}-logo { + color: @text-color-base; + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + color: @text-color-base; + + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + &-icon, + span[role='img'] { + color: @text-color-base; + } + } + } + + &--dark { + background-color: @header-dark-bg-color !important; + // border-bottom: 1px solid @border-color-base; + border-left: 1px solid @border-color-base; + + .@{header-prefix-cls}-logo { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + .ant-badge { + span { + color: @white; + } + } + + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.vue new file mode 100644 index 000000000..f83a297d5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/header/index.vue @@ -0,0 +1,167 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/index.vue new file mode 100644 index 000000000..26b74cda5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/default/index.vue @@ -0,0 +1,105 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts new file mode 100644 index 000000000..ba602293e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts @@ -0,0 +1,22 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + saveTenantJoinUser = '/sys/tenant/saveTenantJoinUser', + joinTenantByHouseNumber = '/sys/tenant/joinTenantByHouseNumber', +} + +/** + * 保存租户 + * @param params + */ +export const saveTenantJoinUser = (params) => { + return defHttp.post({ url: Api.saveTenantJoinUser, params }, { isTransformResponse: false }); +}; + +/** + * 加入租户 + * @param params + */ +export const joinTenantByHouseNumber = (params) => { + return defHttp.post({ url: Api.joinTenantByHouseNumber, params }, { isTransformResponse: false }); +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.vue new file mode 100644 index 000000000..72f611ce0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppLogin.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts new file mode 100644 index 000000000..c8b164466 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts @@ -0,0 +1,196 @@ +import { ref, unref, defineEmits } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useUserStore } from '/@/store/modules/user'; +import { setThirdCaptcha, getCaptcha } from '/@/api/sys/user'; +import { useI18n } from '/@/hooks/web/useI18n'; + +export function useThirdLogin(emit) { + const { createMessage, notification } = useMessage(); + const { t } = useI18n(); + const glob = useGlobSetting(); + const userStore = useUserStore(); + //第三方类型 + const thirdType = ref(''); + //第三方登录相关信息 + const thirdLoginInfo = ref({}); + //状态 + const thirdLoginState = ref(false); + //注册或者绑定账户 + const bindingAccount = ref(false); + //第三方用户UUID + const thirdUserUuid = ref(''); + //提示窗 + const thirdConfirmShow = ref(false); + //绑定手机号 + const thirdPhone = ref(''); + //验证码 + const thirdCaptcha = ref(''); + //第三方登录 + function onThirdLogin(source) { + let url = `${glob.uploadUrl}/sys/thirdLogin/render/${source}`; + window.open( + url, + `login ${source}`, + 'height=500, width=500, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no' + ); + thirdType.value = source; + thirdLoginInfo.value = {}; + thirdLoginState.value = false; + let receiveMessage = function (event) { + let token = event.data; + if (typeof token === 'string') { + //如果是字符串类型 说明是token信息 + if (token === '登录失败') { + createMessage.warning(token); + } else if (token.includes('绑定手机号')) { + bindingAccount.value = true; + let strings = token.split(','); + thirdUserUuid.value = strings[1]; + emit('type', { loginType: 'thirdLogin' }); + } else { + doThirdLogin(token); + } + } else if (typeof token === 'object') { + //对象类型 说明需要提示是否绑定现有账号 + if (token['isObj'] === true) { + thirdConfirmShow.value = true; + thirdLoginInfo.value = { ...token }; + } + } else { + createMessage.warning('不识别的信息传递'); + } + //update-begin---author:wangshuai---date:2024-02-20---for:【QQYUN-8156】连续登录失败,导致失败提醒累加--- + window.removeEventListener('message', unref(receiveMessage),false); + //update-end---author:wangshuai---date:2024-02-20---for:【QQYUN-8156】连续登录失败,导致失败提醒累加--- + }; + window.addEventListener('message', receiveMessage, false); + } + // 根据token执行登录 + function doThirdLogin(token) { + if (unref(thirdLoginState) === false) { + thirdLoginState.value = true; + userStore.ThirdLogin({ token, thirdType: unref(thirdType) }).then((res) => { + console.log('res====>doThirdLogin', res); + if (res && res.userInfo) { + notification.success({ + message: t('sys.login.loginSuccessTitle'), + description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`, + duration: 3, + }); + } else { + requestFailed(res); + } + }); + } + } + + function requestFailed(err) { + notification.error({ + message: '登录失败', + description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试', + duration: 4, + }); + } + + //绑定手机号点击确定按钮 + function thirdHandleOk() { + if (!unref(thirdPhone)) { + cmsFailed('请输入手机号'); + } + if (!unref(thirdCaptcha)) { + cmsFailed('请输入验证码'); + } + let params = { + mobile: unref(thirdPhone), + captcha: unref(thirdCaptcha), + thirdUserUuid: unref(thirdUserUuid), + }; + defHttp.post({ url: '/sys/thirdLogin/bindingThirdPhone', params }, { isTransformResponse: false }).then((res) => { + if (res.success) { + bindingAccount.value = false; + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }).catch((e)=>{ + createMessage.warning(e.message); + }); + } + function cmsFailed(err) { + notification.error({ + message: '登录失败', + description: err, + duration: 4, + }); + return; + } + + /** + * 登录并绑定 + */ + function loginAccountClick() { + emit('type', { loginType: 'login' }); + } + + /** + * 创建新账号 + */ + function registerAccountClick() { + emit('type', { loginType: 'register' }); + } + + /** + * 隐藏绑定页面 + */ + function hideBindThirdAccount() { + bindingAccount.value = false; + } + + /** + * 注册用户并且绑定第三方 + */ + async function createAccountBindThird(values) { + let params = { ...values }; + params.thirdUserUuid = unref(thirdUserUuid); + await defHttp + .put({ url: '/sys/thirdLogin/registerBindThirdAccount', params }, { isTransformResponse: false }) + .then((res) => { + if (res.success) { + bindingAccount.value = false; + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }) + .catch((e) => { + createMessage.warning(e.message); + }) + } + + /** + * 绑定手机号 + * @param values + */ + function bindThirdAccount(values) { + thirdPhone.value = values.mobile; + thirdCaptcha.value = values.sms; + thirdHandleOk(); + } + + //返回数据和方法 + return { + thirdConfirmShow, + bindingAccount, + thirdHandleOk, + thirdPhone, + thirdCaptcha, + onThirdLogin, + loginAccountClick, + registerAccountClick, + hideBindThirdAccount, + bindThirdAccount, + createAccountBindThird, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue new file mode 100644 index 000000000..b459218a7 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue new file mode 100644 index 000000000..52ceef94e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue @@ -0,0 +1,477 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue new file mode 100644 index 000000000..f33f98337 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue new file mode 100644 index 000000000..113e8994d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue new file mode 100644 index 000000000..642a59228 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue @@ -0,0 +1,445 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue new file mode 100644 index 000000000..5f49c41d6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue @@ -0,0 +1,462 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue new file mode 100644 index 000000000..1ab047ea6 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue @@ -0,0 +1,75 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue new file mode 100644 index 000000000..1bf6c6a22 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/index.vue b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/index.vue new file mode 100644 index 000000000..2ad97bab1 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/layouts/login/index.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/route/index.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/route/index.ts new file mode 100644 index 000000000..705fcb68b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/route/index.ts @@ -0,0 +1,99 @@ +import type {RouteRecordRaw} from 'vue-router'; +import {PageEnum} from "@/enums/pageEnum"; + +// 外部链接路由名称(新增) +export const SHARE_ADD_ROUTER_NAME = 'online-cgform-@formId-share-add' + +// 外部链接路由名称(编辑) +export const SHARE_UPDATE_ROUTER_NAME = 'online-cgform-@formId-share-u-@dataId' + +// 外部链接路由名称(详情) +export const SHARE_DETAIL_ROUTER_NAME = 'online-cgform-@formId-share-d-@dataId' + +export const SHARE_ROUTE: RouteRecordRaw = { + path: "/online/cgform/share", + name: "online-cgform-share", + component: () => import("../layouts/default/index.vue"), + meta: { + title: 'Online表单外部页面', + ignoreAuth: true, + }, + children: [ + { + path: ':id/add', + name: SHARE_ADD_ROUTER_NAME, + component: () => import("../components/add/ShareAddView.vue"), + meta: { + title: 'Online表单外部新增页面', + }, + }, + { + path: ':id/u/:dataId', + name: SHARE_UPDATE_ROUTER_NAME, + component: () => import("../components/edit/ShareEditView.vue"), + meta: { + title: 'Online表单外部编辑页面', + }, + }, + { + path: ':id/d/:dataId', + name: SHARE_DETAIL_ROUTER_NAME, + component: () => import("../components/edit/ShareEditView.vue"), + meta: { + title: 'Online表单外部详情页面', + }, + }, + ] +} + + +// 外部链接路由名称(详情) +export const SHARE_LOGIN__ROUTER_NAME = 'online-cgform-share-login' + +export const SHARE_LOGIN_ROUTE: RouteRecordRaw = { + path: "/online/cgform/share/login", + name: SHARE_LOGIN__ROUTER_NAME, + // component: () => import("../layouts/login/index.vue"), + component: () => import("../layouts/login/AppLogin.vue"), + meta: { + title: '登录 · Online表单', + ignoreAuth: true, + }, +} + +// +// Online表单外部链接页面 +const ONLINE_CGFORM_SHARE = '/online/cgform/share'; + +export async function routerBeforeEach(to: any, _from: any, next: any) { + // 如果是登录路由 + if (to.path === PageEnum.BASE_LOGIN) { + // 获取 redirect + let redirect = ((redirect: string) => { + if (!redirect) { + return ''; + } + // 判断 redirect 是否是 online表单 外部页面 + if (redirect.startsWith(ONLINE_CGFORM_SHARE)) { + return redirect; + } + redirect = decodeURIComponent(redirect) + if (redirect.startsWith(ONLINE_CGFORM_SHARE)) { + return redirect; + } + return ''; + })(to.query.redirect as string) + // 如果是则跳转到 online表单 外部专属登录页面 + if (redirect) { + redirect = redirect.split('?')[0]; + next({ + name: SHARE_LOGIN__ROUTER_NAME, + query: { + redirect: encodeURIComponent(redirect), + } + }); + return; + } + } + next(); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/share.api.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/share.api.ts new file mode 100644 index 000000000..e8aee7a9c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/share.api.ts @@ -0,0 +1,16 @@ +import {defHttp} from "@/utils/http/axios"; + +const enum Api { + getCgformById = '/online/cgform/head/queryById', + getCgformRecordById = '/online/cgform/api/form/{formId}/{recordId}', +} + +export const getCgformById = (id: string) => defHttp.get({ + url: Api.getCgformById, + params: {id} +}, {isTransformResponse: false}); + +export const getCgformRecordById = (formId: string, dataId: string) => defHttp.get({ + url: Api.getCgformRecordById.replace('{formId}', formId).replace('{recordId}', dataId), + params: {} +}, {isTransformResponse: false}); diff --git a/jeecgboot-vue3/src/views/super/online/cgform/share/store/shareStore.ts b/jeecgboot-vue3/src/views/super/online/cgform/share/store/shareStore.ts new file mode 100644 index 000000000..f84a72f66 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/share/store/shareStore.ts @@ -0,0 +1,69 @@ +import {defineStore} from 'pinia'; +import {useUserStoreWithOut} from "@/store/modules/user"; +import {removeCacheByDynKey} from "@/utils/auth"; +import {TOKEN_KEY} from "@/enums/cacheEnum"; +import {getUserInfo} from "@/api/sys/user"; + +interface StateType { + cgformRecord: Nullable, + dataRecord: Nullable, +} + +const userStore = useUserStoreWithOut() + +export const useShareStore = defineStore({ + id: 'online-cgform-share', + state: (): StateType => ({ + cgformRecord: null, + dataRecord: null, + }), + getters: { + + getCgformRecord(): Nullable { + return this.cgformRecord; + }, + getDataRecord(): Nullable { + return this.dataRecord; + }, + + }, + actions: { + + // 检查 url 参数是否携带token + async checkUrlToken(): Promise { + const token = new URLSearchParams(window.location.search).get('token'); + const flag = token != null && token.length > 0; + if (flag) { + userStore.setToken(token); + // 检查 token 是否有效 + try { + const res = await getUserInfo(); + if (!res?.userInfo) { + throw new Error('token无效'); + } else { + userStore.setUserInfo(res.userInfo) + if (res.sysAllDictItems) { + userStore.setAllDictItems(res.sysAllDictItems); + } + } + return true; + } catch (e) { + userStore.setToken(''); + removeCacheByDynKey(TOKEN_KEY) + return false; + } + } + return flag; + }, + + setCgformRecord(value: Nullable) { + this.cgformRecord = value; + }, + + setDataRecord(value: Nullable) { + this.dataRecord = value; + }, + + } +}); + diff --git a/jeecgboot-vue3/src/views/super/online/cgform/store/cgformState.ts b/jeecgboot-vue3/src/views/super/online/cgform/store/cgformState.ts new file mode 100644 index 000000000..0d36e2eb7 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/store/cgformState.ts @@ -0,0 +1,38 @@ +import {defineStore} from 'pinia'; +import {store} from '/@/store'; + +interface CgformState { + // 近期被修改过的表,存储表id + changedTables: string[]; +} + +export const useCgformStore = defineStore({ + id: 'cgform-state', + state: (): CgformState => ({ + changedTables: [], + }), + getters: {}, + actions: { + /** + * 检查是否同步过数据库 + * @param tableId 表id + */ + checkIsChanged(tableId: string) { + return this.changedTables.includes(tableId); + }, + addChangedTable(tableId: string) { + this.changedTables.push(tableId); + }, + removeChangedTable(tableId: string) { + const index = this.changedTables.findIndex((item) => item === tableId); + if (index !== -1) { + this.changedTables.splice(index, 1); + } + }, + }, +}); + +// 在 setup 之外使用 +export function useCgformStoreWithOut() { + return useCgformStore(store); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/store/enhance.ts b/jeecgboot-vue3/src/views/super/online/cgform/store/enhance.ts new file mode 100644 index 000000000..aecfe74ce --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/store/enhance.ts @@ -0,0 +1,41 @@ +import { store } from '/@/store'; +import { defineStore } from 'pinia'; +import { createLocalStorage } from '/@/utils/cache'; + +const ls = createLocalStorage(); +const ENHANCE_PRE = 'enhance_'; + +interface EnhanceStore { + enhanceJs: Recordable; +} + +export const useEnhanceStore = defineStore({ + id: 'online-cgform-enhance', + state: (): EnhanceStore => ({ + enhanceJs: {}, + }), + getters: {}, + actions: { + getEnhanceJs(code: string): Recordable[] { + this.enhanceJs[code] = ls.get(ENHANCE_PRE + code); + return this.enhanceJs[code]; + }, + addEnhanceJs(record: Recordable) { + if (!this.enhanceJs[record.code]) { + this.enhanceJs[record.code] = [{ ...record }]; + } else { + this.enhanceJs[record.code].push({ ...record }); + } + let enhanceJsArray = this.enhanceJs[record.code]; + while (enhanceJsArray.length > 16) { + enhanceJsArray.shift(); + } + ls.set(ENHANCE_PRE + record.code, enhanceJsArray); + }, + }, +}); + +// 在setup函数之外使用 +export function useEnhanceStoreWithOut() { + return useEnhanceStore(store); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/types/index.ts b/jeecgboot-vue3/src/views/super/online/cgform/types/index.ts new file mode 100644 index 000000000..5e7022420 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/types/index.ts @@ -0,0 +1,54 @@ +import { Ref } from 'vue'; +import DBAttributeTable from '../components/tables/DBAttributeTable.vue'; +import PageAttributeTable from '../components/tables/PageAttributeTable.vue'; +import CheckDictTable from '../components/tables/CheckDictTable.vue'; +import ForeignKeyTable from '../components/tables/ForeignKeyTable.vue'; +import IndexTable from '../components/tables/IndexTable.vue'; +import QueryTable from '../components/tables/QueryTable.vue'; + +// 定义弹窗form的类型 +export namespace CgformModal { + export type DBAttributeTableType = InstanceType; + export type PageAttributeTableType = InstanceType; + export type CheckDictTableType = InstanceType; + export type ForeignKeyTableType = InstanceType; + export type IndexTableType = InstanceType; + export type QueryTableType = InstanceType; + + export type TablesRef = { + dbTable: Ref; + pageTable: Ref; + checkTable: Ref; + fkTable: Ref; + idxTable: Ref; + queryTable: Ref; + }; +} + +// 定义页面类型,normal = 普通页面,copy=视图页面 +export enum CgformPageType { + normal, + copy, +} + +/** + * Online扩展配置类型 + */ +export type ExtConfigType = Partial<{ + // 是否启用积木报表打印(0否,1是) + reportPrintShow: number, + // 积木报表地址 + reportPrintUrl: string, + // 是否启用联合查询(0否,1是) + joinQuery: number, + // 弹窗是否默认全屏(0否,1是) + modelFullscreen: number, + // 弹窗的最小宽度(px) + modalMinWidth: string, + // 是否固定操作列(0否,1是) + tableFixedAction: number, + // 操作列固定方式 + tableFixedActionType: 'left' | 'right', + // 是否允许调整列表列宽 + canResizeColumn?: number, +}> diff --git a/jeecgboot-vue3/src/views/super/online/cgform/types/onlineRender.ts b/jeecgboot-vue3/src/views/super/online/cgform/types/onlineRender.ts new file mode 100644 index 000000000..f2f6ae3d8 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/types/onlineRender.ts @@ -0,0 +1,157 @@ +/** + * 因为online列表渲染 公用一个list 所以一些独有的属性配置 需要单独放到map中存放 避免切换路由冲突 + */ +interface SpecialConfig { + // 排序字段 + sortField: string; + // 排序方式 + sortType: 'asc' | 'desc'; + // 当前页数 + currentPage: number; + // 当前每页数目 + pageSize: number; + // 总页数 + total: number; + // 选中的行key + selectedRowKeys: string[]; + // 查询条件 + queryParam: object; + // href跳转至online列表页 会携带参数 + acceptHrefParams: object; + // 路由是否被缓存 + cache: boolean; + // 表描述 + description: string; + // 表名 + currentTableName: string; + // 是否启用表单设计器表单 + isDesForm: boolean; + // 表单设计器表单编码 + desFormCode: string; + isTree: boolean; + hasChildrenField?: string; +} + +interface Page { + current?: number; + pageSize?: number; + pageSizeOptions?: string[]; + showTotal?: Function; + showQuickJumper?: boolean; + showSizeChanger?: boolean; + total?: number; +} + +interface CgFormButton { + buttonCode?: string; + buttonName?: string; + buttonStyle?: string; + optType?: 'js' | 'bus'; + exp?: string; + buttonIcon?: string; +} + +/*** + * 表单页面的扩展配置 + */ +interface ExtendConfig { + modalMinWidth: number; +} + +/*** + * 表单字段的扩展配置解析结果 + */ +interface FieldExtends { + //上传数量 + uploadnum?: number | string; + + //限制大文本在列表页面的展示长度 + showLength?: number | string; + + //popup是否支持多选 + popupMulti?: boolean; + + //部门、用户组件 用于存储的字段名 + store?: string; + + //部门、用户组件 用于展示的字段名 + text?: string; + + //部门、用户组件 是否多选 + multiSelect?: boolean; + + //查询排序规则 + orderRule?: 'asc' | 'desc'; + + // 关联记录展示风格 card/select + showType?: string; + // 关联记录封面图 + imageField?: string; + // label长度 + labelLength?: number; +} + +// online提交表单和流程标识 默认只提交表单 +const SUBMIT_FLOW_KEY = 'jeecg_submit_form_and_flow'; +// 表单提交成功后回传表单数据的id key用于提交流程取id +const SUBMIT_FLOW_ID = 'flow_submit_id'; +// 表单提交成功后 在formData中添加设置表名的属性 +const ONL_FORM_TABLE_NAME = 'online_form_table_name'; +// 校验失败 +const VALIDATE_FAILED = 'validate-failed'; + +/* + * 查询表单的样式-label + * */ +const ONL_QUERY_LABEL_COL = { xs: { span: 24 }, sm: { span: 6 } }; + +/* + * 查询表单的样式-wrapper + * */ +const ONL_QUERY_WRAPPER_COL = { xs: { span: 24 }, sm: { span: 18 } }; + +/**setup*/ +const SETUP = 'setup'; + +/**EnhanceJS*/ +const ENHANCEJS = 'EnhanceJS'; + +/** + * 表单类型转换成查询类型 + * 普通查询和高级查询组件区别 :高级查询不支持联动组件 + */ +const FORM_VIEW_TO_QUERY_VIEW = { + password: 'text', + file: 'text', + image: 'text', + textarea: 'text', + umeditor: 'text', + markdown: 'text', + checkbox: 'list_multi', + radio: 'list', +}; + +/**下拉组件PopupContainer类选择器*/ +const POP_CONTAINER = '.jeecg-online-modal .ant-modal-content'; + +/**online权限前缀-现主要用于子表button*/ +const ONL_AUTH_PRE = 'online_'; + +export { + SpecialConfig, + Page, + CgFormButton, + ExtendConfig, + FieldExtends, + SUBMIT_FLOW_KEY, + SUBMIT_FLOW_ID, + VALIDATE_FAILED, + ONL_QUERY_LABEL_COL, + ONL_QUERY_WRAPPER_COL, + SETUP, + ENHANCEJS, + FORM_VIEW_TO_QUERY_VIEW, + POP_CONTAINER, + ONL_AUTH_PRE, + ONL_FORM_TABLE_NAME +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/util/FieldDefVal.ts b/jeecgboot-vue3/src/views/super/online/cgform/util/FieldDefVal.ts new file mode 100644 index 000000000..ae18fd03f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/util/FieldDefVal.ts @@ -0,0 +1,452 @@ +import dayjs from 'dayjs'; +import { useUserStore } from '/@/store/modules/user'; +import { defHttp } from '/@/utils/http/axios'; +import { replaceAll, _eval } from '/@/utils'; +import * as CustomExpression from '/@/utils/desform/customExpression'; +import weekOfYear from 'dayjs/plugin/weekOfYear'; +import quarterOfYear from 'dayjs/plugin/quarterOfYear'; +dayjs.extend(weekOfYear); +dayjs.extend(quarterOfYear); +// 获取所有用户自定义表达式的Key +const ceKeys = Object.keys(CustomExpression); +// 将key用逗号拼接,可以拼接成方法参数,例:a,b,c --> function(a,b,c){} +const ceJoin = ceKeys.join(','); +// 将用户自定义的表达式按key的顺序放到数组中,可以使用 apply 传递给方法直接调用 +const $CE$ = ceKeys.map((key) => CustomExpression[key]); + +/** 普通规则表达式 #{...} */ +const normalRegExp = /#{([^}]+)?}/g; +/** 用户自定义规则表达式 {{...}} */ +const customRegExp = /{{([^}]+)?}}/g; +/** 填值规则表达式 ${...} */ +const fillRuleRegExp = /\${([^}]+)?}/g; + +/** action 类型 */ +export const ACTION_TYPES = { ADD: 'add', EDIT: 'edit', DETAIL: 'detail', RELOAD: 'reload' }; + +/** + * 将主表/一对一子表 默认值配置信息暂存 + * @param field + * @param item + * @param config + */ +export function initDefValueConfig(field, item, config) { + if (hasEffectiveValue(item.defVal)) { + const obj = { field: field, type: item.type, value: item.defVal, view: item.view, fieldExtendJson: item.fieldExtendJson } + // 避免重复添加 + const index = config.findIndex((c) => c.field === field); + if (index === -1) { + config.push(obj); + } else { + config[index] = obj; + } + } +} + +/** + * 将一对多子表 默认值配置信息暂存 + * @param item + * @param config + */ +export function initSubTableDefValueConfig(item, config) { + if (hasEffectiveValue(item.fieldDefaultValue)) { + config.push({ field: item.key, type: item.type, value: item.fieldDefaultValue }); + } +} + +/** + * 加载form组件默认值-仅用于新增页面 + * @param properties 字段配置 + * @param callback 回调传值 + * @param formData 表单值 + */ +export async function loadFormFieldsDefVal(properties, callback, formData?) { + if (Array.isArray(properties) && properties.length > 0) { + let formValues = {}; + for (let prop of properties) { + let { value, type, field } = prop; + value = await handleDefaultValue(value, ACTION_TYPES.ADD, formData||{}); + // 处理数字类型,如果type=number并且value有值 + if ('number' === type && value) { + // parseFloat() 可以直接处理字符串、整数、小数、null和undefined, + // 非数字类型直接返回NaN,不必担心报错 + value = Number.parseFloat(value); + } + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-321】日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + value = transformDefValDate(prop, value); + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-321】日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + formValues[field] = value; + } + callback(formValues); + } +} + +/** + * 2024-05-22 + * liaozhiyang + * 日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + */ +function transformDefValDate(prop, value) { + const { type, field, view, fieldExtendJson } = prop; + if (view == 'date' && fieldExtendJson) { + const extendJson = JSON.parse(fieldExtendJson); + const { picker } = extendJson; + if (picker && picker != 'default' && value) { + let result; + try { + // 年 (2020) + if (picker === 'year') { + // update-begin--author:liaozhiyang---date:20240717---for:【TV360X-1790】年默认值设置YYYY-MM-DD格式,出现invaild Date + const data = value.split('-'); + const y = data[0]; + // update-end--author:liaozhiyang---date:20240717---for:【TV360X-1790】年默认值设置YYYY-MM-DD格式,出现invaild Date + result = dayjs().year(y).format('YYYY-MM-DD'); + } + // 年 - 月 (2024-02) + if (picker === 'month') { + const data = value.split('-'); + const y = data[0]; + const m = +data[1] + 1; + result = dayjs().year(y).month(m).format('YYYY-MM-DD'); + } + // 年 - 周 (2024-14周) + if (picker === 'week') { + const data = value.split('-'); + const y = data[0]; + const w = data[1].match(/^(\d+)周$/)[1]; + result = dayjs().year(y).week(w).format('YYYY-MM-DD'); + } + // 年 - 季度 (2024-Q4) + if (picker === 'quarter') { + const data = value.split('-'); + const y = data[0]; + const q = data[1].match(/^[Qq](\d)$/)[1]; + result = dayjs().year(y).quarter(q).format('YYYY-MM-DD'); + } + } catch (error) { + result = value; + } + return result; + } + return value; + } + return value; +} + +export async function loadOneFieldDefVal(field, item, formValues) { + let { defVal, type } = item; + if (hasEffectiveValue(defVal)) { + let value = await handleDefaultValue(defVal, ACTION_TYPES.ADD, {}); + if ('number' === type && value) { + // update-begin--author:liaozhiyang---date:20240618---for:online普通查询默认值范围查询不好使 + if (item.mode == 'group' && typeof value === 'string' && value.indexOf(',') != -1) { + const arr = value.split(','); + value = []; + if (arr[0]) { + value.push(Number.parseFloat(arr[0])); + } + if (arr[1]) { + value.push(Number.parseFloat(arr[1])); + } + } else { + value = Number.parseFloat(value); + } + // update-end--author:liaozhiyang---date:20240618---for:online普通查询默认值范围查询不好使 + } + formValues[field] = value; + } +} + +/** + * 判断给定的值是不是有效的 + */ +function hasEffectiveValue(val) { + if (val || val === 0) { + return true; + } + return false; +} + +/** 加载JEditableTable组件默认值 */ +export function loadFieldDefValForSubTable({ subForms, subTable, row, action, getFormData }) { + if (subTable && Array.isArray(subTable.columns) && subTable.columns.length > 0) { + subTable.columns.forEach(async (column) => { + let { key, fieldDefaultValue: defVal } = column; + eachHandler( + defVal, + action, + (value) => { + if (subForms.form) { + subForms.form.setFieldsValue({ [key]: value }); + } else { + // update-begin---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑----------- + let v = [{ rowKey: row.id, values: { [key]: value } }]; + (subForms.jvt || subForms.jet).setValues(v); + // update-end---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑------------ + } + }, + getFormData + ); + }); + } +} + +async function eachHandler(defVal, action, callback, getFormData) { + if (defVal != null) { + // 检查类型,如果类型错误则不继续运行 + if (checkExpressionType(defVal)) { + let value = await getDefaultValue(defVal, action, getFormData); + if (value != null) { + callback(value); + return value; + } + } else { + // 不合法的表达式直接返回不解析 + callback(defVal); + } + } +} + +/** + * 处理默认值 + * @param defVal + * @param action + * @param getFormData + */ +async function handleDefaultValue(defVal, action, getFormData) { + if (defVal != null) { + // 检查类型,如果类型错误则不继续运行 + if (checkExpressionType(defVal)) { + let value = await getDefaultValue(defVal, action, getFormData); + if (value != null) { + return value; + } + } + } + return defVal; +} + +/** + * 检查表达式类型是否合法,规则: + * 1、填值规则表达式不能和其他表达式混用 + * 2、每次只能填写一个填值规则表达式 + * 3、普通表达式和用户自定义表达式可以混用 + */ +export function checkExpressionType(defVal) { + // 获取各个表达式的数量 + let normalCount = 0, + customCount = 0, + fillRuleCount = 0; + defVal.replace(fillRuleRegExp, () => fillRuleCount++); + if (fillRuleCount > 1) { + logWarn(`表达式[${defVal}]不合法:只能同时填写一个填值规则表达式!`); + return false; + } + defVal.replace(normalRegExp, () => normalCount++); + defVal.replace(customRegExp, () => customCount++); + // 除填值规则外其他规则的数量 + let fillRuleOtherCount = normalCount + customCount; + if (fillRuleCount > 0 && fillRuleOtherCount > 0) { + logWarn(`表达式[${defVal}]不合法:填值规则表达式不能和其他表达式混用!`); + return false; + } + return true; +} + +/** 获取所有匹配的表达式 */ +function getRegExpMap(text, exp) { + let map = new Map(); + text.replace(exp, function (match, param) { + map.set(match, param.trim()); + return match; + }); + return map; +} + +/** 获取默认值,可以执行表达式,可以执行用户自定义方法,可以异步获取用户信息等 */ +async function getDefaultValue(defVal, action, getFormData) { + // 只有在 add 和 reload 模式下才执行填值规则 + if (action === ACTION_TYPES.ADD || action === ACTION_TYPES.RELOAD) { + // 判断是否是填值规则表达式,如果是就执行填值规则 + if (fillRuleRegExp.test(defVal)) { + let arr: any[] = [getFormData]; + return await executeRegExp(defVal, fillRuleRegExp, executeFillRuleExpression, arr); + } + } + // 只有在 add 模式下才执行其他表达式 + if (action === ACTION_TYPES.ADD) { + // 获取并替换所有常规表达式 + defVal = await executeRegExp(defVal, normalRegExp, executeNormalExpression); + // 获取并替换所有用户自定义表达式 + defVal = await executeRegExp(defVal, customRegExp, executeCustomExpression); + return defVal; + } + return null; +} + +async function executeRegExp(defVal, regExp, execFun, otherParams: any[] = []) { + let map = getRegExpMap(defVal, regExp); + for (let origin of map.keys()) { + let exp = map.get(origin); + let result = await execFun.apply(null, [exp, origin, ...otherParams]); + // 如果只有一个表达式,那么就不替换(因为一旦替换,类型就会被转成String),直接返回执行结果,保证返回的类型不变 + if (origin === defVal) { + return result; + } + defVal = replaceAll(defVal, origin, result); + } + return defVal; +} + +/** 执行【普通表达式】#{xxx} */ +async function executeNormalExpression(expression, origin) { + switch (expression) { + case 'date': + return dayjs().format('YYYY-MM-DD'); + case 'time': + return dayjs().format('HH:mm:ss'); + case 'datetime': + return dayjs().format('YYYY-MM-DD HH:mm:ss'); + default: + // 获取当前登录用户的信息 + let result = getUserInfoByExpression(expression); + if (result != null) { + return result; + } + // 没有符合条件的表达式,返回原始值 + return origin; + } +} + +/** 根据表达式获取相应的用户信息 */ +function getUserInfoByExpression(expression) { + const userStore = useUserStore(); + let userInfo = userStore.getUserInfo; + if (userInfo) { + switch (expression) { + case 'sysUserId': + return userInfo.id; + // 当前登录用户登录账号 + case 'sysUserCode': + case 'sys_user_code': + return userInfo.username; + // 当前登录用户真实名称 + case 'sysUserName': + return userInfo.realname; + // 当前登录用户部门编号 + case 'sysOrgCode': + case 'sys_org_code': + return userInfo.orgCode; + } + } + return null; +} + +/** 执行【用户自定义表达式】 {{xxx}} */ +async function executeCustomExpression(expression, origin) { + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + // 利用 eval 生成一个方法,这个方法的参数就是用户自定义的所有的表达式 + let fn = _eval(`(function (${ceJoin}){ return ${expression} })`); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + try { + // 然后调用这个方法,并把表达式传递进去,从而完成表达式的执行 + return fn.apply(null, $CE$); + } catch (e) { + // 执行失败,输出错误并返回原始值 + logError(e); + return origin; + } +} + +/** 执行【填值规则表达式】 ${xxx} */ +async function executeFillRuleExpression(expression, origin, getFormData) { + let formData = {}; + if (typeof getFormData === 'function') { + formData = getFormData(); + }else if(getFormData){ + formData = {...getFormData} + } + // 解析 url 参数 + expression = handleFillRuleQueryString(expression).exp; + let url = `/sys/fillRule/executeRuleByCode/${expression}`; + let { success, message, result } = await defHttp.put({ url, params: formData }, { isTransformResponse: false }); + if (success) { + return result; + } else { + logError(`填值规则(${expression})执行失败:${message}`); + return origin; + } +} + +// 处理填值规则 queryString 参数 +export function handleFillRuleQueryString(expression: string) { + let arr = expression.split('?'); + if (arr.length > 1) { + let queryString = ''; + let watchFields: string[] = []; + let str = arr[1]; + let pairs = str.split('&'); + pairs.forEach((pair, idx) => { + let [key, value] = pair.split('='); + value = value.trim(); + // 取出监听的字段,多个用逗号分隔 + if (key === 'onl_watch') { + watchFields = value.split(','); + } else { + queryString += `${key}=${value}`; + if (idx < pairs.length - 1) { + queryString += '&'; + } + } + }); + return { + exp: arr[0] + (queryString === '' ? '' : ('?' + queryString)), + watchFields: watchFields, + }; + } + return {exp: expression, watchFields: []}; +} + +export function handleFillRuleWatchKeysMap(properties: Recordable[]) { + const watchKeyMap = new Map(); + if (Array.isArray(properties) && properties.length > 0) { + for (let prop of properties) { + let {value: defVal, field} = prop; + if (defVal == null || defVal == '') { + continue; + } + // 检查类型,如果类型错误则不继续运行 + if (!checkExpressionType(defVal)) { + continue; + } + // 判断是否是填值规则,如果是就解析填值规则 + if (fillRuleRegExp.test(defVal)) { + let map = getRegExpMap(defVal, fillRuleRegExp); + for (let origin of map.keys()) { + let exp = map.get(origin); + const {watchFields} = handleFillRuleQueryString(exp); + for (const watchField of watchFields) { + let arr = watchKeyMap.get(watchField) + if (!Array.isArray(arr)) { + arr = [] + watchKeyMap.set(watchField, arr) + } + if (arr.includes(field)) { + continue; + } + arr.push(field); + } + } + } + } + } + return watchKeyMap; +} + +function logWarn(message) { + console.warn('[loadFieldDefVal]:', message); +} + +function logError(message) { + console.error('[loadFieldDefVal]:', message); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgform/util/constant.ts b/jeecgboot-vue3/src/views/super/online/cgform/util/constant.ts new file mode 100644 index 000000000..d4079a671 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/util/constant.ts @@ -0,0 +1,9 @@ + + +export const ERP = 'erp'; +export const Tree = 'tree'; +export const NORMAL = 'normal'; +export const INNER_TABLE = 'innerTable'; +export const TAB = 'tab'; +export const LABELLENGTH = 6; +export const ERPSUBTABLE = 'erpSubTable'; diff --git a/jeecgboot-vue3/src/views/super/online/cgform/util/utils.ts b/jeecgboot-vue3/src/views/super/online/cgform/util/utils.ts new file mode 100644 index 000000000..b111c5d14 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgform/util/utils.ts @@ -0,0 +1,19 @@ +import {ExtConfigDefaultJson} from "../cgform.data"; + +// 初始化扩展JSON +export function parseExtConfigJson(record: Recordable) { + // 解析扩展JSON + let parseJSON = {}; + if (record.extConfigJson) { + try { + parseJSON = JSON.parse(record.extConfigJson); + } catch (e) { + console.error('online扩展JSON转换失败:', e); + } + } + // 从数据库中取值,并合并 + return Object.assign({}, ExtConfigDefaultJson, parseJSON, { + isDesForm: record.isDesForm || 'N', + desFormCode: record.desFormCode || '', + }); +} diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/auto/OnlCgReportList.vue b/jeecgboot-vue3/src/views/super/online/cgreport/auto/OnlCgReportList.vue new file mode 100644 index 000000000..0adad113c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/auto/OnlCgReportList.vue @@ -0,0 +1,28 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.api.ts b/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.api.ts new file mode 100644 index 000000000..8345bc81b --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.api.ts @@ -0,0 +1,96 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +const { createConfirm } = useMessage(); + +enum Api { + list = '/online/cgreport/head/list', + save = '/online/cgreport/head/add', + edit = '/online/cgreport/head/editAll', + deleteOne = '/online/cgreport/head/delete', + deleteBatch = '/online/cgreport/head/deleteBatch', + onlCgreportParamList = '/online/cgreport/param/listByHeadId', + onlCgreportItemList = '/online/cgreport/item/listByHeadId', + getDataSourceList = '/sys/dataSource/options', + getParamsInfo = '/online/cgreport/api/getParamsInfo/', + analyzeSql = '/online/cgreport/head/parseSql', +} + +/** + * 查询子表数据 + * @param params + */ +export const onlCgreportParamList = Api.onlCgreportParamList; +/** + * 查询子表数据 + * @param params + */ +export const onlCgreportItemList = Api.onlCgreportItemList; +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除单个 + */ +export const deleteOne = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除 + * e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + iconType: 'warning', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 获取参数地址 + * @param params + */ +export const getReportParam = (id) => { + return defHttp.get({ url: Api.getParamsInfo + id }); +}; + +/** + * 获取数据源列表 + */ +export const getDataSourceList = () => { + return defHttp.get({ url: Api.getDataSourceList }); +}; + +/** + * 解析sql + * @param params + */ +export const analyzeSql = (params) => { + return defHttp.get({ + url: Api.analyzeSql + '?' + params, + }); +}; diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.data.ts b/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.data.ts new file mode 100644 index 000000000..e73af55ca --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/cgreport.data.ts @@ -0,0 +1,345 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { JVxeTypes, JVxeColumn } from '/@/components/jeecg/JVxeTable/types'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { getDataSourceList } from './cgreport.api'; +import {usePermissionStore} from "/@/store/modules/permission"; +const permissionStore = usePermissionStore(); +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '报表名字', + align: 'center', + dataIndex: 'name', + width: 120, + }, + { + title: '报表编码', + align: 'center', + dataIndex: 'code', + width: 120, + }, + { + title: '报表SQL', + align: 'center', + dataIndex: 'cgrSql', + width: 360, + }, + { + title: '数据源', + align: 'center', + dataIndex: 'dbSource', + customRender: ({ text, record }) => { + return record["dbSource_dictText"] ? record["dbSource_dictText"] : text + }, + width: 120, + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + width: 120, + }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '报表名称', + field: 'name', + component: 'JInput', + }, + { + label: '报表编码', + field: 'code', + component: 'JInput', + }, +]; + +// 编码校验 仅online报表用 +const codePattern = /^[a-z|A-Z][a-z|A-Z|\d|_|-]{0,}$/; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '报表编码', + field: 'code', + component: 'Input', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + dynamicRules: ({ values, model }) => { + console.log('values:', values); + return [ + { + required: true, + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (!value) { + return reject('请输入报表编码!'); + } + if (!codePattern.test(value)) { + return reject('编码必须以字母开头,可包含数字、下划线、横杠!'); + } + let params = { + tableName: 'onl_cgreport_head', + fieldName: 'code', + fieldVal: value, + dataId: model.id, + }; + duplicateCheckDelay(params) + .then((res) => { + res.success ? resolve() : reject('报表编码已存在!'); + }) + .catch((err) => { + reject(err.message || '校验失败'); + }); + }); + }, + }, + ]; + }, + }, + { + label: '报表名字', + field: 'name', + component: 'Input', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + dynamicRules: () => { + return [{ required: true, message: '请输入报表名字!' }]; + }, + }, + { + label: '动态数据源', + field: 'dbSource', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + component: 'ApiSelect', + rules: [{ required: permissionStore.sysSafeMode, message: '请选择数据源!' }], + componentProps: { + api: getDataSourceList, + }, + }, + /* { + label: ' ', + field: 'line1', + component: 'Input', + slot: 'line1', + colProps: { + span: 24 + }, + itemProps:{ + labelCol: { xs: 1, sm: 1 }, + wrapperCol: { xs: 23, sm: 23 }, + colon: false + }, + },*/ + { + label: '报表SQL', + field: 'cgrSql', + component: 'JCodeEditor', + rules: [{ required: true, message: '请填写报表SQL' }], + // update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9230】报表图表弹窗样式调整 + // itemProps: { + // labelCol: { xs: 24, sm: 4, md: 2, lg: 2, xl: 3, xxl: 2 }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 18 }, md: { span: 24 } }, + // }, + // update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9230】报表图表弹窗样式调整 + componentProps: { + height: '200px', + fullScreen: true, + }, + colProps: { + sm: 24, + xs: 24, + md: 18, + lg: 16, + xl: 16, + xxl: 16, + }, + }, + { + label: ' ', + field: 'analyseButton', + component: 'Input', + slot: 'analyseButton', + colProps: { + xs: 24, + sm: 24, + md: 6, + lg: 8, + xl: 8, + xxl: 8, + }, + itemProps: { + labelCol: { xs: 1, sm: 1 }, + wrapperCol: { xs: 23, sm: 23 }, + colon: false, + }, + }, +]; +//子表表格配置 +export const onlCgreportParamColumns: JVxeColumn[] = [ + { + title: '参数字段', + key: 'paramName', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '参数文本', + key: 'paramTxt', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '默认值', + key: 'paramValue', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + }, +]; +export const onlCgreportItemColumns: JVxeColumn[] = [ + { + title: '字段名字', + key: 'fieldName', + type: JVxeTypes.input, + width: '160px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '字段文本', + key: 'fieldTxt', + type: JVxeTypes.input, + width: '160px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '宽度', + key: 'fieldWidth', + type: JVxeTypes.input, + width: '80px', + defaultValue: '', + }, + { + title: '类型', + key: 'fieldType', + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + type: JVxeTypes.select, + options: [ + { title: '数值类型', value: 'Integer' }, + { title: '字符类型', value: 'String' }, + { title: '日期类型', value: 'Date' }, + { title: '时间类型', value: 'Datetime' }, + { title: '长整型', value: 'Long' }, + { title: '图片类型', value: 'Image' }, + ], + }, + { + title: '列显示', + key: 'isShow', + width: '80px', + align: 'center', + type: JVxeTypes.checkbox, + customValue: [1, 0], + defaultChecked: true, + }, + { + title: '字段href', + key: 'fieldHref', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '查询', + key: 'isSearch', + type: JVxeTypes.checkbox, + customValue: ['1', '0'], + width: '80px', + align: 'center', + defaultChecked: false, + }, + { + title: '查询模式', + key: 'searchMode', + type: JVxeTypes.select, + width: '120px', + placeholder: '请选择${title}', + options: [ + { title: '单值查询', value: 'single' }, + { title: '范围查询', value: 'group' }, + ], + }, + { + title: '取值表达式', + key: 'replaceVal', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '字典code', + key: 'dictCode', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '分组标题', + key: 'groupTitle', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '合计列', + align: 'center', + key: 'isTotal', + type: JVxeTypes.checkbox, + customValue: ['1', '0'], + width: '80px', + defaultChecked: false, + }, +]; diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportAigcModal.vue b/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportAigcModal.vue new file mode 100644 index 000000000..f5207481e --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportAigcModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportModal.vue b/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportModal.vue new file mode 100644 index 000000000..545e22f4f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/components/CgreportModal.vue @@ -0,0 +1,332 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/demo/ModalFormDemo.vue b/jeecgboot-vue3/src/views/super/online/cgreport/demo/ModalFormDemo.vue new file mode 100644 index 000000000..def058a56 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/demo/ModalFormDemo.vue @@ -0,0 +1,79 @@ + + diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/index.vue b/jeecgboot-vue3/src/views/super/online/cgreport/index.vue new file mode 100644 index 000000000..8bcd465d4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/index.vue @@ -0,0 +1,259 @@ + + + diff --git a/jeecgboot-vue3/src/views/super/online/cgreport/router/cgreportRouter.ts b/jeecgboot-vue3/src/views/super/online/cgreport/router/cgreportRouter.ts new file mode 100644 index 000000000..bda2b7cc0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/cgreport/router/cgreportRouter.ts @@ -0,0 +1,24 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerCgreportRouter() { + router.addRoute({ + path: '/online-auto-cgreport-router', + name: 'onl-auto-cgreport-router', + component: LAYOUT, + redirect: '/online/cgreport', + meta: { + title: 'OnlCgreportAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/cgreport/:id', + name: 'OnlCgReportList', + component: () => import('../auto/OnlCgReportList.vue'), + meta: {title: 'AUTO在线报表'}, + }, + ], + }) +} diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/GraphreportList.vue b/jeecgboot-vue3/src/views/super/online/graphreport/GraphreportList.vue new file mode 100644 index 000000000..483700026 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/GraphreportList.vue @@ -0,0 +1,206 @@ + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue new file mode 100644 index 000000000..86b8fbaee --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/ErrorTip.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/ErrorTip.vue new file mode 100644 index 000000000..2f6d5887c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/ErrorTip.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue new file mode 100644 index 000000000..33447aa28 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue new file mode 100644 index 000000000..ee39013ac --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue new file mode 100644 index 000000000..6ec9adb46 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue new file mode 100644 index 000000000..4548bde8f --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useChartRender.ts b/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useChartRender.ts new file mode 100644 index 000000000..cf22c8c0c --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useChartRender.ts @@ -0,0 +1,499 @@ +import { ref, watch, computed, reactive, ExtractPropTypes } from 'vue'; +import { router } from '/@/router'; +import { cloneDeep } from 'lodash-es'; +import { propTypes } from '/@/utils/propTypes'; +import { printJS } from '/@/hooks/web/usePrintJS'; +import { downloadByData } from '/@/utils/file/download'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import Bar from '/@/components/chart/Bar.vue'; +import Pie from '/@/components/chart/Pie.vue'; +import BarMulti from '/@/components/chart/BarMulti.vue'; +import LineMulti from '/@/components/chart/LineMulti.vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { isFunction } from '/@/utils/is'; + +export const ChartRenderProps = { + // 图表标题 + title: propTypes.string, + // 图表数据 + chartsData: propTypes.object, + // 是否运行在组件模式 + asComponent: propTypes.bool.def(false), +}; +type PropsType = ExtractPropTypes; + +export const ChartRenderEmits = ['error']; +export const ChartRenderComponents = { + LineMulti, + BarMulti, + Pie, + Bar, +}; + +export const ChartRenderCommon = { + components: ChartRenderComponents, + props: ChartRenderProps, + emits: ChartRenderEmits, +}; + +const errorText = { + jsonFormattingFailed: 'JSON字符串格式化失败', +}; + +export function useChartRender(props: PropsType, { emit }) { + const { + createMessage: $message, + createConfirm: $confirm, + createInfoModal: $info, + createErrorModal: $error, + createSuccessModal: $success, + createWarningModal: $warning, + } = useMessage(); + const headId = ref(null); + // 图表的高度 + const height = ref('400px'); + // 当前显示的图表 + const activeKey = ref('bar'); + // 图表类型 + const chartTypes = ref([]); + // 是否开启分页 + const pageSwitch = ref(true); + // 打印ID + const printId = computed(() => `print-content-${headId.value}`); + // 曲线图参数配置 + const lineParams = reactive({ + chartData: [] as Recordable[], + }); + // 柱状图参数配置 + const barParams = reactive({ + chartData: [] as Recordable[], + }); + // 饼图参数配置 + const pieParams = reactive({ + chartData: [] as Recordable[], + }); + // 折柱图参数配置 + const barLineParams = reactive({ + dataSource: [] as Recordable[], + }); + // 表格参数配置 + const tableParams = reactive({ + // 固定的列数据 + fixedColumns: [ + { + title: '#', + key: 'rowIndex', + width: '10%', + align: 'center', + customRender: function ({ record, index }) { + if (record.isTotal === true) { + return '总计'; + } else { + return parseInt(index) + 1; + } + }, + }, + ], + columns: [] as Recordable[], + dataSource: [] as Recordable[], + }); + // 用户JS增强的事件暂存处(通过headId隔离) + const extendJsHandlerIsolation = reactive({}); + // 当前图表的JS增强 + const extendJsHandler = computed>({ + get() { + if (headId.value == null) { + return null; + } else { + return extendJsHandlerIsolation[headId.value]; + } + }, + set(obj) { + if (headId.value != null) { + extendJsHandlerIsolation[headId.value] = obj; + } + }, + }); + // 是否包含曲线图 + const hasLine = computed(() => chartTypes.value.includes('line')); + // 是否包含柱状图 + const hasBar = computed(() => chartTypes.value.includes('bar')); + // 是否包含饼图 + const hasPie = computed(() => chartTypes.value.includes('pie')); + // 是否包含数据表格 + const hasTable = ref(false); + // 曲线图参数 + const lineProps = computed(() => { + return { + type: 'line', + height: height.value, + chartData: lineParams.chartData, + onClick(params) { + // console.debug('lineProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 柱状图参数 + const barProps = computed(() => { + return { + height: height.value, + chartData: barParams.chartData, + onClick(params) { + // console.debug('barProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 饼图参数 + const pieProps = computed(() => { + return { + height: height.value, + chartData: pieParams.chartData, + onClick(params) { + // console.debug('pieProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 折柱图参数 + const barLineProps = computed(() => { + return { + height: height.value, + dataSource: barLineParams.dataSource, + onClick(_event, _chart) { + console.debug('barLineProps-click: ', arguments); + }, + }; + }); + // 图表区域 ACard 标签的固定属性 + const chartCardProps = computed(() => { + return { + title: props.title, + headStyle: { paddingLeft: '20px' }, + bodyStyle: { padding: '10px' }, + bordered: !props.asComponent, + }; + }); + // 数据表格区域 ACard 标签的固定属性 + const tableCardProps = computed(() => { + return { + title: '数据明细', + headStyle: { paddingLeft: '20px' }, + bodyStyle: { padding: '0' }, + style: { marginTop: '20px' }, + bordered: !props.asComponent, + }; + }); + // 导出按钮固定属性 + const exportButtonProps = computed(() => { + return { + type: 'primary', + preIcon: 'ant-design:download', + text: '导出', + style: { margin: '12px' }, + }; + }); + /** 分页开关固定属性 */ + const pageSwitchProps = computed(() => { + return { + checkedChildren: '分页', + unCheckedChildren: '分页', + style: { + position: 'absolute', + top: '17px', + right: '12px', + }, + }; + }); + // 数据表格的固定属性 + const tableProps = computed(() => { + return { + size: 'middle', + rowKey: 'id', + // bordered: true, + pagination: pageSwitch.value ? { pageSize: 10 } : false, + columns: tableParams.columns, + dataSource: tableParams.dataSource, + style: { borderTop: '1px solid #e8e8e8' }, + }; + }); + // 是否显示打印按钮 + const showPrint = computed(() => !props.asComponent); + // 是否显示详情按钮 + const showDetail = computed(() => props.asComponent); + + watch( + () => props.chartsData, + (data) => parseChartsData(data), + { immediate: true } + ); + + /** 执行JS扩展 */ + function executeExtendJs(headId, jsCode) { + if (!jsCode || !headId) { + return; + } + let onClick = { line: null, bar: null, pie: null }; + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + // 执行JS增强 + new Function('onClick', 'headId', `${jsCode}`)(onClick, headId); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + if (extendJsHandler.value == null) { + extendJsHandler.value = { click: onClick }; + } else { + extendJsHandler.value.click = onClick; + } + } + + // click 事件的 this 指向 + const onClickThis = { + $router: router, + $http: defHttp, + $message, + $confirm, + $info, + $error, + $success, + $warning, + }; + + /** 触发JS增强里定义的事件 */ + function emitExtendJsEvent(params) { + if (extendJsHandler.value != null) { + let clickType = params.seriesType; + let fn: Fn = extendJsHandler.value.click[clickType]; + if (isFunction(fn)) { + fn.call(onClickThis, params); + } + } + } + + /** 解析 chartData */ + function parseChartsData(chartsData) { + if (chartsData == null) return null; + let { head, data, items, dictOptions } = chartsData; + if (head == null) return; + let { id, xaxisField, yaxisField, dataType, cgrSql, graphType, extendJs } = head; + headId.value = id; + executeExtendJs(id, extendJs); + try { + data = dataType === 'sql' || dataType === 'api' ? data : JSON.parse(cgrSql); + } catch { + emit('error', errorText.jsonFormattingFailed); + return; + } + let dictList = dictOptions[xaxisField]; + let graphTypes = graphType.split(','); + activeKey.value = graphTypes[0]; + if (activeKey.value == 'table') { + activeKey.value = graphTypes[1]; + } + chartTypes.value = graphTypes; + let yaxisFields: string[] = yaxisField.split(','); + let fieldMap = new Map(); + items.forEach((item) => fieldMap.set(item.fieldName, item)); + // 判断是否定义了数据表格,如果定义了则删除该项,不参与动态渲染,只显示在最底部 + let index = graphTypes.indexOf('table'); + hasTable.value = index !== -1; + if (hasTable.value) { + graphTypes.splice(index, 1); + } + let parseOption = { graphTypes, data, items, fieldMap, xaxisField, yaxisFields, dictList, dictOptions }; + parseLineData(parseOption); + parseBarData(parseOption); + parsePicData(parseOption); + parseTableData(parseOption); + } + + type ParseDataOption = { + graphTypes: string[]; + data: Recordable[]; + items: Recordable[]; + fieldMap: Map; + xaxisField: string; + yaxisFields: string[]; + dictList; + dictOptions; + }; + + // 根据用户配置构造出通用数据 + function parseCommonData(option: ParseDataOption) { + let { data, fieldMap, xaxisField, yaxisFields, dictList } = option; + let chartData: Recordable[] = []; + for (let yField of yaxisFields) { + for (let item of data) { + let name = item[xaxisField]; + // 判断是否有字典 + if (dictList) { + name = filterDictText(dictList, name); + } + chartData.push({ + name: name, + value: item[yField], + type: fieldMap.get(yField)?.fieldTxt || yField, + }); + } + } + return chartData; + } + + // 根据用户配置构造出 lineChartData + function parseLineData(option: ParseDataOption) { + let { graphTypes } = option; + if (graphTypes.includes('line')) { + lineParams.chartData = parseCommonData(option); + } + } + + // 根据用户配置构造出 barChartData + function parseBarData(option: ParseDataOption) { + let { graphTypes } = option; + if (graphTypes.includes('bar')) { + barParams.chartData = parseCommonData(option); + } + } + + // 根据用户配置构造出 pieChartData + function parsePicData(option: ParseDataOption) { + let { graphTypes, data, xaxisField, yaxisFields, dictList } = option; + let yField = yaxisFields[0]; + if (graphTypes.includes('pie')) { + let chartData: Recordable[] = []; + for (let item of data) { + let name = item[xaxisField]; + // 判断是否有字典 + if (dictList) { + name = filterDictText(dictList, name); + } + chartData.push({ + name: name, + value: item[yField], + }); + } + pieParams.chartData = chartData; + } + } + + // 根据用户配置构造出 tableData + function parseTableData(option: ParseDataOption) { + let { data, items, xaxisField, yaxisFields, dictList, dictOptions } = option; + if (hasTable.value) { + tableParams.dataSource = data.map((item, index) => { + item.id = index; + let pieData = { + item: item[xaxisField], + count: item[yaxisFields[0]], + }; + // 判断是否有字典 + if (dictList) { + pieData.item = filterDictText(dictList, pieData.item); + } + return item; + }); + // 根据用户配置构造出 tableColumns + let tableColumns: Recordable[] = cloneDeep(tableParams.fixedColumns); + let isTotals: string[] = []; + items.forEach((item) => { + if (item.isShow === 'Y') { + let column: Recordable = { + align: 'center', + width: '10%', + title: item.fieldTxt, + dataIndex: item.fieldName, + }; + if (item.dictCode) { + column.customRender = ({ text }) => filterDictText(dictOptions[item.fieldName], text); + } + tableColumns.push(column); + // 判断是否计算总数 + if (item.isTotal === 'Y') isTotals.push(item.fieldName); + } + }); + tableParams.columns = tableColumns; + // 如果有计算需要的值就进行计算 + if (isTotals.length > 0) { + let totalRow = { id: tableParams.dataSource.length, isTotal: true }; + isTotals.forEach((column) => { + let count = 0; + tableParams.dataSource.forEach((row) => { + count += parseFloat(row[column]); + }); + totalRow[column] = isNaN(count) ? '包含非数字内容' : count.toFixed(2); + }); + tableParams.dataSource.push(totalRow); + } + } + } + + // 导出Excel + function onExportXls() { + let fileName = props.title; + defHttp + .get( + { + url: '/online/graphreport/api/exportXlsById', + params: { + id: headId.value, + name: fileName, + }, + responseType: 'blob', + }, + { isTransformResponse: false } + ) + .then((data) => { + if (!data || data.size == 0) { + $message.warning('导出失败!'); + return; + } + downloadByData(data, fileName + '.xls'); + }); + } + + // 打印 + function onPrint() { + printJS({ + type: 'html', + printable: '#' + printId.value, + }); + } + + /** 跳转到详情页 */ + function onGoToDetail() { + goToInfo(props.chartsData); + } + + function goToInfo(data) { + let url = `/online/graphreport/chart/${data.head.id}`; + router.push({ path: url }); + } + + return { + headId, + printId, + height, + activeKey, + chartTypes, + pageSwitch, + showPrint, + showDetail, + hasLine, + hasBar, + hasPie, + hasTable, + lineProps, + barProps, + pieProps, + tableProps, + barLineProps, + chartCardProps, + tableCardProps, + exportButtonProps, + pageSwitchProps, + extendJsHandlerIsolation, + onPrint, + onGoToDetail, + onExportXls, + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts b/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts new file mode 100644 index 000000000..2a32c3fc0 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts @@ -0,0 +1,101 @@ +import { nextTick, Ref, h } from 'vue'; +import { FormSchema } from '/@/components/Form'; +import { InputNumber, Input, DatePicker } from 'ant-design-vue'; + +type PSchema = Partial; + +export function useParseFormSchemas(chartsData: Ref, showSearchField: Ref) { + // 解析查询条件 FormSchemas + async function parseFormSchemas() { + let { head, items, dictOptions } = chartsData.value; + if (head.dataType === 'sql') { + let formSchemas: FormSchema[] = []; + items.forEach((field) => { + // 判断是否查询 + if (field.searchFlag !== 'Y') return; + let isRange = field.searchMode === 'group'; + let schema: PSchema = {}; + let schemas: FormSchema[] = []; + // 判断是否有字典,字典组件不能范围查询 + if (field.dictCode && dictOptions[field.dictCode]) { + schema.component = 'Select'; + schema.componentProps = { + options: dictOptions[field.dictCode], + }; + } else if (['Integer', 'Long'].includes(field.fieldType)) { + // 数字输入框 + schema.component = 'InputNumber'; + if (isRange) { + schema.render = getRangeRender(schemas, field, InputNumber); + } + } else if (field.fieldType === 'Date') { + // 日期选择组件 + schema.component = 'DatePicker'; + schema.componentProps = { + format: 'YYYY-MM-DD', + }; + if (isRange) { + schema.render = getRangeRender(schemas, field, DatePicker); + } + } else { + // 普通文本框 + schema.component = 'Input'; + if (isRange) { + schema.render = getRangeRender(schemas, field, Input); + } + } + formSchemas = formSchemas + .concat({ + label: field.fieldTxt, + field: field.fieldName, + component: 'Input', + itemProps: { + class: { 'range-query': isRange }, + } as any, + ...schema, + }) + .concat(schemas); + }); + showSearchField.value = formSchemas.length > 0; + await nextTick(); + return formSchemas; + } else { + showSearchField.value = false; + return null; + } + } + + return { parseFormSchemas }; +} + +/** + * 获取范围渲染方法 + * + * @param schemas 显示名 + * @param fieldItem 字段 + * @param component vue 组件 + */ +function getRangeRender(schemas: FormSchema[], fieldItem, component: any) { + let { fieldTxt: label, fieldName: beginField } = fieldItem; + let endField = beginField + '_end'; + // 添加占位符 + schemas.push({ label: '', field: endField, component: 'Input', show: false }); + return function ({ model }) { + return [ + h(component, { + value: model[beginField], + 'onUpdate:value': (v) => (model[beginField] = v), + placeholder: '请输入开始' + label, + + format: 'YYYY-MM-DD', + }), + h('span', { class: 'range-span' }, '~'), + h(component, { + value: model[endField], + 'onUpdate:value': (v) => (model[endField] = v), + placeholder: '请输入结束' + label, + format: 'YYYY-MM-DD', + }), + ]; + }; +} diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportAigcModal.vue b/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportAigcModal.vue new file mode 100644 index 000000000..796a1c5bb --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportAigcModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportModal.vue b/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportModal.vue new file mode 100644 index 000000000..7a22507fe --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/components/GraphreportModal.vue @@ -0,0 +1,346 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/FieldTable.vue b/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/FieldTable.vue new file mode 100644 index 000000000..77468cfe5 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/FieldTable.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/ParamsTable.vue b/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/ParamsTable.vue new file mode 100644 index 000000000..c0839bf08 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/components/tables/ParamsTable.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.api.ts b/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.api.ts new file mode 100644 index 000000000..7ea45b036 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.api.ts @@ -0,0 +1,39 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/graphreport/head/list', + delete = '/online/graphreport/head/delete', + deleteBatch = '/online/graphreport/head/deleteBatch', + exportXls = '/online/graphreport/head/exportXls', + importXls = '/online/graphreport/head/importExcel', + parseField = '/online/graphreport/head/parseField', + paramsList = '/online/graphreport/params/listByHeadId', + getChartsData = '/online/graphreport/api/getChartsData', + getParamsInfo = '/online/graphreport/params/listByHeadId', +} + +/** + * 列表接口 + * e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +// 批量删除 +export function doBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { ids: idList.join(',') }, + }, + { joinParamsToUrl: true } + ); +} + +export const queryParamsList = (headId: string) => defHttp.get({ url: Api.paramsList, params: { headId } }); + +// 查询图表数据 +export const getChartsData = (params) => defHttp.get({ url: Api.getChartsData, params: params }); +export const getParamsInfo = (params) => defHttp.get({ url: Api.getParamsInfo, params: params }); + +export const parseField = (type, data, params = {}) => defHttp.post({ url: Api.parseField, params: { type, data, ...params } }); diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.data.ts b/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.data.ts new file mode 100644 index 000000000..1cce46dc4 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/graphreport.data.ts @@ -0,0 +1,241 @@ +import { FormSchema } from '/@/components/Form'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { bindMapFormSchema } from '/@/utils/common/compUtils'; +import { computed, ref } from 'vue'; +import { usePermissionStore } from '/@/store/modules/permission'; + +// 弹窗表单 +export function useFormSchemas(_, handler) { + const permissionStore = usePermissionStore(); + // 由于需要动态改变布局,所以使用 computed + type SpanType = 'one' | 'tow' | 'threeTow' | 'three'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 2 }, + wrapperCol: { xs: 24, sm: 22 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 4 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 三分之二列 + threeTow: { + colProps: { xs: 24, sm: 16 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 21 }, + }, + }, + // 三列 + three: { + colProps: { xs: 24, sm: 8 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 18 }, + }, + }, + }, + 'three' + ); + + const dataType = ref('sql'); + const isCombination = ref('combination'); + // 根据不同的值展示不同的form + const formInfo = { + cgrSql: { + sql: { label: '查询SQL', placeholder: '请输入查询SQL', language: 'sql' }, + json: { label: '数据JSON', placeholder: '请输入数据JSON', language: 'javascript' }, + api: { label: 'API接口', placeholder: '请输入API接口', language: 'javascript' }, + }, + }; + const cgrSqlFormInfo = computed(() => { + return formInfo.cgrSql[dataType.value]; + }); + + const formSchemas = computed(() => [ + { label: 'ID', field: 'id', component: 'Input', show: false }, + mapFormSchema({ + label: '图表名称', + field: 'name', + component: 'Input', + required: true, + }), + mapFormSchema({ + label: '编码', + field: 'code', + component: 'Input', + dynamicRules({ model }) { + return [ + { required: true, message: '请输入编码!' }, + { + async validator({}, value) { + if (/[\u4E00-\u9FA5]/g.test(value)) { + return Promise.reject('编码不能为汉字'); + } + let { success, message } = await duplicateCheckDelay({ + tableName: 'onl_graphreport_head', + fieldName: 'code', + fieldVal: value, + dataId: model.id, + }); + if (!success) { + return Promise.reject(message); + } + }, + }, + ]; + }, + }), + mapFormSchema({ + label: '展示模板', + field: 'displayTemplate', + component: 'Select', + componentProps: { + options: [ + { label: 'Tab风格', value: 'tab' }, + { label: '单排布局', value: 'single' }, + { label: '双排布局', value: 'double' }, + ], + }, + defaultValue: 'tab', + }), + mapFormSchema({ + label: 'X轴字段', + field: 'xaxisField', + component: 'Input', + required: true, + }), + mapFormSchema( + { + label: 'Y轴字段', + field: 'yaxisField', + component: 'JDictSelectTag', + componentProps: { + mode: 'tags', + open: false, + dictCode: 'online_graph_display_template', + }, + required: true, + }, + 'threeTow' + ), + mapFormSchema({ + label: '数据类型', + field: 'dataType', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'online_graph_data_type', + showChooseOption: false, + onChange: (value) => (dataType.value = value), + }, + defaultValue: 'sql', + }), + mapFormSchema({ + label: '数据源', + field: 'dbSource', + component: 'Select', + componentProps: { + options: handler.dbSourceOptions.value, + }, + rules: [{ required: permissionStore.sysSafeMode, message: '请选择数据源!' }], + ifShow: ({ model }) => model.dataType === 'sql', + }), + mapFormSchema( + { + label: '图表类型', + field: 'graphType', + component: 'JDictSelectTag', + componentProps: { + mode: isCombination.value === 'single' ? 'default' : 'multiple', + dictCode: 'online_graph_type', + showChooseOption: false, + }, + defaultValue: ['bar'], + }, + dataType.value === 'sql' ? 'three' : 'threeTow' + ), + mapFormSchema( + { + label: '描述', + field: 'content', + component: 'Input', + }, + 'one' + ), + mapFormSchema( + { + label: cgrSqlFormInfo.value?.label, + field: 'cgrSql', + required: true, + component: 'JCodeEditor', + componentProps: { + placeholder: cgrSqlFormInfo.value?.placeholder, + language: cgrSqlFormInfo.value?.language, + fullScreen: true, + autoHeight: '!ie', + height: '100px', + }, + dynamicRules() { + return [ + { + required: true, + // 根据数据类型的不同显示不同的错误信息 + message: cgrSqlFormInfo.value?.placeholder, + }, + { + // 自定义校验:校验JSON字符串格式是否正确 + async validator({}, value) { + if (value && dataType.value === 'json') { + try { + JSON.parse(value); + } catch { + return Promise.reject('JSON格式不正确!'); + } + } + }, + }, + ]; + }, + }, + 'one' + ), + mapFormSchema( + { + label: ' ', // SQL解析 + field: 'cgrSql', + component: 'Input', + slot: 'SQLAnalyzeButton', + itemProps: { colon: false }, + // ifShow: ({ model }) => model.dataType === 'sql', + }, + 'one' + ), + mapFormSchema( + { + label: 'JS增强', + field: 'extendJs', + component: 'JCodeEditor', + componentProps: { + placeholder: 'JS增强', + language: 'javascript', + fullScreen: true, + autoHeight: '!ie', + height: '100px', + }, + }, + 'one' + ), + ]); + + return { formSchemas, dataType, isCombination }; +} diff --git a/jeecgboot-vue3/src/views/super/online/graphreport/router/graphreportRouter.ts b/jeecgboot-vue3/src/views/super/online/graphreport/router/graphreportRouter.ts new file mode 100644 index 000000000..5bad0150d --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/graphreport/router/graphreportRouter.ts @@ -0,0 +1,24 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerGraphreportRouter() { + router.addRoute({ + path: '/online-auto-graphreport-router', + name: 'onl-auto-graphreport-router', + component: LAYOUT, + redirect: '/online/graphreport', + meta: { + title: 'OnlGraphreportAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/graphreport/chart/:code', + name: 'GraphreportAutoChart', + component: () => import('../auto/GraphreportAutoChart.vue'), + meta: {title: 'AUTO在线图表'}, + }, + ], + }) +} diff --git a/jeecgboot-vue3/src/views/super/online/register.ts b/jeecgboot-vue3/src/views/super/online/register.ts new file mode 100644 index 000000000..843a47936 --- /dev/null +++ b/jeecgboot-vue3/src/views/super/online/register.ts @@ -0,0 +1,47 @@ +import type {App} from 'vue'; +import {createAsyncComponent} from "@/utils/factory/createAsyncComponent"; +import {registerCgformRouter} from "./cgform/router/cgformRouter"; +import {registerCgreportRouter} from "./cgreport/router/cgreportRouter"; +import {registerGraphreportRouter} from "./graphreport/router/graphreportRouter"; + +/** 注册 online */ +export async function register(app: App) { + + // 注册Online弹窗 + const OnlineAutoModalAsync = createAsyncComponent(() => import('./cgform/auto/default/OnlineAutoModal.vue'), {loading: true}); + app.component('OnlineAutoModal', OnlineAutoModalAsync); + + // 注册Online表单路由 + registerCgformRouter(); + // 注册Online报表路由 + registerCgreportRouter(); + // 注册Online图表路由 + registerGraphreportRouter(); + + // 注册外部链接页面(如果有) + await registerCgformShareView(); + + console.log('[online] 注册完成!'); +} + +// 注册外部链接页面,自动判断是否存在 +async function registerCgformShareView() { + try { + const globModels = import.meta.glob('./cgform/share/index.ts'); + if (!globModels) { + return + } + const models = Object.values(globModels); + if (models.length == 0) { + return + } + const shareModel = await models[0]() as Recordable; + if (typeof shareModel?.register !== 'function') { + return + } + await shareModel.register(); + + console.debug('[online] Online表单外部链接路由注册完成!'); + } catch (e) { + } +}