From 870892c346782331385f1a7e91d3c7844fa16b7b Mon Sep 17 00:00:00 2001 From: zhangdaiscott Date: Mon, 6 Jun 2022 13:58:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=89=88=E5=8F=91=E5=B8=83=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BD=8E=E4=BB=A3=E7=A0=81=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=9A=20online=E8=A1=A8=E5=8D=95=E3=80=81online=E6=8A=A5?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + docs/Vue3升级脚本_mysql.sql | 12 + package.json | 4 +- src/components/Form/src/componentMap.ts | 2 + .../src/jeecg/components/JRangeNumber.vue | 69 ++ src/components/Form/src/types/index.ts | 1 + .../jeecg/OnLine/JPopupOnlReport.vue | 218 +++++ .../jeecg/OnLine/SearchFormItem.vue | 289 +++++++ .../jeecg/OnLine/hooks/usePopBiz.ts | 746 ++++++++++++++++++ .../jeecg/OnLine/types/onlineConfig.ts | 40 + src/main.ts | 5 + src/router/helper/routeHelper.ts | 7 +- src/utils/monorepo/dynamicRouter.ts | 19 + src/utils/monorepo/registerPackages.ts | 50 ++ vite.config.ts | 4 + 15 files changed, 1466 insertions(+), 2 deletions(-) create mode 100644 src/components/Form/src/jeecg/components/JRangeNumber.vue create mode 100644 src/components/jeecg/OnLine/JPopupOnlReport.vue create mode 100644 src/components/jeecg/OnLine/SearchFormItem.vue create mode 100644 src/components/jeecg/OnLine/hooks/usePopBiz.ts create mode 100644 src/components/jeecg/OnLine/types/onlineConfig.ts create mode 100644 src/utils/monorepo/dynamicRouter.ts create mode 100644 src/utils/monorepo/registerPackages.ts diff --git a/.gitignore b/.gitignore index 704fbb9..ca1015e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ pnpm-debug.log* *.sln *.sw? /os_del.cmd +/.vscode/ +/.history/ diff --git a/docs/Vue3升级脚本_mysql.sql b/docs/Vue3升级脚本_mysql.sql index d81f958..0352a9d 100644 --- a/docs/Vue3升级脚本_mysql.sql +++ b/docs/Vue3升级脚本_mysql.sql @@ -259,3 +259,15 @@ INSERT INTO `sys_permission` VALUES ('d7d6e2e4e2934f2c9385a623fd98c6f3', '', ' delete from sys_permission where id = '1449995470942593026'; -- 角色授权vue3的菜单 + + +-- online低代码菜单(online表单、online报表) +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1455100420297859074', '', '在线开发', '/online', 'layouts/default/index', 1, NULL, '/online/cgform', 0, NULL, '0', 2.00, 0, 'ant-design:cloud-outlined', 0, 0, 0, 0, NULL, 'admin', '2021-11-01 17:12:29', 'admin', '2022-05-11 16:38:26', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1455101470794850305', '1455100420297859074', 'Online表单开发', '/online/cgform', 'super/online/cgform/index', 1, NULL, NULL, 1, NULL, '0', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2021-11-01 17:16:40', 'admin', '2022-04-04 18:36:25', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1457678003102879745', '1455100420297859074', '系统编码规则', '/system/fillrule', 'system/fillRule/index', 1, NULL, NULL, 1, NULL, '0', 9.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2021-11-08 19:54:53', 'admin', '2021-11-18 10:49:40', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1458353686530232321', '1455100420297859074', '系统校验规则', '/system/checkrule', 'system/checkRule/index', 1, NULL, NULL, 1, NULL, '0', 15.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2021-11-10 16:39:48', 'admin', '2021-11-18 10:49:48', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1460888189937176577', '1455100420297859074', 'Online报表配置', '/online/cgreport', 'super/online/cgreport/index', 1, NULL, NULL, 1, NULL, '0', 2.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2021-11-17 16:31:01', 'admin', '2021-12-08 10:55:32', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1461270075543347202', '1455100420297859074', 'Online表单视图', '/online/copyform/:code', 'super/online/cgform/CgformCopyList', 1, NULL, NULL, 1, NULL, '0', 99.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2021-11-18 17:48:30', NULL, NULL, 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1461291438825558017', '1455100420297859074', 'AUTO在线报表', '/online/cgreport/:id', 'super/online/cgreport/auto/OnlCgReportList', 1, NULL, NULL, 1, NULL, '0', 2.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2021-11-18 19:13:23', 'admin', '2021-11-19 20:16:13', 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1465686870713782273', '1455100420297859074', 'AUTO在线表单', '/online/cgformList/:id', 'super/online/cgform/auto/default/OnlineAutoList', 1, NULL, NULL, 1, NULL, '0', 5.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2021-11-30 22:19:16', NULL, NULL, 0, 0, NULL, 0); +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1509417558230999041', '1455100420297859074', 'AUTO树表单列表', '/online/cgformTreeList/:id', 'super/online/cgform/auto/tree/OnlineAutoTreeList', 1, NULL, NULL, 1, NULL, '0', 5.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2022-03-31 14:29:24', NULL, NULL, 0, 0, NULL, 0); diff --git a/package.json b/package.json index b69c650..45e4751 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "bootstrap": "yarn install", "serve": "npm run dev", "dev": "vite", + "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 vite build && esno ./build/script/postBuild.ts", "build:test": "cross-env vite build --mode test && esno ./build/script/postBuild.ts", "build:no-cache": "yarn clean:cache && npm run build", @@ -18,7 +19,6 @@ "preview": "npm run build && vite preview", "preview:dist": "vite preview", "log": "conventional-changelog -p angular -i CHANGELOG.md -s", - "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", "clean:lib": "rimraf node_modules", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", @@ -34,6 +34,7 @@ "gen:icon": "esno ./build/generate/icon/index.ts" }, "dependencies": { + "@jeecg/online": "1.0.1", "@iconify/iconify": "^2.0.4", "@fullcalendar/core": "^5.10.1", "@fullcalendar/daygrid": "^5.10.1", @@ -191,6 +192,7 @@ "@fullcalendar/interaction", "@fullcalendar/timegrid", "@fullcalendar/vue3", + "@jeecg/online", "@vueuse/core", "@vueuse/shared", "@zxcvbn-ts/core", diff --git a/src/components/Form/src/componentMap.ts b/src/components/Form/src/componentMap.ts index 7b5b7fd..a1b4294 100644 --- a/src/components/Form/src/componentMap.ts +++ b/src/components/Form/src/componentMap.ts @@ -58,6 +58,7 @@ import JSearchSelect from './jeecg/components/JSearchSelect.vue' import JAddInput from './jeecg/components/JAddInput.vue' import {Time} from '/@/components/Time'; import JOnlineSelectCascade from './jeecg/components/JOnlineSelectCascade.vue' +import JRangeNumber from './jeecg/components/JRangeNumber.vue' const componentMap = new Map(); @@ -125,6 +126,7 @@ componentMap.set('JUpload', JUpload); componentMap.set('JSearchSelect', JSearchSelect); componentMap.set('JAddInput', JAddInput); componentMap.set('JOnlineSelectCascade', JOnlineSelectCascade) +componentMap.set('JRangeNumber', JRangeNumber) export function add(compName: ComponentType, component: Component) { componentMap.set(compName, component); diff --git a/src/components/Form/src/jeecg/components/JRangeNumber.vue b/src/components/Form/src/jeecg/components/JRangeNumber.vue new file mode 100644 index 0000000..daf7177 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JRangeNumber.vue @@ -0,0 +1,69 @@ + + + + + \ No newline at end of file diff --git a/src/components/Form/src/types/index.ts b/src/components/Form/src/types/index.ts index 9bfea41..8ba826d 100644 --- a/src/components/Form/src/types/index.ts +++ b/src/components/Form/src/types/index.ts @@ -142,4 +142,5 @@ export type ComponentType = | 'JAddInput' | 'Time' | 'JOnlineSelectCascade' + | 'JRangeNumber' ; diff --git a/src/components/jeecg/OnLine/JPopupOnlReport.vue b/src/components/jeecg/OnLine/JPopupOnlReport.vue new file mode 100644 index 0000000..f7a3df0 --- /dev/null +++ b/src/components/jeecg/OnLine/JPopupOnlReport.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/src/components/jeecg/OnLine/SearchFormItem.vue b/src/components/jeecg/OnLine/SearchFormItem.vue new file mode 100644 index 0000000..068fa0f --- /dev/null +++ b/src/components/jeecg/OnLine/SearchFormItem.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/components/jeecg/OnLine/hooks/usePopBiz.ts b/src/components/jeecg/OnLine/hooks/usePopBiz.ts new file mode 100644 index 0000000..aa7ec7f --- /dev/null +++ b/src/components/jeecg/OnLine/hooks/usePopBiz.ts @@ -0,0 +1,746 @@ +import {reactive, ref, unref, defineAsyncComponent } from "vue"; +import {httpGroupRequest} from "/@/components/Form/src/utils/GroupRequest"; +import {defHttp} from '/@/utils/http/axios'; +import {filterMultiDictText} from '/@/utils/dict/JDictSelectUtil.js'; +import {useMessage} from '/@/hooks/web/useMessage'; +import { OnlineColumn } from '/@/components/jeecg/OnLine/types/onlineConfig' +import { h } from 'vue' +import { useRouter } from 'vue-router'; +import {useMethods} from '/@/hooks/system/useMethods'; + +export function usePopBiz(props,tableRef?) { + + const {createMessage} = useMessage(); + //弹窗可视状态 + const visible = ref(false); + //表格加载 + const loading = ref(false); + //cgRpConfigId + const cgRpConfigId = ref(''); + //标题 + const title = ref('列表'); + // 排序字段,默认无排序 + const iSorter = ref(''); + // 查询对象 + const queryInfo = ref([]); + // 查询参数 + const queryParam = ref({}); + // 动态参数 + const dynamicParam = ref({}); + //字典配置项 + const dictOptions = ref({}); + //数据集 + const dataSource = ref>([]); + //定义表格信息 + const columns = ref>([]); + //定义请求url信息 + const configUrl = reactive({ + //列表页加载column和data + getColumnsAndData: '/online/cgreport/api/getColumnsAndData/', + getColumns: '/online/cgreport/api/getRpColumns/', + getData: '/online/cgreport/api/getData/', + getQueryInfo: '/online/cgreport/api/getQueryInfo/', + export: '/online/cgreport/api/exportXls/' + }); + //已选择的值 + const checkedKeys = ref>([]); + //选择的行记录 + const selectRows = ref>([]); + // 点击单元格选中行 popup需要 但是报表预览不需要 + let clickThenCheckFlag = true + if(props.clickToRowSelect===false){ + clickThenCheckFlag = false + } + + /** + * 选择列配置 + */ + const rowSelection = { + fixed: true, + selectedRowKeys: checkedKeys, + selectionRows: selectRows, + onChange: onSelectChange + } + + /** + * 序号列配置 + */ + const indexColumnProps = { + dataIndex: 'index', + width: '15px', + }; + /** + * 分页配置 + */ + const pagination = reactive({ + current: 1, + pageSize: 10, + pageSizeOptions: ['10', '20', '30'], + // showTotal: (total, range) => { + // return range[0] + '-' + range[1] + ' 共' + total + '条' + // }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, + // 合计逻辑 [待优化 3.0] + showTotal: (total) => onShowTotal(total), + realPageSize: 10, + realTotal: 0, + // 是否有合计列,默认为"",在第一次获取到数据之后会设计为ture或者false + isTotal: '', + onShowSizeChange: (current, pageSize) => onSizeChange(current, pageSize) + }); + + /** + * 表格选择事件 + * @param selectedRowKeys + * @param selectRow + */ + function onSelectChange(selectedRowKeys: (string | number)[]) { + if (!selectedRowKeys || selectedRowKeys.length == 0) { + selectRows.value = [] + } else { + for (let i = 0; i < selectedRowKeys.length; i++) { + let combineKey = combineRowKey(getRowByKey(selectedRowKeys[i])); + let keys = unref(checkedKeys); + if (combineKey && keys.indexOf(combineKey) < 0) { + let row = getRowByKey(selectedRowKeys[i]); + row && selectRows.value.push(row) + } + } + } + checkedKeys.value = selectedRowKeys; + } + + /** + * 过滤没用选项 + * @param selectedRowKeys + */ + function filterUnuseSelect() { + selectRows.value = unref(selectRows).filter(item=>{ + let combineKey = combineRowKey(item); + return unref(checkedKeys).indexOf(combineKey)>=0 + }) + } + + /** + * 根据key获取row信息 + * @param key + */ + function getRowByKey(key) { + let row = unref(dataSource).filter(record => combineRowKey(record) === key); + return row&&row.length>0?row[0]:''; + } + + /** + * 加载rowKey + */ + function combineRowKey(record) { + let res = record?.id || ''; + Object.keys(record).forEach(key => { + res = (key =='rowIndex') ? (record[key] + res) : (res + record[key]); + }); + res = res.length > 50 ? res.substring(0, 50) : res; + return res + } + + /** + * 加载列信息 + */ + function loadColumnsInfo() { + let url = `${configUrl.getColumns}${props.code}`; + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({url}, {isTransformResponse: false, successMessageMode: 'none'}), groupIdKey).then(res => { + if (res.success) { + initDictOptionData(res.result.dictOptions); + cgRpConfigId.value = res.result.cgRpConfigId; + title.value = res.result.cgRpConfigName; + let currColumns = res.result.columns; + for (let a = 0; a < currColumns.length; a++) { + if (currColumns[a].customRender) { + let dictCode = currColumns[a].customRender; + currColumns[a].customRender = ({text}) => { + return filterMultiDictText(unref(dictOptions)[dictCode], text + ""); + } + } + // 排序字段受控 + if (unref(iSorter) && currColumns[a].dataIndex === unref(iSorter).column) { + currColumns[a].sortOrder = unref(iSorter).order === 'asc' ? 'ascend' : 'descend' + } + } + if (currColumns[0].key !== 'rowIndex') { + currColumns.unshift({ + title: '序号', + dataIndex: 'rowIndex', + key:'rowIndex', + width:60, + align:"center", + customRender:function ({text}) { + return parseInt(text)+1; + } + }); + } + columns.value = [...currColumns]; + initQueryInfo(null) + } + }) + } + + + /** + * 加载列和数据[列表专用] + */ + function loadColumnsAndData() { + // 第一次加载 置空isTotal 在这里调用确保 该方法只是进入页面后 加载一次 其余查询不走该方法 + pagination.isTotal = '' + let url = `${configUrl.getColumnsAndData}${props.id}`; + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({url}, {isTransformResponse: false, successMessageMode: 'none'}), groupIdKey).then(res => { + if (res.success) { + initDictOptionData(res.result.dictOptions); + cgRpConfigId.value = props.id; + let { columns: metaColumnList, cgreportHeadName, fieldHrefSlots, isGroupTitle } = res.result; + title.value = cgreportHeadName; + // href 跳转 + const fieldHrefSlotKeysMap = {} + fieldHrefSlots.forEach(item => fieldHrefSlotKeysMap[item.slotName] = item) + let currColumns = handleColumnHrefAndDict(metaColumnList, fieldHrefSlotKeysMap) + + // popup需要序号, 普通列表不需要 + if(clickThenCheckFlag===true){ + currColumns.unshift({ + title: '序号', + dataIndex: 'rowIndex', + key:'rowIndex', + width:60, + align:"center", + customRender:function ({text}) { + return parseInt(text)+1; + } + }); + } + + // 合并表头 + if(isGroupTitle === true){ + currColumns = handleGroupTitle(currColumns); + } + columns.value = [...currColumns]; + initQueryInfo(res.result.data) + }else{ + //update-begin-author:taoyan date:20220401 for: VUEN-583【vue3】JeecgBootException: sql黑名单校验不通过,请联系管理员!,前台无提示 + createMessage.warning(res.message) + //update-end-author:taoyan date:20220401 for: VUEN-583【vue3】JeecgBootException: sql黑名单校验不通过,请联系管理员!,前台无提示 + } + }) + } + + /** + * 处理求和的列 合计逻辑 [待优化 3.0] + */ + function handleSumColumn(metaColumnList: OnlineColumn[], dataTotal: number):void { + // 获取需要合计列的dataIndex + let sumColumnList = getNeedSumColumns(metaColumnList); + // 判断是否为第一次获取数据,如果是的话,则需要重新设置pageSize + if (pagination.isTotal == '') { + if (sumColumnList.length > 0) { + pagination.isTotal = true + // 有合计字段时,每次最多查询原pageSize-1条记录,另外需要第一次时将查询的10条中删除最后一条 + // 删除最后一条数据 如果第一次得到的数据长度等于pageSize的话,则删除最后一条 + if (dataSource.value.length == pagination.pageSize) { + let remove_data = dataSource.value.pop() + } + pagination.realPageSize = pagination.pageSize - 1 + } else { + pagination.isTotal = false + } + } + // 需要添加合计字段 + if (pagination.isTotal) { + let totalRow = { } + sumColumnList.forEach(dataIndex => { + let count = 0 + dataSource.value.forEach(row => { + // 统计去除null及空数据 + if(row[dataIndex] != null && row[dataIndex] != ''){ + count += parseFloat(row[dataIndex]) + } + }) + totalRow[dataIndex] = isNaN(count) ? '包含非数字内容' : count.toFixed(2) + + // 长整形时合计不显示.00后缀 + let v = metaColumnList.find(v=>v.dataIndex==dataIndex); + if(v && v.fieldType == 'Long'){ + totalRow[dataIndex] = parseInt(totalRow[dataIndex]); + } + }) + dataSource.value.push(totalRow) + pagination.realTotal = dataTotal + pagination.total = Number(dataTotal) + Number(Math.floor(dataTotal/pagination.realPageSize)) + } + } + + /** + * 获取需要求和的列 dataIndex + * @param columns + */ + function getNeedSumColumns(columns: OnlineColumn[]):string[]{ + let arr:string[] = [] + for(let column of columns){ + if (column.isTotal === '1') { + arr.push(column.dataIndex!) + if(column.children && column.children.length>0){ + let subArray = getNeedSumColumns(column.children) + if(subArray.length>0){ + arr.push(...subArray) + } + } + } + } + return arr; + } + + /** + * 处理列的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'){ + column.customRender = ({text}) => { + if(!text){ + return '' + } + if(text.length>10){ + return text.substring(0, 10) + } + return text; + } + }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_' + column.customRender = ({text, record}) => { + let value = text + // 如果 dictCode 有值,就进行字典转换 + if (dictCode) { + if (dictCode.startsWith(replaceFlag)) { + let textFieldName = dictCode.replace(replaceFlag, '') + value = record[textFieldName] + } else { + value = filterMultiDictText(unref(dictOptions)[dictCode], text + ""); + } + } + // 扩展参数设置列的内容长度 + 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) + }, value) + } + } + return value + } + } + } + } + return columns; + } + + /** + * 处理合并表头 + * @param columns + */ + function handleGroupTitle(columns: OnlineColumn[]):OnlineColumn[]{ + let newColumns:OnlineColumn[] = [] + for(let column of columns){ + //排序字段受控 ---- 此逻辑为新增逻辑 待 + if (unref(iSorter) && column.dataIndex === unref(iSorter).column) { + column.sortOrder = unref(iSorter).order === 'asc' ? 'ascend' : 'descend' + } + //判断字段是否需要合并表头 + if (column.groupTitle) { + let clIndex = newColumns.findIndex(im => im.title === column.groupTitle) + if (clIndex !== -1) { + //表头已存在直接push children + newColumns[clIndex].children!.push(column) + } else { + //表头不存在组装表头信息 + let clGroup:OnlineColumn = {},child:OnlineColumn[] = [] + child.push(column) + clGroup.title = column.groupTitle + clGroup.align = 'center' + clGroup.children = child + newColumns.push(clGroup) + } + } else { + newColumns.push(column) + } + } + return newColumns; + } + + // 获取路由器对象 href跳转用到 + let router = useRouter(); + /** + * 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') { + href = href.trim().replace(/\${([^}]+)?}/g, (s1, s2) => record[s2]) + // 执行 {{...}} JS增强语句 + if (jsPattern.test(href)) { + href = href.replace(jsPattern, function (text, s0) { + try { + return eval(s0) + } 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) + } + } + } + + /** + * 导出 + */ + function handleExport() { + const {handleExportXls} = useMethods(); + let url = `${configUrl.export}${cgRpConfigId.value}`; + let params = getQueryParams();//查询条件 + handleExportXls(title.value , url, params) + } + + + /** + * 合计逻辑 [待优化 3.0] + * 分页 大小改变事件 + * @param _current + * @param size + */ + function onSizeChange(_current, size){ + pagination.isTotal = ''; + pagination.pageSize = size; + if(pagination.isTotal){ + pagination.realPageSize = size - 1; + }else{ + pagination.realPageSize = size + } + pagination.current = 1; + } + + /** + * 合计逻辑 [待优化 3.0] + * 显示总条数 + * @param total + */ + function onShowTotal(total) { + // 重新根据是否有合计计算每页显示的数据 + let start = (pagination.current - 1) * pagination.realPageSize + 1 + let end = start + (pagination.isTotal ? dataSource.value.length - 1 : dataSource.value.length) - 1 + let realTotal = pagination.isTotal ? pagination.realTotal : total + return start + '-' + end + ' 共' + realTotal + '条' + } + + /** + * 弹出框显示隐藏触发事件 + */ + async function visibleChange($event) { + visible.value = $event; + $event && loadColumnsInfo(); + } + + /** + * 初始化查询条件 + * @param data 数据结果集 + */ + function initQueryInfo(data) { + let url = `${configUrl.getQueryInfo}${unref(cgRpConfigId)}` + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({url}, {isTransformResponse: false, successMessageMode: 'none'}), groupIdKey).then((res) => { + // console.log("获取查询条件", res); + if (res.success) { + dynamicParamHandler(res.result); + queryInfo.value = res.result; + console.log("queryInfo==>",queryInfo.value) + //查询条件加载后再请求数据 + if(data){ + setDataSource(data) + }else{ + //没有传递data时查询数据 + loadData(1); + } + + } else { + createMessage.warning(res.message) + } + }) + } + + /** + * 加载表格数据 + * @param arg + */ + function loadData(arg?) { + if (arg == 1) { + pagination.current = 1 + } + let params = getQueryParams();//查询条件 + loading.value = true; + let url = `${configUrl.getData}${unref(cgRpConfigId)}` + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}${JSON.stringify(params)}` : ''; + httpGroupRequest(() => defHttp.get({url, params}, {isTransformResponse: false, successMessageMode: 'none'}), groupIdKey).then(res => { + loading.value = false; + let data = res.result; + console.log("表格信息:", data) + setDataSource(data); + }) + } + + /** + * 设置dataSource + */ + function setDataSource(data) { + if (data) { + pagination.total = Number(data.total) + let currentPage = pagination?.current??1; + for (let a = 0; a < data.records.length; a++) { + if (!data.records[a].rowIndex) { + data.records[a].rowIndex = a+(currentPage-1)*10; + } + } + dataSource.value = data.records + } else { + pagination.total = 0; + dataSource.value = []; + } + // 合计逻辑 [待优化 3.0] + handleSumColumn(columns.value, pagination.total) + } + + /** + * 获取查询参数 + */ + function getQueryParams() { + let paramTarget = {}; + if (unref(dynamicParam)) { + //处理自定义参数 + Object.keys(unref(dynamicParam)).map(key => { + paramTarget['self_' + key] = unref(dynamicParam)[key] + }) + } + let param = Object.assign(paramTarget, unref(queryParam), unref(iSorter)); + param.pageNo = pagination.current; + // 合计逻辑 [待优化 3.0] + // 实际查询时不使用table组件的pageSize,而使用自定义的realPageSize,realPageSize会在第一次获取到数据后变化 + param.pageSize = pagination.realPageSize; + return filterObj(param); + } + + /** + * 处理动态参数 + */ + function dynamicParamHandler(arr?) { + if (arr && arr.length > 0) { + //第一次加载查询条件前 初始化queryParam为空对象 + let queryTemp = {}; + for (let item of arr) { + if (item.mode === 'single') { + queryTemp[item.field] = '' + } + } + queryParam.value = {...queryTemp} + } + let dynamicTemp = {}; + if (props.param) { + Object.keys(props.param).map(key => { + let str = props.param[key]; + if (key in queryParam) { + if (str && str.startsWith("'") && str.endsWith("'")) { + str = str.substring(1, str.length - 1) + } + //如果查询条件包含参数 设置值 + unref(queryParam)[key] = str + } + dynamicTemp[key] = props.param[key] + }) + } + dynamicParam.value = {...dynamicTemp} + } + + /** + * 分页 + * @param page + * @param filters + * @param sorter + */ + function handleChangeInTable(page, filters, sorter) { + console.log(page, filters, sorter); + //分页、排序、筛选变化时触发 + if (Object.keys(sorter).length > 0) { + iSorter.value = { + column: sorter.field, + order: 'ascend' === sorter.order ? 'asc' : 'desc' + } + // 排序字段受控 + unref(columns).forEach(col => { + if (col['dataIndex'] === sorter.field) { + col['sortOrder'] = sorter.order + } + }) + } + pagination.current = page.current; + pagination.pageSize = page.pageSize; + loadData() + } + + /** + * 行点击事件 + * @param record + */ + function clickThenCheck(record) { + if(clickThenCheckFlag===true){ + let rowKey = combineRowKey(record); + if (!unref(checkedKeys) || unref(checkedKeys).length == 0) { + let arr1: any[] = [], arr2: any[] = []; + arr1.push(record); + arr2.push(rowKey); + checkedKeys.value = arr2; + selectRows.value = arr1 + } else { + if (unref(checkedKeys).indexOf(rowKey) < 0) { + //不存在就选中 + checkedKeys.value.push(rowKey); + selectRows.value.push(record) + } else { + //已选中就取消 + let rowKey_index = unref(checkedKeys).indexOf(rowKey); + checkedKeys.value.splice(rowKey_index, 1); + selectRows.value.splice(rowKey_index, 1); + } + } + } + } + + //防止字典中有垃圾数据 + function initDictOptionData(arr) { + let obj = {}; + Object.keys(arr).map(k => { + obj[k] = arr[k].filter(item => { + return item != null + }); + }); + dictOptions.value = obj + } + + /** + * 过滤对象中为空的属性 + * @param obj + * @returns {*} + */ + function filterObj(obj) { + if (!(typeof obj == 'object')) { + return; + } + + for (let key in obj) { + if (obj.hasOwnProperty(key) + && (obj[key] == null || obj[key] == undefined || obj[key] === '')) { + delete obj[key]; + } + } + return obj; + } + + // 样式 + const dialogStyle = { + top: 0, + left: 0, + height: '100%', + margin: 0, + padding: 0, + } + + // 弹窗属性配置 + const hrefComponent = ref({ + model: { + title: '', + okText: '关闭', + width: '100%', + visible: 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.value.model.visible = false, + cancel: () => hrefComponent.value.model.visible = false, + }, + is: null, + params: {}, + }); + + // 超链点击事件--> 打开一个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.value.params = params + } else { + hrefComponent.value.params = {} + } + hrefComponent.value.model.visible = true + hrefComponent.value.model.title = '操作' + hrefComponent.value.is = defineAsyncComponent(() => import(/* @vite-ignore */'/@/views/' + (path.startsWith('/') ? path.slice(1) : path))) + } + + return [{visibleChange, loadColumnsInfo,loadColumnsAndData, dynamicParamHandler, loadData, handleChangeInTable, combineRowKey, clickThenCheck,filterUnuseSelect, handleExport}, + {hrefComponent, visible, rowSelection, checkedKeys, selectRows, pagination, dataSource, columns, indexColumnProps, loading, title,iSorter,queryInfo,queryParam,dictOptions}]; +} diff --git a/src/components/jeecg/OnLine/types/onlineConfig.ts b/src/components/jeecg/OnLine/types/onlineConfig.ts new file mode 100644 index 0000000..9517445 --- /dev/null +++ b/src/components/jeecg/OnLine/types/onlineConfig.ts @@ -0,0 +1,40 @@ +interface ScopedSlots{ + customRender: string +} + +interface HrefSlots{ + // 链接地址 + href: string + // fieldHref_字段名 + slotName: string +} + +interface OnlineColumn { + dataIndex?: string; + title?: string; + key?: string; + fieldType?: string; + width?: number | string; + align?: string; + sorter?: string | boolean; + isTotal?: string | number | boolean; + groupTitle?: string; + // 超链的时候 和HrefSlots中的slotName匹配 + scopedSlots? : ScopedSlots; + // 一般用于字典 字典传过来的是字典编码字符串 后转函数 + customRender?: string | Function; + // 这个类型不知道有什么用 + hrefSlotName?: string; + showLength?: number | string; + children?: OnlineColumn[]; + sortOrder?: string; + // 插槽对应控件类型(列表) + slots?:ScopedSlots, + //超过宽度将自动省略,暂不支持和排序筛选一起使用。 + ellipsis?: boolean +} + +export{ + OnlineColumn, + HrefSlots +} diff --git a/src/main.ts b/src/main.ts index 961fba3..8ee5f84 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,8 @@ import {setupI18n} from '/@/locales/setupI18n'; import {registerGlobComp} from '/@/components/registerGlobComp'; import {registerThirdComp} from '/@/settings/registerThirdComp'; import {useSso} from '/@/hooks/web/useSso'; +import {registerPackages} from '/@/utils/monorepo/registerPackages'; + // 在本地开发中引入的,以提高浏览器响应速度 if (import.meta.env.DEV) { import('ant-design-vue/dist/antd.less'); @@ -35,6 +37,9 @@ async function bootstrap() { // 初始化内部系统配置 initAppConfigStore(); + // 注册外部模块路由 + registerPackages(app); + // 注册全局组件 registerGlobComp(app); diff --git a/src/router/helper/routeHelper.ts b/src/router/helper/routeHelper.ts index 8e55b96..1ab989b 100644 --- a/src/router/helper/routeHelper.ts +++ b/src/router/helper/routeHelper.ts @@ -7,6 +7,7 @@ import { warn } from '/@/utils/log'; import { createRouter, createWebHashHistory } from 'vue-router'; import { getToken } from '/@/utils/auth'; import {URL_HASH_TAB} from '/@/utils' +import { packageViews } from '/@/utils/monorepo/dynamicRouter'; export type LayoutMapKey = 'LAYOUT'; const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue'); @@ -23,7 +24,11 @@ let dynamicViewsModules: Record Promise>; // Dynamic introduction function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { - dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); + if (!dynamicViewsModules) { + dynamicViewsModules = import.meta.glob('../../views/**/*.{vue,tsx}'); + // 跟模块views合并 + dynamicViewsModules = Object.assign({}, dynamicViewsModules, packageViews); + } if (!routes) return; routes.forEach((item) => { // update-begin--author:sunjianlei---date:20210918---for:适配旧版路由选项 -------- diff --git a/src/utils/monorepo/dynamicRouter.ts b/src/utils/monorepo/dynamicRouter.ts new file mode 100644 index 0000000..d734333 --- /dev/null +++ b/src/utils/monorepo/dynamicRouter.ts @@ -0,0 +1,19 @@ +export type DynamicViewsRecord = Record Promise> + +/** 已注册模块的动态页面 */ +export const packageViews: DynamicViewsRecord = {}; + +/** + * 注册动态路由页面 + * @param getViews 获取该模块下所有页面的方法 + */ +export function registerDynamicRouter(getViews: () => DynamicViewsRecord) { + if (typeof getViews === 'function') { + let dynamicViews = getViews(); + Object.keys(dynamicViews).forEach((key) => { + // 处理动态页面的key,使其可以让路由识别 + let newKey = key.replace('./src/views', '../../views'); + packageViews[newKey] = dynamicViews[key]; + }); + } +} diff --git a/src/utils/monorepo/registerPackages.ts b/src/utils/monorepo/registerPackages.ts new file mode 100644 index 0000000..91ab6a2 --- /dev/null +++ b/src/utils/monorepo/registerPackages.ts @@ -0,0 +1,50 @@ +import type { App } from 'vue'; +import { warn } from '/@/utils/log'; +import { registerDynamicRouter } from '/@/utils/monorepo/dynamicRouter'; +// 引入模块 +import PACKAGE_TEST_JEECG_ONLINE from '@jeecg/online'; + +export function registerPackages(app: App) { + use(app, PACKAGE_TEST_JEECG_ONLINE); +} + +// noinspection JSUnusedGlobalSymbols +const installOptions = { + baseImport, +}; + +/** 注册模块 */ +function use(app: App, pkg) { + app.use(pkg, installOptions); + registerDynamicRouter(pkg.getViews); +} + +// 模块里可使用的import +const importGlobs = [ + import.meta.glob('../../utils/**/*.{ts,js,tsx}'), + import.meta.glob('../../hooks/**/*.{ts,js,tsx}'), +]; + +/** + * 基础项目导包 + * 目前支持导入如下 + * /@/utils/** + * /@/hooks/** + * + * @param path 文件路径,ts无需输入后缀名。如:/@/utils/common/compUtils + */ +async function baseImport(path: string) { + if (path) { + // 将 /@/ 替换成 ../../ + path = path.replace(/^\/@\//, '../../'); + for (const glob of importGlobs) { + for (const key of Object.keys(glob)) { + if (path === key || `${path}.ts` === key || `${path}.tsx` === key) { + return glob[key](); + } + } + } + warn(`引入失败:${path} 不存在`); + } + return null; +} diff --git a/vite.config.ts b/vite.config.ts index 55c24ae..64f220e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -62,6 +62,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { }, build: { target: 'es2015', + cssTarget: 'chrome80', outDir: OUTPUT_DIR, terserOptions: { compress: { @@ -93,6 +94,9 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { plugins: createVitePlugins(viteEnv, isBuild), optimizeDeps: { + esbuildOptions: { + target: 'es2020', + }, // @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly include: [ '@iconify/iconify',