设置表单控件的值',
+ 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: () => (
+
+
+ destroy()}>取消
+ 移除表单
+ 删除表单
+
+
+ ),
+ });
+
+ 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 @@
+
+
+
+
+
+ 新增
+ AI建表
+ 自定义按钮
+ JS增强
+ SQL增强
+ JAVA增强
+ 导入数据库表
+ 代码生成
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+ 批量操作
+
+
+
+
+
+
+ 已同步
+ 未同步
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 继续{{ isUpdate ? '修改' : '新增' }}
+
+
+
+ 查看
+ 新
+ 数据
+
+
+
+
+
+
+ 添加数据
+ 编辑新数据
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+ {{ text }}
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ getUserInfo.realname }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+ {{ t('layout.header.welcomeIn') }} {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ loginType === 'accountLogin' ? t('sys.login.signInFormTitle') : t('sys.login.mobileSignInFormTitle') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.otherSignIn') }}
+
+
+
+
{{ t('sys.login.registerButton') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ languageValue === 'zh_CN' ? 'CN' : 'EN' }}
+
+
+
+
+
+ 简体中文
+
+
+ English
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ t('sys.login.userName') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.password') }}
+
+
+
+
+
+
+
+
+
+ {{ t('sys.login.inputCode') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('sys.login.forgetPassword') }}
+
+
+
{{ t('sys.login.rememberMe') }}
+
+
+
+
{{ t('sys.login.loginButton') }}
+
+ {{ t('sys.login.mobileSignInFormTitle') }}
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ t('sys.login.forgetFormTitle') }}
+
+
+
+
+
+
+
+
+ {{ t('sys.login.mobile') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.smsCode') }}
+
+
+
+
+
+
+ {{ t('sys.login.nextStep') }}
+
+
+
+
+
+
+
+
+ {{ t('sys.login.userName') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.password') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.confirmPassword') }}
+
+
+
+
+ 完成
+
+
+
+
{{ t('sys.exception.backLogin') }}
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+ 请填写昵称和邮箱,方便大家与您联系
+
+
+
+
+
+
+
+ {{ t('sys.login.email') }}
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ t('sys.login.signUpFormTitle') }}
+
+
+
+
+
+
+
+
+ {{ t('sys.login.mobile') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.smsCode') }}
+
+
+
+
+
+
+
+
+
+ 8-20位,需包含字母和数字
+
+
+
+
+
+ {{ t('sys.login.policy') }}
+
+
+
+
{{ t('sys.login.nextStep') }}
+
+
+ {{ t('sys.exception.backLogin') }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ t('sys.login.mobile') }}
+
+
+
+
+
+
+
+ {{ t('sys.login.smsCode') }}
+
+
+
+
+
+
+
{{ t('sys.login.loginButton') }}
+
+ {{ t('sys.login.backSignIn') }}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
创建报表需要专业建议?试试AI智能建表吧
+
+
请选择Online表单,并输入报表需求,例如:统计男生女生各自的数量
+
+
+
+ {{ item.text }}
+
+ 女
+
+
+
+
+
+
+
+
+ 关闭
+
+ 立即生成
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 您可以键入“”作为一个参数,这里abc是参数的名称。例如:
+ select * from table where id = ${abc}。
+ select * from table where id like concat('%',${abc},'%')。(mysql模糊查询)
+ select * from table where id like '%'||${abc}||'%'。(oracle模糊查询)
+ select * from table where id like '%'+${abc}+'%'。(sqlserver模糊查询)
+ 注:参数只支持动态报表,popup暂不支持
+
+
+
+
SQL解析
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ 录入
+ AI生成报表
+
+
+
+
+
+ 删除
+
+
+
+ 批量操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ 新增
+ AI生成图表
+
+
+
+
+
+
+ 删除
+
+
+
+
+ 批量操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ exportButtonProps.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ exportButtonProps.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ 曲线图
+
+
+ 柱状图
+
+
+ 饼图
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ exportButtonProps.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
创建图表需要专业建议?试试AI智能建表吧
+
+
请选择Online表单,并输入图表需求,例如:统计男生女生各自的数量
+
+
+
+ {{ item.text }}
+
+ 女
+
+
+
+
+
+
+
+
+ 关闭
+
+ 立即生成
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
SQL解析
+
JSON解析
+
API解析
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+ 保存
+
+
+
+
+
+
+
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) {
+ }
+}