v3.9.2 前端开放online源码

This commit is contained in:
JEECG 2026-04-28 15:25:59 +08:00
parent 4e03c2c80a
commit 11b1ab81d7
212 changed files with 45501 additions and 0 deletions

View File

@ -0,0 +1,125 @@
<template>
<div :class="prefixCls">
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button @click="onShowCustomButton" type="primary" preIcon="ant-design:highlight">自定义按钮</a-button>
<a-button @click="onShowEnhanceJs" type="primary" preIcon="ant-design:strikethrough">JS增强</a-button>
<a-button @click="onShowEnhanceSql" type="primary" preIcon="ant-design:filter">SQL增强</a-button>
<a-button @click="onShowEnhanceJava" type="primary" preIcon="ant-design:tool">Java增强</a-button>
</template>
<template #dbSync="{ text }">
<span v-if="text === 'Y'" style="color: limegreen">已同步</span>
<span v-if="text === 'N'" style="color: red">未同步</span>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
</div>
<CgformModal @register="registerCgformModal" :actionButton="false" @success="reload" />
<EnhanceJsModal @register="registerEnhanceJsModal" />
<EnhanceJavaModal @register="registerEnhanceJavaModal" />
<EnhanceSqlModal @register="registerEnhanceSqlModal" />
<DbToOnlineModal @register="registerDbToOnlineModal" @success="reload" />
<CustomButtonList @register="registerCustomButtonModal" />
<AuthManagerDrawer @register="registerAuthManagerDrawer" />
<AuthSetterModal @register="registerAuthSetterModal" />
<CgformAddressModal @register="registerAddressModal" />
</template>
<script lang="ts">
import { watch, provide, defineComponent } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import CgformModal from './components/CgformModal.vue';
import DbToOnlineModal from './components/DbToOnlineModal.vue';
import CustomButtonList from './components/button/CustomButtonList.vue';
import EnhanceJsModal from './components/enhance/EnhanceJsModal.vue';
import EnhanceJavaModal from './components/enhance/EnhanceJavaModal.vue';
import EnhanceSqlModal from './components/enhance/EnhanceSqlModal.vue';
import AuthManagerDrawer from './components/auth/AuthManagerDrawer.vue';
import AuthSetterModal from './components/auth/AuthSetterModal.vue';
import CgformAddressModal from "./components/CgformAddressModal.vue";
import { useCgformList } from './hooks/useCgformList';
import { CgformPageType } from './types';
// noinspection JSUnusedGlobalSymbols
export default defineComponent({
name: 'CgformCopyList',
components: {
BasicTable,
TableAction,
CgformModal,
DbToOnlineModal,
CustomButtonList,
EnhanceJsModal,
EnhanceJavaModal,
EnhanceSqlModal,
AuthManagerDrawer,
AuthSetterModal,
CgformAddressModal,
},
setup() {
const pageType = CgformPageType.copy;
provide('cgformPageType', pageType);
const {
router,
pageContext,
getTableAction,
getDropDownAction,
onShowCustomButton,
onShowEnhanceJs,
onShowEnhanceSql,
onShowEnhanceJava,
registerCustomButtonModal,
registerEnhanceJsModal,
registerEnhanceSqlModal,
registerEnhanceJavaModal,
registerAuthManagerDrawer,
registerAuthSetterModal,
registerCgformModal,
registerDbToOnlineModal,
registerAddressModal,
} = useCgformList({
pageType,
designScope: 'online-cgform-list',
columns: [
{ title: '视图表名', dataIndex: 'tableName' },
{ title: '视图表描述', dataIndex: 'tableTxt' },
{ title: '原表版本', dataIndex: 'copyVersion' },
{ title: '视图版本', dataIndex: 'tableVersion' },
],
formSchemas: [{ label: '表名', field: 'tableName', component: 'JInput' }],
});
const { prefixCls, tableContext } = pageContext;
const [registerTable, { reload }, { rowSelection }] = tableContext;
watch(router.currentRoute, () => reload());
return {
prefixCls,
reload,
rowSelection,
getTableAction,
getDropDownAction,
onShowCustomButton,
onShowEnhanceJs,
onShowEnhanceSql,
onShowEnhanceJava,
registerCustomButtonModal,
registerEnhanceJsModal,
registerEnhanceSqlModal,
registerEnhanceJavaModal,
registerAuthManagerDrawer,
registerAuthSetterModal,
registerTable,
registerCgformModal,
registerDbToOnlineModal,
registerAddressModal,
};
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,168 @@
<!-- online查询条件中的下拉搜索 -->
<template>
<a-select
:value="selected"
:placeholder="placeholder"
show-search
:default-active-first-option="false"
:show-arrow="true"
:filter-option="false"
:not-found-content="null"
@search="handleSearch"
@change="handleChange"
@popupScroll="handlePopupScroll"
allowClear
>
<a-select-option v-for="d in selectOptions" :key="d.value">
{{ d.text }}
</a-select-option>
</a-select>
</template>
<script>
import { useDebounceFn } from '@vueuse/core';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage: $message } = useMessage();
import { watch, ref } from 'vue';
export default {
name: 'JOnlineSearchSelect',
props: {
placeholder: {
type: String,
default: '',
required: false,
},
value: {
type: String,
required: false,
},
// online CgReport item id
fieldId: {
type: String,
required: true,
},
},
emits: ['update:value'],
setup(props, { emit }) {
let selected = ref('');
let selectOptions = ref([]);
let isHasData = true;
let scrollLoading = false;
let searchKeyword = '';
const pageNo = ref(1);
watch(
() => props.value,
(newVal) => {
if (!newVal) {
selected.value = undefined;
} else {
selected.value = newVal;
}
},
{ immediate: true }
);
watch(
() => props.fieldId,
() => {
resetOptions();
},
{ immediate: true }
);
/**
* 2024-07-17
* liaozhiyang
* TV360X-1813online报表查询支持滚动加载
* */
const handleSearch = useDebounceFn((keyword) => {
searchKeyword = keyword;
pageNo.value = 1;
isHasData = true;
searchByKeyword(keyword);
}, 800);
/**
* 2024-07-17
* liaozhiyang
* TV360X-1813online报表查询支持滚动加载
* */
async function searchByKeyword(keyword = '') {
let params = {
keyword: keyword,
fieldId: props.fieldId,
pageSize: 10,
pageNo: pageNo.value,
};
let url = `/online/cgreport/api/getReportDictList`;
await defHttp
.get({ url: url, params }, { isTransformResponse: false })
.then((res) => {
if (res.success) {
if (res.result && res.result.length > 0) {
if (pageNo.value == 1) {
selectOptions.value = [...res.result];
} else {
selectOptions.value.push(...res.result);
}
pageNo.value++;
} else {
if (pageNo.value == 1) {
selectOptions.value = [];
}
isHasData = false;
}
} else {
$message.warning(res.message);
}
})
.catch(() => {
pageNo.value != 1 && pageNo.value--;
});
}
function handleChange(value) {
emit('update:value', value);
//clear,
if (!value || value == '') {
resetOptions();
}
}
function resetOptions() {
selectOptions.value = [];
// update-begin--author:liaozhiyang---date:20240717---forTV360X-1813online
pageNo.value = 1;
isHasData = true;
searchKeyword = '';
// update-end--author:liaozhiyang---date:20240717---forTV360X-1813online
searchByKeyword();
}
/**
* 2024-07-17
* liaozhiyang
* TV360X-1813online报表查询支持滚动加载
* */
const handlePopupScroll = async (e) => {
const { target } = e;
const { scrollTop, scrollHeight, clientHeight } = target;
if (!scrollLoading && isHasData && scrollTop + clientHeight >= scrollHeight - 10) {
scrollLoading = true;
searchByKeyword(searchKeyword).finally(() => {
scrollLoading = false;
});
}
};
return {
selectOptions,
handleSearch,
handleChange,
selected,
handlePopupScroll,
};
},
};
</script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,353 @@
<template>
<div :id="tableName + '_form'">
<!-- 积木报表的打印按钮只有配置了 reportUrl 才显示 -->
<div v-if="!!formData.id && !!onlineExtConfigJson.reportPrintShow" style="text-align: right;position: absolute;top: 15px;right: 20px;z-index: 999">
<PrinterOutlined title="打印" @click="onOpenReportPrint" style="font-size: 16px"/>
</div>
<detail-form :schemas="detailFormSchemas" :data="formData" :span="formSpan"></detail-form>
<!-- 子表 -->
<a-tabs v-if="themeTemplate !== ERP && hasSubTable && showSub" @change="onTabChange">
<a-tab-pane v-for="(sub, index) in subTabInfo" :tab="sub.describe" :key="index + ''" :forceRender="true">
<div :style="{ 'overflow-y': 'auto', 'overflow-x': 'hidden', 'max-height': subFormHeight + 'px' }" v-if="sub.relationType == 1">
<!-- 子表-一对一 -->
<online-sub-form-detail :key="subReloadKey" :table="sub.key" :form-template="formTemplate" :main-id="getSubTableForeignKeyValue(sub.foreignKey)" :properties="sub.properties"> </online-sub-form-detail>
</div>
<div v-else>
<!-- 子表-一对多 -->
<JVxeTable
v-if="showStatus[sub.key]"
:ref="refMap[sub.key]"
keep-source
:row-number="rowNumber"
row-selection
:height="subTableHeight"
:disabled="true"
:columns="sub.columns"
:dataSource="subDataSource[sub.key]"
:authPre="getSubTableAuthPre(sub.key)"
/>
<a-spin v-else :spinning="true"/>
</div>
</a-tab-pane>
</a-tabs>
<Loading :loading="loading" :absolute="false" />
<slot name="bottom"></slot>
</div>
</template>
<script lang="ts">
import { useMessage } from '/@/hooks/web/useMessage';
import { ref, reactive, watch } from 'vue';
import { Loading } from '/@/components/Loading';
import { getToken } from '/@/utils/auth';
import { goJmReportViewPage } from '/@/utils';
import { PrinterOutlined } from '@ant-design/icons-vue';
import DetailForm from '../../extend/form/DetailForm.vue';
import OnlineSubFormDetail from './OnlineSubFormDetail.vue';
import { getDetailFormSchemas } from '../../hooks/auto/useAutoForm';
import { defHttp } from '/@/utils/http/axios';
import { ERP } from "../../util/constant";
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useOnlineFormDetailContext } from '../../hooks/auto/useAutoFormDetail';
import { useEnhance } from '../../hooks/auto/useEnhance';
export default {
name: 'OnlineFormDetail',
components: {
DetailForm,
Loading,
PrinterOutlined,
OnlineSubFormDetail,
},
props: {
id: {
type: String,
default: '',
},
formTemplate: {
type: Number,
default: 1,
},
disabled: {
type: Boolean,
default: false,
},
isTree: {
type: Boolean,
default: false,
},
pidField: {
type: String,
default: '',
},
submitTip: {
type: Boolean,
default: true,
},
showSub:{
type: Boolean,
default: true,
},
themeTemplate: {
type: String,
default: '',
}
},
emits: ['success', 'rendered'],
setup(props, { emit }) {
console.log('onlineForm-setup》》');
const { createMessage: $message } = useMessage();
const { getIsMobile } = useAppInject();
const tableName = ref('');
const single = ref(true);
//
const loading = ref(false);
const tableType = ref(1);
const formData = ref<any>({});
// update-begin-author:liaozhiyang---date:20240313---forQQYUN-9034online
const subFormHeight = ref(getIsMobile.value ? 'auto' : 300);
// update-end-author:liaozhiyang---date:20240313---forQQYUN-9034online
const subReloadKey = ref(0);
//
// VUEN-803340
const subTableHeight = ref(340);
const rowNumber = ref(getIsMobile.value ? false : true);
let detailData = {};
//
const fieldDisplayStatus = reactive<any>({});
/**
* online表单扩展配置
*/
const onlineExtConfigJson = reactive({
reportPrintShow: 0,
reportPrintUrl: '',
joinQuery: 0,
modelFullscreen: 0,
modalMinWidth: '',
});
const { detailFormSchemas, hasSubTable, subTabInfo, refMap, showStatus, subDataSource, createFormSchemas, formSpan } = getDetailFormSchemas(props);
/**
* 处理扩展配置
*/
function handleExtConfigJson(jsonStr) {
let extConfigJson = { reportPrintShow: 0, reportPrintUrl: '', joinQuery: 0, modelFullscreen: 1, modalMinWidth: '' };
if (jsonStr) {
extConfigJson = JSON.parse(jsonStr);
}
Object.keys(extConfigJson).map((k) => {
onlineExtConfigJson[k] = extConfigJson[k];
});
}
// update-begin--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
const { onlineFormDetailContext, resetContext } = useOnlineFormDetailContext();
let { EnhanceJS, initCgEnhanceJs } = useEnhance(onlineFormDetailContext, false);
// update-end--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
//
async function createRootProperties(data) {
tableType.value = data.head.tableType;
tableName.value = data.head.tableName;
single.value = data.head.tableType == 1;
handleExtConfigJson(data.head.extConfigJson);
createFormSchemas(data.schema.properties);
// update-begin--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
EnhanceJS = initCgEnhanceJs(data.enhanceJs);
// update-end--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
emit('rendered', onlineExtConfigJson);
}
/**
* status: 是否是修改页面
* record: 列表页面的行数据
* param 树形列表添加子节点 传入的父级节点id
* */
async function show(_status, record) {
console.log('进入表单详情》》form', record);
// -update-begin--author:liaozhiyang---date:20251209---forQQYUN-13970
subReloadKey.value++;
// -update-end--author:liaozhiyang---date:20251209---forQQYUN-13970
await edit(record);
changeShowStatus(true);
}
function getFormData(dataId) {
let url = `/online/cgform/api/detail/${props.id}/${dataId}`;
return new Promise((resolve, reject) => {
defHttp
.get({ url }, { isTransformResponse: false })
.then((res) => {
if (res.success) {
resolve(res.result);
} else {
reject();
$message.warning(res.message);
}
})
.catch(() => {
reject();
});
});
}
//update-begin-author:taoyan date:2023-2-13 for: QQYUN-4226vue3online
function changeShowStatus(flag){
Object.keys(showStatus).map(k=>{
showStatus[k] = flag;
})
}
function onTabChange(){
changeShowStatus(false);
setTimeout(()=>{
changeShowStatus(true);
}, 300);
}
//update-end-author:taoyan date:2023-2-13 for: QQYUN-4226vue3online
async function edit(record) {
let temp: any = await getFormData(record.id);
// update-begin--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
detailData = temp;
// js
detailFormSchemas.value.filter((item) => item.hidden).forEach((item) => (item.hidden = false));
Object.keys(fieldDisplayStatus).forEach(function (key) {
delete fieldDisplayStatus[key];
});
handleEnhanceJS({ buttonCode: 'loaded' });
//
formData.value = { ...detailData };
editSubVxeTableData(detailData);
// update-end--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
}
function editSubVxeTableData(record) {
if (!record) {
//
record = {};
}
let keys = Object.keys(subDataSource.value);
if (keys && keys.length > 0) {
let obj = {};
for (let key of keys) {
obj[key] = record[key] || [];
}
subDataSource.value = obj;
}
}
function getSubTableAuthPre(table) {
return 'online_' + table + ':';
}
//
function onOpenReportPrint() {
let url = onlineExtConfigJson.reportPrintUrl;
let temp: any = formData.value;
if (temp) {
let id = temp.id;
let token = getToken();
goJmReportViewPage(url, id, token);
}
}
function getSubTableForeignKeyValue(key) {
let temp = formData.value;
console.log('getValueIgnoreCase(temp, key)', temp, key, getValueIgnoreCase(temp, key));
return getValueIgnoreCase(temp, key);
}
/**
* VUEN-1056 30生成的一对多编辑的时候子表数据挂不上
*/
function getValueIgnoreCase(data, key) {
if (data) {
let temp = data[key];
if (!temp && temp !== 0) {
temp = data[key.toLowerCase()];
if (!temp && temp !== 0) {
temp = data[key.toUpperCase()];
}
}
return temp;
}
return '';
}
// update-begin--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
function handleEnhanceJS({ buttonCode }) {
if (EnhanceJS && EnhanceJS[buttonCode]) {
EnhanceJS[buttonCode].call(onlineFormDetailContext, onlineFormDetailContext);
}
}
watch(fieldDisplayStatus, (newValue) => {
Object.entries(newValue).forEach(([key, value]) => {
if (value == false) {
const findItem = detailFormSchemas.value.find((item) => item.field === key);
if (findItem) {
findItem.hidden = true;
}
}
});
});
const context = {
setFieldsValue: (values) => {
Object.entries(values).forEach(([key, value]) => {
detailData[key] = value;
});
},
getFieldsValue: () => {
return { ...detailData };
},
sh: fieldDisplayStatus,
isUpdate: ref(false),
isDetail: ref(true),
};
resetContext(context);
// update-end--author:liaozhiyang---date:20240425---forissues/6139onlinejsloaded
return {
detailFormSchemas,
formData,
formSpan,
//
tableName,
loading,
//
hasSubTable,
subTabInfo,
subFormHeight,
subTableHeight,
refMap,
onTabChange,
subReloadKey,
//
subDataSource,
getSubTableAuthPre,
//
show,
createRootProperties,
//
onOpenReportPrint,
onlineExtConfigJson,
getSubTableForeignKeyValue,
showStatus,
ERP,
rowNumber,
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,911 @@
<template>
<div :id="tableName + '_form'" class="onlinePopFormWrap" :class="[`formTemplate_${formTemplate}`]">
<BasicForm ref="onlineFormRef" @register="registerForm" />
</div>
</template>
<script lang="ts">
import { useMessage } from '/@/hooks/web/useMessage';
import { computed, ref, unref, nextTick, toRaw, reactive } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { SUBMIT_FLOW_KEY, VALIDATE_FAILED } from '../../types/onlineRender';
import { defHttp } from '/@/utils/http/axios';
import { pick } from 'lodash-es';
import { useFormItems, getRefPromise, useOnlineFormContext } from '../../hooks/auto/useAutoForm';
import { Loading } from '/@/components/Loading';
import { useEnhance } from '../../hooks/auto/useEnhance';
import OnlineSubForm from './OnlineSubForm.vue';
import { loadFormFieldsDefVal } from '../../util/FieldDefVal';
import { getToken } from '/@/utils/auth';
import { goJmReportViewPage } from '/@/utils'
import { PrinterOutlined, DiffOutlined, FormOutlined } from '@ant-design/icons-vue';
import { useModal } from '/@/components/Modal';
import { Method } from 'axios';
import { isObject } from '/@/utils/is';
const urlObject = {
optPre: '/online/cgform/api/form/',
urlButtonAction: '/online/cgform/api/doButton',
};
export default {
name: 'OnlinePopForm',
components: {
BasicForm,
Loading,
OnlineSubForm,
PrinterOutlined,
DiffOutlined,
FormOutlined
},
props: {
id: {
type: String,
default: '',
},
formTemplate: {
type: Number,
default: 1,
},
disabled: {
type: Boolean,
default: false,
},
isTree: {
type: Boolean,
default: false,
},
pidField: {
type: String,
default: '',
},
submitTip:{
type: Boolean,
default: true
},
modalClass:{
type: String,
default: '',
},
//-/false
request: {
type: Boolean,
default: true
},
// vxeTable
isVxeTableData: {
type: Boolean,
default: false
}
},
emits: ['success', 'rendered', 'dataChange'],
setup(props, { emit }) {
console.log('onlineForm-setup》》');
const { createMessage: $message } = useMessage();
const [registerVxeFormModal, { openModal:openVxeFormModal }] = useModal();
const vxeTableId = ref('');
// ref
const onlineFormRef = ref(null);
const single = ref(true);
//
const loading = ref(false);
const tableType = ref(1);
//
const submitFlowFlag = ref(false);
const isUpdate = ref(false);
/**
* online表单扩展配置
*/
const onlineExtConfigJson = reactive({
reportPrintShow: 0,
reportPrintUrl: '',
joinQuery: 0,
modelFullscreen: 0,
modalMinWidth: '',
});
const { onlineFormContext, resetContext } = useOnlineFormContext();
const {
formSchemas,
defaultValueFields,
changeDataIfArray2String,
tableName,
dbData,
checkOnlyFieldValue,
hasSubTable,
subTabInfo,
refMap,
subDataSource,
baseColProps,
createFormSchemas,
fieldDisplayStatus,
labelCol,
wrapperCol,
labelWidth
} = useFormItems(props, onlineFormRef);
let { EnhanceJS, initCgEnhanceJs } = useEnhance(onlineFormContext, false);
//
const [registerForm, { setProps, validate, resetFields, setFieldsValue, updateSchema, getFieldsValue, scrollToField }] = useForm({
schemas: formSchemas,
showActionButtonGroup: false,
baseColProps: baseColProps,
// update-begin--author:liaozhiyang---date:20240329---forQQYUN-7872onlinelabel
labelWidth,
// update-end--author:liaozhiyang---date:20240329---forQQYUN-7872onlinelabel
// update-begin--author:liaozhiyang---date:20240105---forQQYUN-7499markdown
labelCol,
wrapperCol
// update-end--author:liaozhiyang---date:20240105---forQQYUN-7499markdown
});
//
const onlineFormDisabled = ref(false);
function handleFormDisabled() {
let flag = props.disabled;
onlineFormDisabled.value = flag;
setProps({ disabled: flag });
}
/**
* status: 是否是修改页面
* record: 列表页面的行数据
* param 树形列表添加子节点 传入的父级节点id
* */
async function show(status, record, param) {
console.log('onlinepopform新增编辑进入表单》》form', record);
await resetFields();
dbData.value = '';
let flag = unref(status);
isUpdate.value = flag;
if (flag) {
//
await edit(record);
}
await nextTick(() => {
if (!flag && param) {
// param
setFieldsValue(param);
}
handleDefaultValue();
// loaded
handleCgButtonClick('js', 'loaded');
//
handleFormDisabled();
});
}
/**
* 当前表单默认值逻辑-进入新增页面触发
*/
function handleDefaultValue() {
if (unref(isUpdate) === false) {
let fieldProperties = toRaw(defaultValueFields[tableName.value]);
loadFormFieldsDefVal(fieldProperties, (values) => {
setFieldsValue(values);
});
}
}
async function edit(record) {
//
let formData:any = await getFormData(record.id);
if(!formData || Object.keys(formData).length==0){
//
formData = {...toRaw(record)}
}
dbData.value = Object.assign({}, formData);
//
let arr = realFormFieldNames.value;
let values = pick(formData, ...arr);
// vxetablerecord,
if(props.isVxeTableData === true){
values = Object.assign({},values, record)
}
await setFieldsValue(values);
// editSubVxeTableData(formData);
}
function editSubVxeTableData(record) {
if (!record) {
//
record = {};
}
let keys = Object.keys(subDataSource.value);
if (keys && keys.length > 0) {
let obj = {};
for (let key of keys) {
obj[key] = record[key] || [];
}
subDataSource.value = obj;
}
}
let realFormFieldNames = computed(() => {
let arr = formSchemas.value;
let names = [];
for (let a of arr) {
names.push(a.field);
}
return names;
});
function getFormData(dataId) {
let url = `${urlObject.optPre}${props.id}/${dataId}`;
return new Promise((resolve, reject) => {
defHttp
.get({ url }, { isTransformResponse: false })
.then((res) => {
if (res.success) {
resolve(res.result);
} else {
reject();
$message.warning(res.message);
}
})
.catch(() => {
reject();
});
});
}
//
async function createRootProperties(data) {
tableType.value = data.head.tableType;
tableName.value = data.head.tableName;
single.value = data.head.tableType == 1;
handleExtConfigJson(data.head.extConfigJson);
createFormSchemas(data.schema.properties, data.schema.required, checkOnlyFieldValue, onlineExtConfigJson);
EnhanceJS = initCgEnhanceJs(data.enhanceJs);
emit('rendered', onlineExtConfigJson);
//
let formRefObject:any = await getRefPromise(onlineFormRef);
formRefObject.$formValueChange = (field, value, changeFormData) => {
onValuesChange(field, value);
if(changeFormData){
//
setFieldsValue(changeFormData)
}
};
}
/**
* 处理扩展配置
*/
function handleExtConfigJson(jsonStr) {
let extConfigJson = { reportPrintShow: 0, reportPrintUrl: '', joinQuery: 0, modelFullscreen: 1, modalMinWidth: '', formLabelLength: null };
if (jsonStr) {
extConfigJson = JSON.parse(jsonStr);
}
Object.keys(extConfigJson).map((k) => {
onlineExtConfigJson[k] = extConfigJson[k];
});
}
function handleSubmit() {
if (single.value === true) {
handleSingleSubmit();
} else {
handleOne2ManySubmit();
}
}
function handleOne2ManySubmit() {
validateAll().then((formData) => {
handleApplyRequest(formData);
});
}
//
function validateAll() {
let temp = {};
return new Promise((resolve, reject) => {
//
validate().then(
(values) => resolve(values),
({ errorFields }) => {
reject({
code: VALIDATE_FAILED,
key: tableName.value,
//
scrollToField: () => errorFields[0] && scrollToField(errorFields[0].name, { behavior: 'smooth', block: 'center' }),
});
}
);
})
.then((result) => {
Object.assign(temp, changeDataIfArray2String(result));
return validateSubTableFields();
})
.then((allTableData) => {
Object.assign(temp, allTableData);
return Promise.resolve(temp);
})
.catch((e) => {
if (e === VALIDATE_FAILED || e?.code === VALIDATE_FAILED) {
$message.warning('校验未通过');
if (e.key) {
changeTab(e.key);
if (e.scrollToField) {
setTimeout(() => e.scrollToField(), 150)
}
}
} else {
console.error(e);
}
return Promise.reject(null);
});
}
/**
* 切换tab到出现校验错误的页面
* */
function changeTab(key) {
let arr = subTabInfo.value;
for (let i = 0; i < arr.length; i++) {
if (key == arr[i].key) {
subActiveKey.value = i + '';
break;
}
}
}
//
function validateSubTableFields() {
return new Promise(async (resolve, reject) => {
let subData = {};
try {
let arr = subTabInfo.value;
for (let i = 0; i < arr.length; i++) {
let key = arr[i].key;
let instance = refMap[key].value;
// :
if (instance instanceof Array) {
instance = instance[0];
}
if (arr[i].relationType == 1) {
try {
let subFormData = await instance.getAll();
subData[key] = [];
subData[key].push(subFormData);
} catch (e) {
return reject({code: VALIDATE_FAILED, key, ...e});
}
} else {
let errMap = await instance.fullValidateTable();
if (errMap) {
return reject({code: VALIDATE_FAILED, key});
}
subData[key] = instance.getTableData();
}
}
} catch (e) {
reject(e);
}
resolve(subData);
});
}
/**
* 表单提交-单表
*/
async function handleSingleSubmit() {
try {
let values = await validate();
values = Object.assign({}, dbData.value, values);
values = changeDataIfArray2String(values);
loading.value = true;
handleApplyRequest(values);
} catch (error) {
// update-begin--author:liaozhiyang---date:20240524---forTV360X-420
if (isObject(error)) {
const errorFields = error.errorFields;
if (errorFields?.length && errorFields[0].errors) {
$message.warning(errorFields[0].errors[0]);
scrollToField(errorFields[0].name, { behavior: 'smooth', block: 'center' });
}
}
console.log(error);
// update-end--author:liaozhiyang---date:20240524---forTV360X-420
} finally {
loading.value = false;
}
}
// JS
function handleApplyRequest(formData) {
customBeforeSubmit(context, formData)
.then(() => {
doApplyRequest(formData);
})
.catch((msg) => {
$message.warning(msg);
});
}
function triggleChangeValues(values, id, target) {
if (id && target) {
if (target.setValues) {
//
target.setValues(values);
} else {
//
target.setValues([
{
rowKey: id,
values: values,
},
]);
}
} else {
//
setFieldsValue(values);
}
}
function triggleChangeValue(field, value) {
let obj = {};
obj[field] = value;
setFieldsValue(obj);
}
// TabKey
const subActiveKey = ref('0');
const subFormHeight = ref(300);
//
// VUEN-803340
const subTableHeight = ref(340);
function getSubTableForeignKeyValue(key) {
if (isUpdate.value === true) {
let formData = dbData.value;
return getValueIgnoreCase(formData, key);
}
return '';
}
/**
* VUEN-1056 30生成的一对多编辑的时候子表数据挂不上
*/
function getValueIgnoreCase(data, key) {
if (data) {
let temp = data[key];
if (!temp && temp !== 0) {
temp = data[key.toLowerCase()];
if (!temp && temp !== 0) {
temp = data[key.toUpperCase()];
}
}
return temp;
}
return '';
}
//
function handleSubFormChange(valueObj, tableKey) {
if (EnhanceJS && EnhanceJS[tableKey + '_onlChange']) {
let tableChangeObj = EnhanceJS[tableKey + '_onlChange']();
let columnKey = Object.keys(valueObj)[0];
if (tableChangeObj[columnKey]) {
let subRef = refMap[tableKey].value;
if (subRef instanceof Array) {
subRef = subRef[0];
}
let formEvent = subRef.getFormEvent();
let event = {
column: { key: columnKey },
value: valueObj[columnKey],
...formEvent,
};
tableChangeObj[columnKey].call(onlineFormContext, onlineFormContext, event);
}
}
}
//
function handleValueChange(event, tableKey) {
if (EnhanceJS && EnhanceJS[tableKey + '_onlChange']) {
let tableChangeObj = EnhanceJS[tableKey + '_onlChange'](onlineFormContext);
if (tableChangeObj[event.column.key]) {
tableChangeObj[event.column.key].call(onlineFormContext, onlineFormContext, event);
}
}
}
//
function handleAdded(sub, event) {
console.log('handleAdded', sub, event)
//update-begin-author:taoyan date:2022-6-26 for:
// event.target.emit('executeFillRule', event);
//update-end-author:taoyan date:2022-6-26 for:
}
function getSubTableAuthPre(table) {
return 'online_' + table + ':';
}
//
async function onValuesChange(columnKey, value) {
//console.log('columnKey-value', `${columnKey}-${value}`)
//
let oldFormData = dbData.value;
if(oldFormData[columnKey]!=value){
emit('dataChange', columnKey);
}
if (!EnhanceJS || !EnhanceJS['onlChange']) {
return false;
}
if (!columnKey) {
return false;
}
//let tableChangeObj = EnhanceJS["onlChange"].call(onlineFormContext);
let tableChangeObj = EnhanceJS['onlChange']();
if (tableChangeObj[columnKey]) {
let formData = await getFieldsValue();
let event = {
row: formData,
column: { key: columnKey },
value: value,
};
tableChangeObj[columnKey].call(onlineFormContext, onlineFormContext, event);
}
}
//
function handleCgButtonClick(optType, buttonCode) {
if ('js' == optType) {
if (EnhanceJS && EnhanceJS[buttonCode]) {
EnhanceJS[buttonCode].call(onlineFormContext, onlineFormContext);
}
} else if ('action' == optType) {
let formData = dbData.value;
let params = {
formId: props.id,
buttonCode: buttonCode,
dataId: formData.id,
uiFormData: Object.assign({}, formData),
};
//console.log("",params)
defHttp
.post(
{
url: `${urlObject.urlButtonAction}`,
params,
},
{ isTransformResponse: false }
)
.then((res) => {
if (res.success) {
$message.success('处理完成!');
} else {
$message.warning('处理失败!');
}
});
}
}
// ---------------------------------------------
/**
* 清除子表数据
* @param tbname
*/
function clearSubRows(tbname) {
let instance = refMap[tbname].value;
let rows = [...instance.getNewDataWithId(), ...subDataSource.value[tbname]];
if (!rows || rows.length == 0) {
return false;
}
let ids = [];
for (let i of rows) {
ids.push(i.id);
}
instance.removeRowsById(ids);
}
/**
* 添加子表数据
* @param tbname
* @param rows 可以是数组也可以是对象
* @returns {boolean}
*/
function addSubRows(tbname, rows) {
if (!rows) {
return false;
}
let instance = refMap[tbname].value;
if (typeof rows == 'object') {
instance.addRows(rows, true);
} else {
$message.error('添加子表数据,参数不识别!');
}
}
/**
* 先删除后添加
* @param tbname
* @param rows
*/
function clearThenAddRows(tbname, rows) {
clearSubRows(tbname);
addSubRows(tbname, rows);
}
/**
* 修改下拉框的下拉选项
* @param field
* @param options
*/
function changeOptions(field, options) {
if (!options && options.length <= 0) {
options = [];
}
options.map((item) => {
if (!item.hasOwnProperty('label')) {
item['label'] = item.text;
}
});
updateSchema({
field,
componentProps: {
options,
},
});
}
/**
* 表单提交前事件
* @param that
* @param formData
* @returns {Promise<void>|*}
*/
function customBeforeSubmit(that, formData) {
if (EnhanceJS && EnhanceJS['beforeSubmit']) {
return EnhanceJS['beforeSubmit'](that, formData);
} else {
return Promise.resolve();
}
}
/**
* 处理自定义弹框 字段的显示隐藏
* @param show
* @param hide
*/
function handleCustomFormSh(show, hide) {
let plain = toRaw(fieldDisplayStatus);
if (show && show.length > 0) {
Object.keys(plain).map((k) => {
if (!k.endsWith('_load') && show.indexOf(k) < 0) {
fieldDisplayStatus[k] = false;
}
});
} else if (hide && hide.length > 0) {
Object.keys(plain).map((k) => {
if (hide.indexOf(k) >= 0) {
fieldDisplayStatus[k] = false;
}
});
}
}
async function handleCustomFormEdit(record) {
console.log('自定义弹窗打开online表单》》form', record);
await resetFields();
dbData.value = '';
isUpdate.value = true;
//
await edit(record);
await nextTick(() => {
// loaded
handleCgButtonClick('js', 'loaded');
});
}
/**
* VUEN-1036
* 获取子表的实例对象 可以直接调用子表的方法
*/
function getSubTableInstance(tableName) {
let instance = refMap[tableName].value;
if (instance instanceof Array) {
instance = instance[0];
}
return instance;
}
//
function onOpenReportPrint(){
let url = onlineExtConfigJson.reportPrintUrl;
let id = dbData.value.id;
let token = getToken();
goJmReportViewPage(url, id, token)
}
//----
function openSubFormModalForAdd(sub){
console.log(sub)
vxeTableId.value = sub.id;
openVxeFormModal(true, )
}
function openSubFormModalForEdit(sub){
console.log(sub)
}
/**
* 保存数据
* @param formData
*/
function doApplyRequest(formData) {
//
Object.keys(formData).map((key) => {
if (Array.isArray(formData[key])) {
if (formData[key].length == 0) {
formData[key] = '';
}
}
});
console.log('提交pop表单数据》》》form:', formData);
if(props.request == false){
emit('success', formData);
}else{
let url = `${urlObject.optPre}${props.id}?tabletype=${tableType.value}`;
console.log('提交pop表单url》》》url:', url);
//
if (submitFlowFlag.value === true) {
formData[SUBMIT_FLOW_KEY] = 1;
}
let method:Method = isUpdate.value === true ? 'put' : 'post';
defHttp.request({ url, method, params: formData }, { isTransformResponse: false })
.then((res) => {
//console.log('', res)
if (res.success) {
if (res.result) {
//formData[SUBMIT_FLOW_ID] = res.result;
if(!formData.id){
formData['id'] = res.result;
}
}
//
emit('success', formData);
dbData.value = formData;
isUpdate.value = true;
$message.success('操作成功!')
//
} else {
$message.warning(res.message);
}
})
.finally(() => {
loading.value = false;
});
}
}
/**
* 数据恢复到 dbdata
*/
async function recoverFormData(){
let record = dbData.value;
let arr = realFormFieldNames.value;
let values = pick(record, ...arr);
if(record){
await setFieldsValue(values);
}else{
let temp:any = {}
for(let key of arr){
temp[key] = ''
}
await setFieldsValue(temp);
}
}
let context = {
tableName,
loading,
subActiveKey,
onlineFormRef,
getFieldsValue,
setFieldsValue,
submitFlowFlag,
subFormHeight,
subTableHeight,
refMap,
triggleChangeValues,
triggleChangeValue,
sh: fieldDisplayStatus,
clearSubRows,
addSubRows,
clearThenAddRows,
changeOptions,
isUpdate,
getSubTableInstance,
};
resetContext(context);
return {
//
tableName,
onlineFormRef,
registerForm,
loading,
//
subActiveKey,
hasSubTable,
subTabInfo,
refMap,
//
subFormHeight,
getSubTableForeignKeyValue,
isUpdate,
handleSubFormChange,
//
subTableHeight,
onlineFormDisabled,
subDataSource,
getSubTableAuthPre,
handleAdded,
handleValueChange,
openSubFormModalForAdd,
openSubFormModalForEdit,
registerVxeFormModal,
vxeTableId,
//
show,
createRootProperties,
handleSubmit,
sh: fieldDisplayStatus,
handleCgButtonClick,
handleCustomFormSh,
handleCustomFormEdit,
//
dbData,
onOpenReportPrint,
onlineExtConfigJson,
recoverFormData
};
},
};
</script>
<style lang="less" scoped>
.onlinePopFormWrap {
// update-begin--author:liaozhiyang---date:20240429---forQQYUN-7632 labellabelwidth
padding: 20px 1.5% 0 1.5%;
// update-begin--author:liaozhiyang---date:20240506---forQQYUN-9229
&.formTemplate_1 {
> form {
padding-left: 5%;
padding-right: 5%;
}
}
&.formTemplate_2 {
> form {
padding-left: 1%;
padding-right: 1%;
}
}
// update-end--author:liaozhiyang---date:20240506---forQQYUN-9229
:deep(.ant-form) {
> .ant-row {
> .ant-col {
padding: 0 6px;
}
}
}
// update-end--author:liaozhiyang---date:20240429---forQQYUN-7632 labellabelwidth
}
</style>

View File

@ -0,0 +1,342 @@
<template>
<BasicModal @register="registerModal" :width="popModalFixedWidth" :dialogStyle="{top: '70px'}" :bodyStyle="popBodyStyle" :title="modalTitle" wrapClassName="jeecg-online-pop-list-modal">
<template #footer>
<div style="display: inline-block;width: calc(100% - 140px);text-align: left;">
<a-button v-if="addAuth" style="border-radius: 50px" type="primary" @click="handleAdd"><PlusOutlined/>新增记录</a-button>
</div>
<a-button key="back" @click="handleCancel">关闭</a-button>
<a-button :disabled="submitDisabled" key="submit" type="primary" @click="handleSubmit" :loading="submitLoading">确定</a-button>
</template>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!-- update-begin-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
<template #tableTitle>
<a-input-search v-model:value="searchText" @search="onSearch" placeholder="请输入关键词,按回车搜索" style="width: 240px" />
</template>
<!-- update-end-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)">
</TableAction>
</template>
<template #fileSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text)"> 下载 </a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text }">
<div v-html="text"></div>
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
</BasicTable>
</BasicModal>
<!-- 弹窗到另外一张表单用-可编辑表单 -->
<online-pop-modal :id="id" @register="registerPopModal" @success="handleDataSave" topTip></online-pop-modal>
</template>
<script lang="ts">
import { defineComponent, watch, ref, toRaw, computed } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import { defHttp } from '/@/utils/http/axios';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { PlusOutlined } from '@ant-design/icons-vue';
import { useModal } from '/@/components/Modal';
import OnlinePopModal from './OnlinePopModal.vue';
import { useFixedHeightModal } from '../../hooks/auto/useAutoModal';
export default defineComponent({
name: 'OnlinePopListModal',
props: {
/**可以是表名 可以是ID*/
id: {
type: String,
default: '',
},
multi:{
type: Boolean,
default: false
},
addAuth:{
type: Boolean,
default: true
}
},
components: {
BasicModal,
BasicTable,
TableAction,
PlusOutlined,
OnlinePopModal
},
emits: ['success', 'register'],
setup(props, { emit }) {
const { createMessage: $message } = useMessage();
//
const { popModalFixedWidth, resetBodyStyle, popBodyStyle } = useFixedHeightModal();
const searchText = ref('');
const modalWidth = ref(800);
//useModalInner
const [registerModal, {closeModal}] = useModalInner((data) => {
searchText.value = '';
// update-begin--author:liaozhiyang---date:20240517---forTV360X-43
selectedRowKeys.value = data.selectedRowKeys;
selectedRows.value = data.selectedRows;
// update-end--author:liaozhiyang---date:20240517---forTV360X-43
setPagination({current:1})
reload();
resetBodyStyle();
});
// online
const [registerPopModal, { openModal: openPopModal }] = useModal();
function handleCancel() {
closeModal();
}
const submitDisabled = computed(()=>{
const arr = selectedRowKeys.value;
if(arr && arr.length>0){
return false;
}
return true;
})
const submitLoading = ref(false);
function handleSubmit(){
submitLoading.value = true;
let arr = toRaw(selectedRows.value);
if(arr && arr.length>0){
emit('success', arr)
closeModal();
}
setTimeout(()=>{
submitLoading.value = false
}, 200);
}
//---------------------------------------------
function queryTableData(params){
const url = '/online/cgform/api/getData/'+props.id;
return defHttp.get({ url, params });
}
function list(params){
params['column'] = 'id';
return new Promise(async (resolve, _reject) => {
const aa = await queryTableData(params)
resolve(aa);
})
}
const onlineTableContext = {
isPopList: true,
reloadTable(){
console.log('reloadTable')
},
isTree(){
return false;
}
};
const extConfigJson = ref<any>({});
// BasicTable
const {
columns,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
} = useTableColumns(onlineTableContext, extConfigJson);
/**
* 查询table列信息 及其他配置
*/
function getColumnList() {
const url = '/online/cgform/api/getColumns/'+props.id;
return new Promise((resolve, reject) => {
defHttp.get({url}, { isTransformResponse: false }).then((res) => {
if (res.success) {
resolve(res.result);
} else {
$message.warning(res.message);
reject();
}
})
});
}
const modalTitle = ref('')
watch(()=>props.id, async ()=>{
let columnResult:any = await getColumnList();
handleColumnResult(columnResult);
modalTitle.value = columnResult.description;
}, {immediate: true})
const { tableContext } = useListPage({
designScope: 'process-design',
pagination: true,
tableProps: {
title: '',
api: list,
clickToRowSelect: true,
columns: columns,
showTableSetting: false,
immediate:false,
//showIndexColumn: true,
canResize: false,
showActionColumn: false,
actionColumn: {
dataIndex: 'action',
slots: { customRender: 'action' },
},
useSearchForm: false,
beforeFetch: (params) => {
return addQueryParams(params);
},
},
});
const [registerTable, { reload, setPagination }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
watch(()=>props.multi, (val)=>{
if(val==true){
rowSelection.type = 'checkbox'
}else{
rowSelection.type = 'radio'
}
}, {immediate: true});
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleUpdate.bind(null, record),
}
];
}
function handleUpdate(record){
console.log('handleUpdate', record)
}
function onSearch(){
reload();
}
const eqConditonTypes = ['int', 'double', 'Date', 'Datetime', 'BigDecimal']
function addQueryParams(params){
let text = searchText.value;
if(!text){
params['superQueryMatchType'] = 'or';
params['superQueryParams'] = ''
return params;
}
let arr = columns.value;
let conditions:any[] = []
if(arr && arr.length>0){
for(let item of arr){
if(item.dbType){
if(item.dbType == 'string'){
conditions.push({field: item.dataIndex,type:item.dbType.toLowerCase(), rule: 'like', val: text})
}else if(item.dbType == 'Date'){
if(text.length=='2020-10-10'.length){
conditions.push({field: item.dataIndex,type:item.dbType.toLowerCase(), rule: 'eq', val: text})
}
}else if(item.dbType == 'Datetime'){
if(text.length=='2020-10-10 10:10:10'.length){
conditions.push({field: item.dataIndex,type:item.dbType.toLowerCase(), rule: 'eq', val: text})
}
}else if(eqConditonTypes.indexOf(item.dbType)){
conditions.push({field: item.dataIndex, type:item.dbType.toLowerCase(), rule: 'eq', val: text})
}else{
//text blob
}
}
}
}
params['superQueryMatchType'] = 'or';
params['superQueryParams'] = encodeURI(JSON.stringify(conditions));
return params;
}
function handleAdd(){
openPopModal(true, {})
}
// modal list
function handleDataSave(data){
console.log('handleDateSave' ,data)
// update-begin--author:liaozhiyang---date:20250429---forissues/8163
let arr = [data, ...selectedRows.value];
// update-end--author:liaozhiyang---date:20250429---forissues/8163
emit('success', arr);
closeModal();
//reload();
}
return {
registerModal,
modalWidth,
handleCancel,
submitDisabled,
submitLoading,
handleSubmit,
registerTable,
getTableAction,
searchText,
onSearch,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
hrefComponent,
viewOnlineCellImage,
rowSelection,
modalTitle,
registerPopModal,
handleAdd,
reload,
popModalFixedWidth,
popBodyStyle,
handleDataSave
};
},
});
</script>
<style lang="less" scoped>
.online-cell-image {
max-height: 30px;
max-width: 50px;
object-fit: contain;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,222 @@
<template>
<BasicModal :width="popModalFixedWidth" :dialogStyle="{top: '70px'}" :bodyStyle="popBodyStyle" v-bind="$attrs" :footer="modalFooter" cancelText="关闭" @register="registerModal" wrapClassName="jeecg-online-pop-modal" @ok="handleSubmit">
<template #title>
{{title}}
<j-modal-tip v-if="showTopTip" :visible="topTipVisible" @save="handleSaveData" @cancel="handleRecover"></j-modal-tip>
</template>
<online-pop-form
ref="onlineFormCompRef"
:id="id"
:disabled="disableSubmit"
:form-template="formTemplate"
:isTree="isTreeForm"
:pidField="pidFieldName"
:request="request"
:isVxeTableData="isVxeTableData"
@rendered="renderSuccess"
@success="handleSuccess"
@data-change="handleDataChange"
modal-class="jeecg-online-pop-modal"
>
</online-pop-form>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, watchEffect, ref, computed, h } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlinePopForm from './OnlinePopForm.vue';
import { useAutoModal } from '../../hooks/auto/useAutoModal';
import JModalTip from '../../extend/linkTable/JModalTip.vue'
import { Button } from 'ant-design-vue';
export default defineComponent({
name: 'OnlinePopModal',
props: {
/**可以是表名 可以是ID*/
id: {
type: String,
default: '',
},
/*展示字段名*/
showFields:{
type: Array,
default: ()=>[],
},
/*隐藏字段名*/
hideFields:{
type: Array,
default: ()=>[],
},
topTip:{
type: Boolean,
default: false,
},
request:{
type: Boolean,
default: true,
},
saveClose:{
type: Boolean,
default: false
},
// vxeTable
isVxeTableData:{
type: Boolean,
default: false
},
formTableType:{
type: String,
default: '',
},
// -update-begin--author:liaozhiyang---date:20240613---forTV360X-1000
// taskId
taskId: {
type: String,
},
tableName: {
type: String,
},
// -update-end--author:liaozhiyang---date:20240613---forTV360X-1000
},
components: {
BasicModal,
OnlinePopForm,
JModalTip,
Button
},
emits: ['success', 'register', 'formConfig'],
setup(props, { emit }) {
console.log('进入表单弹框》》》》modal');
const {
title,
registerModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
handleSuccess,
topTipVisible,
successThenClose,
isUpdate,
popBodyStyle,
popModalFixedWidth,
getFormStatus
} = useAutoModal(false, { emit });
// id
watch(() => props.id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!props.id) {
return;
}
console.log('重新渲染表单》》》》modal');
//update-begin-author:taoyan date:2023-4-10 for: issues/4655 online线 #4655
let params = {}
if(props.formTableType){
params['tabletype'] = props.formTableType
}
// -update-begin--author:liaozhiyang---date:20240613---forTV360X-1000
if (props.taskId) {
await handleFormConfig(props.id, params,null,props.taskId, props.tableName );
} else {
await handleFormConfig(props.id, params);
}
// -update-end--author:liaozhiyang---date:20240613---forTV360X-1000
//update-end-author:taoyan date:2023-4-10 for: issues/4655 online线 #4655
}
//
function handleSaveData() {
//propssaveClosetrue
if(props.saveClose === false){
successThenClose.value = false;
}
handleSubmit();
}
//
function handleRecover(){
topTipVisible.value = false;
onlineFormCompRef.value.recoverFormData()
}
// modal
function handleDataChange(){
topTipVisible.value = true;
}
//
const showTopTip = computed(()=>{
// update-begin--author:liaozhiyang---date:20250318---forissues/7930
if (disableSubmit.value) {
return false;
}
// update-end--author:liaozhiyang---date:20250318---forissues/7930
if(!isUpdate.value){
return false;
}
return props.topTip
});
//
const modalFooter = computed(()=>{
if(isUpdate.value==true){
return null;
}else{
let flag = submitLoading.value;
const defaultFooter:any[] = [
h(Button, { type: 'primary', loading: flag, onClick: handleSubmit },()=>'确定'),
h(Button, { onClick:handleCancel },()=>'关闭')
];
return defaultFooter
}
});
const that = {
title,
topTipVisible,
handleSaveData,
handleRecover,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleSuccess,
handleCancel,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
handleDataChange,
isUpdate,
showTopTip,
modalFooter,
popBodyStyle,
popModalFixedWidth,
getFormStatus
};
return that;
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,863 @@
<template>
<div class="jeecg-basic-table-form-container online-query-form p-0" v-if="formSchemas && formSchemas.length > 0">
<BasicForm ref="onlineQueryFormRef" @register="registerForm">
<!-- 范围查询日期 -->
<template #groupDate="{ model, field, schema }">
<!-- update-begin--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
<!-- <a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="开始日期"
v-model:value="model[field + '_begin']"
style="width: calc(50% - 15px);min-width: 100px;"
v-bind="schema.componentProps"
></a-date-picker>
<span class="group-query-string">~</span>
<a-form-item-rest>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="结束日期"
v-model:value="model[field + '_end']"
style="width: calc(50% - 15px);min-width: 100px;"
v-bind="schema.componentProps"
></a-date-picker>
</a-form-item-rest> -->
<a-range-picker :style="{ width: '100%' }" v-model:value="model[field]" v-bind="schema.componentProps" :placeholder="getGroupDatePlaceholder(schema.componentProps)" valueFormat="YYYY-MM-DD"/>
<!-- update-end--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
</template>
<!-- 范围查询时间 -->
<template #groupDatetime="{ model, field }">
<!-- update-begin--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
<!-- <a-date-picker
:showTime="true"
valueFormat="YYYY-MM-DD HH:mm:ss"
placeholder="开始时间"
v-model:value="model[field + '_begin']"
style="min-width: 100px;width: calc(50% - 15px);"
></a-date-picker>
<span class="group-query-string">~</span>
<a-form-item-rest>
<a-date-picker
:showTime="true"
valueFormat="YYYY-MM-DD HH:mm:ss"
placeholder="结束时间"
v-model:value="model[field + '_end']"
style="min-width: 100px;width: calc(50% - 15px);"
></a-date-picker>
</a-form-item-rest> -->
<a-range-picker :style="{ width: '100%' }" v-model:value="model[field]" :show-time="true" valueFormat="YYYY-MM-DD HH:mm:ss"/>
<!-- update-end--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
</template>
<!-- update-begin--author:liaozhiyang---date:20240517---forQQYUN-9348增加online查询区域时间范围查询功能 -->
<!-- 范围查询时间 -->
<template #groupTime="{ model, field }">
<!-- update-begin--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
<!-- <a-time-picker
placeholder="开始时间"
value-format="HH:mm:ss"
v-model:value="model[field + '_begin']"
style="min-width: 100px;width: calc(50% - 15px);"
></a-time-picker>
<span class="group-query-string">~</span>
<a-form-item-rest>
<a-time-picker
placeholder="结束时间"
value-format="HH:mm:ss"
v-model:value="model[field + '_end']"
style="min-width: 100px;width: calc(50% - 15px);"
></a-time-picker>
</a-form-item-rest> -->
<a-time-range-picker :style="{ width: '100%' }" v-model:value="model[field]" value-format="HH:mm:ss" />
<!-- update-end--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
</template>
<!-- update-end--author:liaozhiyang---date:20240517---forQQYUN-9348增加online查询区域时间范围查询功能 -->
<!-- 范围查询数值 -->
<template #groupNumber="{ model, field, schema }">
<!-- update-begin--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
<!-- <a-input-number placeholder="开始值" v-model:value="model[field + '_begin']" style="width: calc(50% - 15px)"></a-input-number>
<span class="group-query-string">~</span>
<a-form-item-rest>
<a-input-number placeholder="结束值" v-model:value="model[field + '_end']" style="width: calc(50% - 15px)"></a-input-number>
</a-form-item-rest> -->
<JRangeNumber v-model:value="model[field]" v-bind="schema.componentProps" />
<!-- update-end--author:liaozhiyang---date:20240530---forTV360X-213普通查询日期数值组件更换 -->
</template>
<!-- 查询/重置按钮-->
<template #formFooter>
<a-col :md="6" :sm="8">
<span style="float: left; overflow: hidden; margin-left: 10px" class="table-page-search-submitButtons">
<a-button
v-if="queryBtnCfg.enabled"
type="primary"
:preIcon="queryBtnCfg.buttonIcon"
@click="doSearch"
>
<span>{{ queryBtnCfg.buttonName }}</span>
</a-button>
<a-button
v-if="resetBtnCfg.enabled"
type="primary"
:preIcon="resetBtnCfg.buttonIcon"
style="margin-left: 8px"
@click="resetSearch"
>
<span>{{ resetBtnCfg.buttonName }}</span>
</a-button>
<a v-if="toggleButtonShow" @click="toggleSearchStatus = !toggleSearchStatus" style="margin-left: 8px">
{{ toggleSearchStatus ? '收起' : '展开' }}
<a-icon :type="toggleSearchStatus ? 'up' : 'down'" />
</a>
</span>
</a-col>
</template>
</BasicForm>
</div>
</template>
<script>
import { BasicForm, useForm } from '/@/components/Form/index';
import { watch, ref, reactive, toRaw, isProxy } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import FormSchemaFactory from './factory/FormSchemaFactory';
import IFormSchema from './factory/IFormSchema';
import { handleLinkDown, getFieldIndex, getRefPromise, LINK_DOWN } from '../../hooks/auto/useAutoForm';
import { ONL_QUERY_LABEL_COL, ONL_QUERY_WRAPPER_COL, FORM_VIEW_TO_QUERY_VIEW } from '../../types/onlineRender';
import { loadOneFieldDefVal } from '../../util/FieldDefVal';
import { useExtendComponent } from '../../hooks/auto/useExtendComponent';
import { LABELLENGTH } from '../../util/constant';
import dayjs from 'dayjs';
import JRangeNumber from '/@/components/Form/src/jeecg/components/JRangeNumber.vue'
import { useDebounceFn } from "@vueuse/core";
export default {
name: 'OnlineQueryForm',
components: {
BasicForm,
JRangeNumber,
},
props: {
id: {
type: String,
default: '',
},
queryBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '查询',
buttonIcon: 'ant-design:search',
}
}
},
resetBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '重置',
buttonIcon: 'ant-design:reload',
}
}
},
},
emits: ['search', 'loaded'],
setup(props, { emit }) {
//
const LOAD_URL = '/online/cgform/api/getQueryInfoVue3/';
// ref
const onlineQueryFormRef = ref(null);
//
const formSchemas = ref([]);
// VUEN-2493onlineonline
const baseColProps = ref({ xs:24, sm: 24, md: 12, lg:6, xl:6 });
//
const toggleButtonShow = ref(false);
//
const toggleSearchStatus = ref(false);
//
const queryParams = ref({});
//
const hideList = ref([]);
const { createMessage: $message } = useMessage();
const { linkTableCard2Select } = useExtendComponent();
const formLabelWidth = ref(80);
/**
* 默认值分三种,param > cache > config
* 1.表单配置-config
* 2.路由缓存-cache
* 3.地址栏参数-param
* 当表单id发生改变config改变列表页传入cacheparam;监听status change然后重置表单的值
*/
const defaultValues = reactive({
config: {},
cache: {},
param: {},
status: false,
});
const debouncedCustomSetFieldsValue = useDebounceFn(customSetFieldsValue, 500);
/**
* 监听cacheFormValues
*/
watch(
() => defaultValues.status,
async (val) => {
console.log('-------------defaultValues发生改变,需要重置表单---------------');
const { config, cache, param } = toRaw(defaultValues);
let rawValues = Object.assign({}, config, cache, param);
//update-begin---author:wangshuai---date:2025-10-11---for:issues/8790online bug ---
await debouncedCustomSetFieldsValue(rawValues);
//update-end---author:wangshuai---date:2025-10-11---for:issues/8790online bug ---
},
{ immediate: true, deep: true }
);
/**
* 设置默认值
* @param values
*/
async function initDefaultValues(cache, param) {
defaultValues.cache = { ...cache };
defaultValues.param = { ...param };
defaultValues.status = !defaultValues.status;
}
const clearObj = (obj) => {
Object.keys(obj).map((key) => {
delete obj[key];
});
};
//
watch(
() => props.id,
(val) => {
if (val) {
resetForm();
} else {
formSchemas.value = [];
}
},
{ immediate: true }
);
/**
* 获取表单配置
*/
async function initSchemas(formProperties) {
let arr = [];
let configValue = {};
let keys = Object.keys(formProperties);
let setLabelLength = -1;
for (let key of keys) {
const item = formProperties[key];
//update-begin-author:taoyan date:2023-7-19 for:QQYUN-5783 org_codevalueid
if(key === 'sys_org_code'){
if(!item.fieldExtendJson){
item.fieldExtendJson = '{"store":"orgCode"}'
}
}
//update-end-author:taoyan date:2023-7-19 for:QQYUN-5783 org_codevalueid
let view = item.view;
// update-begin--author:liaozhiyang---date:20240611---forTV360X-461stringtext
item.originView = item.view;
// update-end--author:liaozhiyang---date:20240611---forTV360X-461stringtext
if (FORM_VIEW_TO_QUERY_VIEW[view]) {
item.view = FORM_VIEW_TO_QUERY_VIEW[view];
}
await loadOneFieldDefVal(key, item, configValue);
if (item.mode == 'group' && ('date' == view || 'datetime' == view || 'number' == view || 'time' == view )) {
// -
let temp = FormSchemaFactory.createSlotFormSchema(key, item);
arr.push(temp);
} else {
if (item.view === LINK_DOWN) {
let array = handleLinkDown(item, key);
for (let linkDownItem of array) {
let temp = FormSchemaFactory.createFormSchema(linkDownItem.key, linkDownItem);
let tempIndex = getFieldIndex(arr, linkDownItem.key);
if (tempIndex == -1) {
arr.push(temp);
} else {
arr[tempIndex] = temp;
}
}
} else {
let tempIndex = getFieldIndex(arr, key);
if (tempIndex == -1) {
let temp = FormSchemaFactory.createFormSchema(key, item);
arr.push(temp);
}
}
}
// update-begin--author:liaozhiyang---date:20231205---forQQYUN-7140online label6
let fieldExtendJson = item.fieldExtendJson;
if (fieldExtendJson) {
fieldExtendJson = JSON.parse(fieldExtendJson);
if (fieldExtendJson.labelLength) {
console.log(key, fieldExtendJson.labelLength);
if (setLabelLength > -1) {
// label
setLabelLength = fieldExtendJson.labelLength > setLabelLength ? fieldExtendJson.labelLength : setLabelLength;
} else {
setLabelLength = fieldExtendJson.labelLength;
}
}
}
// update-end--author:liaozhiyang---date:20231205---forQQYUN-7140online label6
}
// update-begin--author:liaozhiyang---date:20231205---forQQYUN-7140online label6
// ,
if (setLabelLength == -1) {
setLabelLength = LABELLENGTH;
} else {
// update-begin--author:liaozhiyang---date:20240517---forTV360X-98labellabelLength
arr.forEach(item=>{
item.labelLength = setLabelLength;
})
// update-end--author:liaozhiyang---date:20240517---forTV360X-98labellabelLength
}
// update-end--author:liaozhiyang---date:20231205---forQQYUN-7140online label6
arr.sort(function (a, b) {
return a.order - b.order;
});
let schemaArray = [];
if (arr.length > 2) {
toggleButtonShow.value = true;
}
let hideFieldName = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
item.setFormRef(onlineQueryFormRef);
item.noChange();
item.asSearchForm();
if (i > 1) {
hideFieldName.push(item.field);
item.isHidden();
}
//update-begin-author:taoyan date:2022-10-24 for: VUEN-2493onlineonline
let tempSchema = item.getFormItemSchema();
if(item.slot == 'groupDatetime'){
// update-begin--author:liaozhiyang---date:20240530---forTV360X-213
// colprops (3)
arr.length <= 3 && (tempSchema['colProps'] = { xs:24, sm: 24, md: 12, lg:8, xl:8 })
// update-end--author:liaozhiyang---date:20240530---forTV360X-213
}
// update-begin--author:liaozhiyang---date:20240522---forTV360X-250Switchselect
if (tempSchema.component === 'JSwitch') {
const componentProps = tempSchema.componentProps ?? {};
tempSchema.componentProps = { ...componentProps, query: true };
}
// update-end--author:liaozhiyang---date:20240522---forTV360X-250Switchselect
linkTableCard2Select(tempSchema);
// update-begin--author:liaozhiyang---date:20240530---forTV360X-389
if (tempSchema.component === 'LinkTableSelect') {
let componentProps = tempSchema.componentProps ?? {};
tempSchema.componentProps = { ...componentProps, editBtnShow: false };
}
// update-end--author:liaozhiyang---date:20240530---forTV360X-389
// update-begin--author:liaozhiyang---date:20240614---forTV360X-1231
const compProps = tempSchema.componentProps ?? {};
if (!compProps.getPopupContainer) {
tempSchema.componentProps = { ...compProps, getPopupContainer: () => document.body };
}
// update-end--author:liaozhiyang---date:20240614---forTV360X-1231
// update-begin--author:liaozhiyang---date:20240725---forTV360X-1857online
const fieldData = formProperties[tempSchema.field] ?? {};
// TV360X-1966string
if (fieldData.mode == 'like' && fieldData.view === 'text' && fieldData.originView === 'text') {
tempSchema.component = 'JInput';
}
// update-end--author:liaozhiyang---date:20240725---forTV360X-1857online
schemaArray.push(tempSchema);
//update-end-author:taoyan date:2022-10-24 for: VUEN-2493onlineonline
}
hideList.value = hideFieldName;
formSchemas.value = schemaArray;
//
defaultValues.config = { ...configValue };
defaultValues.status = !defaultValues.status;
// update-begin--author:liaozhiyang---date:20231204---forQQYUN-7140online label6
setTimeout(() => {
// 14size24
const w = setLabelLength * 14 + setLabelLength + 24;
formLabelWidth.value = w;
}, 0);
// update-end--author:liaozhiyang---date:20231204---forQQYUN-7140online label6
}
/**
* 2024-05-31
* liaozhiyang
* TV360X-415个性化查询支持年季度.
* 解析特定view(组件)字段的值把view字段值为date_yeardate_monthdate_weekdate_quarter
* 改成date并组装fieldExtendJson
*/
const analysisComponent = (res) => {
const properties = res.properties;
if (properties) {
Object.entries(properties).forEach(([key, value]) => {
const data = value;
if (['date_year', 'date_month', 'date_week', 'date_quarter'].includes(data.view)) {
const fieldExtendJson = data.fieldExtendJson ? JSON.parse(data.fieldExtendJson) : {};
fieldExtendJson.picker = data.view.split('_')[1];
data.fieldExtendJson = JSON.stringify(fieldExtendJson);
data.view = 'date';
}
});
}
};
async function resetForm() {
let json = await loadQueryInfo();
// update-begin--author:liaozhiyang---date:20240531---forTV360X-415
analysisComponent(json);
// update-end--author:liaozhiyang---date:20240318---forTV360X-415
// update-begin--author:liaozhiyang---date:20240524---forTV360X-516
// filterComponent(json);
// update-end--author:liaozhiyang---date:20240524---forTV360X-516
//
let allFields = getAllFields(json);
emit('loaded', json);
//
let { formProperties, hasField } = getQueryFormProperties(allFields, json);
if (hasField == false) {
formSchemas.value = [];
return;
}
// formSchemas
await initSchemas(formProperties);
}
/**
* 2024-05-24
* liaozhiyang
* 过滤掉不支持查询的组件图片文件密码关联记录联动
*/
const filterComponent = (data) => {
const { properties = {} } = data;
Object.entries(properties).forEach(([field, value]) => {
if (value.view === 'table') {
filterComponent(value);
}
if (['image', 'password', 'file', 'link_table', 'link_down'].includes(value.view)) {
delete properties[field];
}
});
};
/**
* 设置表单的值
*/
async function customSetFieldsValue(rawValues) {
await getRefPromise(onlineQueryFormRef);
console.log('rawValues', rawValues);
// update-begin--author:liaozhiyang---date:20240618---foronline使
const values = transformGroupDefValus(rawValues);
await setFieldsValue(values);
// update-end--author:liaozhiyang---date:20240618---foronline使
if (Object.keys(values).length > 0) {
doSearch();
}
}
/**
* 转化
*/
function getQueryFormProperties(allFields, json) {
const { searchFieldList, joinQuery, table } = json;
let hasField = false;
let formProperties = {};
if (allFields) {
Object.keys(allFields).map((field) => {
if (searchFieldList.indexOf(field) >= 0) {
//
if (joinQuery == true) {
//
if (field.indexOf('@') < 0) {
//@, @
formProperties[table + '@' + field] = allFields[field];
hasField = true;
} else {
//@
formProperties[field] = allFields[field];
hasField = true;
}
} else {
//
if (field.indexOf('@') < 0) {
formProperties[field] = allFields[field];
hasField = true;
}
}
}
});
}
return {
formProperties,
hasField,
};
}
/**
* 获取查询条件表单配置
* json结构
* 主表字段1{配置1}
* 主表字段2{配置2}
* 子表名@子表字段1{子表字段配置1}
* 子表名@子表字段2{子表字段配置2}
*/
function getAllFields(json) {
//
const { properties, searchFieldList, joinQuery, table } = json;
let allFields = {};
let order = 1;
let hasField = false;
Object.keys(properties).map((field) => {
let item = properties[field];
if (item.view == 'table') {
//
//
let subProps = item['properties'];
let subTableOrder = order * 100;
Object.keys(subProps).map((subField) => {
let subItem = subProps[subField];
//
subItem['order'] = subTableOrder + Number(subItem['order']);
let subFieldKey = field + '@' + subField;
allFields[subFieldKey] = subItem;
});
order++;
} else {
//
item['order'] = Number(item['order']);
allFields[field] = item;
}
});
return allFields;
}
/**
* 查询返回数据格式, 需要经过getQueryFormProperties转成表单配置
* json结构
* table(表名)
* title(表描述)
* properties(表字段)
* field1
* field2
* fieldxxx
* sub-table1(子表名1)
* title(子表描述1)
* properties(子表字段)
* sub-table2(子表名2)
* title(子表描述2)
* properties(子表字段)
*/
function loadQueryInfo() {
let url = `${LOAD_URL}${props.id}`;
return new Promise((resolve) => {
defHttp
.get({ url }, { isTransformResponse: false })
.then((res) => {
// console.log("-online", res);
if (res.success) {
resolve(res.result);
} else {
resolve(false);
$message.warning(res.message);
}
})
.catch(() => {
$message.warning('获取查询条件失败!');
resolve(false);
});
});
}
//
const [registerForm, { resetFields, setFieldsValue, updateSchema, getFieldsValue }] = useForm({
name: 'online-query-form',
schemas: formSchemas,
showActionButtonGroup: false,
baseColProps: baseColProps,
autoSubmitOnEnter: true,
labelWidth: formLabelWidth,
wrapperCol: null,
submitFunc() {
//update-begin---author:wangshuai---date:2025-10-11---for:issues/8790online bug ---
//doSearch();
//update-end---author:wangshuai---date:2025-10-11---for:issues/8790online bug ---
},
/* labelCol: ONL_QUERY_LABEL_COL,
wrapperCol: ONL_QUERY_WRAPPER_COL*/
});
/**
* 执行查询
*/
function doSearch() {
let formValues = getFieldsValue();
// update-begin--author:liaozhiyang---date:20240517---forTV360X-28
transformDateValus(formValues);
// update-end--author:liaozhiyang---date:20240517---forTV360X-28
// update-begin--author:liaozhiyang---date:20240530---forTV360X-213
transformGroupValus(formValues);
// update-end--author:liaozhiyang---date:20240530---forTV360X-213
//
let data = Object.assign({}, toRaw(defaultValues.param), changeDataIfArray2String(formValues));
emit('search', data, true);
}
/**
* 2024-06-18
* liaozhiyang
* online普通查询默认值范围查询不好使
* */
const transformGroupDefValus = (obj) => {
const values = { ...obj };
const groupSchemas = formSchemas.value.filter((item) => ['groupTime', 'groupDatetime', 'groupNumber', 'groupDate'].includes(item.slot));
if (groupSchemas.length) {
Object.keys(values).forEach((filed) => {
let key;
const findItem = groupSchemas.find((item) => {
if (item.field === filed) {
key = filed;
return true;
}
return false;
});
if (findItem) {
const value = values[key];
if (typeof value === 'string') {
const arr = value.split(',');
values[key] = [...arr];
}
}
});
}
return values;
};
/**
* 2024-05-20
* liaozhiyang
* TV360X-213把groupDatetime,groupTime,groupDate,groupNumber等范围字段分割成两个字段
*/
const transformGroupValus = (values) => {
if (values) {
const groupSchemas = formSchemas.value.filter((item) => ['groupTime', 'groupDatetime', 'groupDate', 'groupNumber'].includes(item.slot));
if (groupSchemas.length) {
Object.keys(values).forEach((filed) => {
let key;
const findItem = groupSchemas.find((item) => {
if (item.field === filed) {
key = filed;
return true;
}
return false;
});
if (findItem) {
const value = values[key];
if (typeof value === 'string') {
const arr = value.split(',');
values[`${key}_begin`] = arr[0];
values[`${key}_end`] = arr[1];
delete values[key];
}
}
});
}
}
};
/**
* 2024-05-20
* liaozhiyang
* 把年年月周等时间重置到当前格式的第一天因为存的时候也是第一天 (TV360X-180兼容时间范围)
*/
const transformDateValus = (values) => {
const dateSchemas = formSchemas.value.filter((item) => item.componentProps?.picker && item.componentProps.picker != 'default');
if (dateSchemas.length) {
Object.keys(values).forEach((filed) => {
let key;
const findItem = dateSchemas.find((item) => {
if (item.field === filed || `${item.field}_begin` === filed || `${item.field}_end` === filed) {
key = filed;
return true;
}
return false;
});
if (findItem) {
const value = values[key];
if (value) {
// update-begin--author:liaozhiyang---date:20240530---forTV360X-213
const auto = (value, key, isEnd) => {
const picker = findItem.componentProps.picker;
if (picker === 'year') {
if (isEnd) {
values[key] = dayjs(value).endOf('year').format('YYYY-MM-DD');
} else {
values[key] = dayjs(value).startOf('year').format('YYYY-MM-DD');
}
} else if (picker === 'month') {
if (isEnd) {
values[key] = dayjs(value).endOf('month').format('YYYY-MM-DD');
} else {
values[key] = dayjs(value).startOf('month').format('YYYY-MM-DD');
}
} else if (picker === 'week') {
if (isEnd) {
values[key] = dayjs(value).endOf('week').format('YYYY-MM-DD');
} else {
values[key] = dayjs(value).startOf('week').format('YYYY-MM-DD');
}
} else if (picker === 'quarter') {
if (isEnd) {
values[key] = dayjs(value).endOf('quarter').format('YYYY-MM-DD');
} else {
values[key] = dayjs(value).startOf('quarter').format('YYYY-MM-DD');
}
}
};
if (findItem?.slot === 'groupDate') {
const arr = value.split(',');
auto(arr[0], `${key}_begin`, false);
auto(arr[1], `${key}_end`, true);
delete values[key];
} else {
auto(value, key, false);
}
// update-end--author:liaozhiyang---date:20240530---forTV360X-213
}
}
});
}
}
/**
* 重置是将 查询控件的值 重置到默认值
* 2024-05-23
* liaozhiyang
* TV360X-124提供clearSearch方法回到初始状态
*/
async function clearSearch() {
await resetFields();
const { config, param } = toRaw(defaultValues);
let rawValues = Object.assign({}, config, param);
if (Object.keys(rawValues).length > 0) {
await setFieldsValue(rawValues);
}
return rawValues;
}
async function resetSearch() {
const rawValues = await clearSearch();
emit('search', rawValues, false);
}
/**
* 有些数据是数组格式的 强转成字符串
*/
function changeDataIfArray2String(data) {
Object.keys(data).map((k) => {
if (data[k]) {
if (data[k] instanceof Array) {
data[k] = data[k].join(',');
}
}
});
return data;
}
watch(
() => toggleSearchStatus.value,
(status) => {
let names = hideList.value;
if (names && names.length > 0) {
let arr = [];
for (let name of names) {
arr.push({
field: name,
show: status,
});
}
updateSchema(arr);
}
},
{ immediate: false }
);
/**
* 2024-05-30
* liaozhiyang
* TV360X-392日期placeholder修改
* */
const getGroupDatePlaceholder = (data) => {
let result = ['开始日期', '结束日期'];
console.log(data);
if (data?.picker) {
switch (data?.picker) {
case 'year':
result = ['开始年份', '结束年份'];
break;
case 'month':
result = ['开始月份', '结束月份'];
break;
case 'week':
result = ['开始周', '结束周'];
break;
case 'quarter':
result = ['开始季度', '结束季度'];
break;
default:
result = ['开始日期', '结束日期'];
}
}
return result;
};
return {
onlineQueryFormRef,
registerForm,
initDefaultValues,
toggleButtonShow,
toggleSearchStatus,
doSearch,
resetSearch,
queryParams,
formSchemas,
clearSearch,
getGroupDatePlaceholder,
};
},
};
</script>
<style scoped lang="less">
.group-query-string {
width: 20px;
display: inline-block;
text-align: center;
}
// 0
.jeecg-basic-table-form-container.p-0 {
padding: 0;
}
// update-begin--author:liaozhiyang---date:20240514---forQQYUN-9241form
.jeecg-basic-table-form-container {
:deep(.ant-form-item) {
&:not(.ant-form-item-with-help) {
margin-bottom: 16px;
}
}
}
// update-end--author:liaozhiyang---date:20240514---forQQYUN-9241form
.online-query-form {
:deep(.ant-form) {
max-height: 40vh;
overflow-y: auto;
}
}
</style>

View File

@ -0,0 +1,415 @@
<!-- 此文件已没有使用的地方 -->
<template>
<a-form-item :labelCol="labelCol" :class="'jeecg-online-search'">
<template #label>
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<!-- 1.日期 -->
<template v-if="item.view == 'date'">
<template v-if="single_mode === item.mode">
<a-date-picker
style="width: 100%"
:showTime="false"
valueFormat="YYYY-MM-DD"
:placeholder="'请选择' + item.label"
v-model:value="innerValue"
></a-date-picker>
</template>
<template v-else>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="开始日期"
v-model:value="beginValue"
style="width: calc(50% - 15px)"
></a-date-picker>
<span class="group-query-strig">~</span>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="结束日期"
v-model:value="endValue"
style="width: calc(50% - 15px)"
></a-date-picker>
</template>
</template>
<!-- 2.时间 -->
<template v-else-if="item.view == 'datetime'">
<template v-if="single_mode === item.mode">
<a-date-picker
style="width: 100%"
:showTime="true"
valueFormat="YYYY-MM-DD hh:mm:ss"
:placeholder="'请选择' + item.label"
v-model:value="innerValue"
></a-date-picker>
</template>
<template v-else>
<a-date-picker
:showTime="true"
valueFormat="YYYY-MM-DD hh:mm:ss"
placeholder="开始时间"
v-model:value="beginValue"
style="width: calc(50% - 15px)"
></a-date-picker>
<span class="group-query-strig">~</span>
<a-date-picker
:showTime="true"
valueFormat="YYYY-MM-DD hh:mm:ss"
placeholder="结束时间"
v-model:value="endValue"
style="width: calc(50% - 15px)"
></a-date-picker>
</template>
</template>
<!-- 3 TODO 时分秒 -->
<!-- 4.简单下拉框 -->
<template v-else-if="isEasySelect()">
<JDictSelectTag v-if="item.config === '1'" :placeholder="'请选择' + item.label" v-model:value="innerValue" :dictCode="getDictCode()" />
<a-select v-else :placeholder="'请选择' + item.label" v-model:value="innerValue">
<template v-for="(obj, index) in dictOptions[getDictOptionKey(item)]" :key="index">
<a-select-option :value="obj.value"> {{ obj.text }}</a-select-option>
</template>
</a-select>
</template>
<!-- 5.下拉树 -->
<template v-else-if="item.view === 'sel_tree'">
<JTreeSelect
:placeholder="'请选择' + item.label"
v-model:value="innerValue"
:dict="item.dict"
:pidField="item.pidField"
:pidValue="item.pidValue"
:hasChildField="item.hasChildField"
load-triggle-change
>
</JTreeSelect>
</template>
<!-- 6.分类树 -->
<template v-else-if="item.view === 'cat_tree'">
<JCategorySelect
@change="handleCategoryTreeChange"
:loadTriggleChange="true"
:pcode="item.pcode"
v-model:value="innerValue"
:placeholder="'请选择' + item.label"
/>
</template>
<!-- 7.下拉搜索 -->
<template v-else-if="item.view === 'sel_search'">
<JDictSelectTag v-if="item.config === '1'" v-model:value="innerValue" :placeholder="'请选择' + item.label" :dict="getDictCode()" />
<JOnlineSearchSelect v-else v-model:value="innerValue" :placeholder="'请选择' + item.label" :sql="getSqlByDictCode()" />
</template>
<!-- 8.用户 -->
<JSelectUser
v-else-if="item.view == 'sel_user'"
v-bind="userSelectProp"
v-model:value="innerValue"
:placeholder="'请选择' + item.label"
></JSelectUser>
<!-- 9.部门 -->
<JSelectDept
v-else-if="item.view == 'sel_depart'"
:showButton="false"
v-bind="depSelectProp"
v-model:value="innerValue"
:placeholder="'请选择' + item.label"
/>
<!-- 10.popup -->
<JPopup
v-else-if="item.view == 'popup'"
:placeholder="'请选择' + item.label"
v-model:value="innerValue"
:code="item.dictTable"
:setFieldsValue="setFieldsValue"
:field-config="getPopupFieldConfig(item)"
:multi="true"
>
</JPopup>
<!-- 11.省市区 -->
<JAreaSelect v-else-if="item.view == 'pca'" :placeholder="'请选择' + item.label" v-model:value="innerValue" />
<!-- 12.下拉多选 -->
<template v-else-if="item.view == 'checkbox' || item.view == 'list_multi'" :label="item.label">
<JSelectMultiple :dictCode="getDictCode()" :placeholder="'请选择' + item.label" v-model:value="innerValue"></JSelectMultiple>
<!--<JDictSelectTag mode="multiple" @change="handleSelectChange" :dictCode="getDictCode()"/>-->
</template>
<!-- 13.普通输入框 -->
<template v-else>
<template v-if="single_mode === item.mode">
<a-input :placeholder="'请选择' + item.label" v-model:value="innerValue"></a-input>
</template>
<template v-else>
<a-input placeholder="开始值" v-model:value="beginValue" style="width: calc(50% - 15px)"></a-input>
<span class="group-query-strig">~</span>
<a-input placeholder="结束值" v-model:value="endValue" style="width: calc(50% - 15px)"></a-input>
</template>
</template>
</a-form-item>
</template>
<script lang="ts">
import { defineComponent, nextTick, ref, unref, watch, toRaw } from 'vue';
import {
JDictSelectTag,
JTreeSelect,
JSearchSelect,
JCategorySelect,
JSelectUserByDept,
JSelectDept,
JPopup,
JAreaLinkage,
JSelectUser,
JSelectMultiple,
JAreaSelect,
FormActionType,
} from '/@/components/Form';
import JOnlineSearchSelect from '../../auto/comp/JOnlineSearchSelect.vue';
export default defineComponent({
name: 'OnlineSearchFormItem',
components: {
JOnlineSearchSelect,
JDictSelectTag,
JTreeSelect,
JCategorySelect,
JSelectUser,
JSelectUserByDept,
JSelectDept,
JPopup,
JAreaLinkage,
JAreaSelect,
JSelectMultiple,
},
props: {
value: {
type: String,
default: '',
},
item: {
type: Object,
default: () => {},
required: true,
},
dictOptions: {
type: Object,
default: () => {},
required: false,
},
onlineForm: {
type: Object,
default: () => {},
required: false,
},
},
emits: ['update:value', 'change'],
setup(props, { emit }) {
// label
const labelTextMaxWidth = '120px';
const labelCol = {
style: {
'max-width': labelTextMaxWidth,
},
};
const single_mode = 'single';
let innerValue = ref<string | undefined | []>('');
let beginValue = ref('');
let endValue = ref('');
watch(
() => props.value,
() => {
if (isEasySelect()) {
//
innerValue.value = !!props.value ? props.value : undefined;
} else {
innerValue.value = props.value;
}
if (!props.value) {
beginValue.value = '';
endValue.value = '';
}
},
{ deep: true, immediate: true }
);
watch(
innerValue,
(newVal) => {
console.log('innerValue-change', newVal);
emit('update:value', newVal);
},
{ immediate: true }
);
watch(beginValue, (newVal) => {
emit('change', props.item.field + '_begin', newVal);
emit('update:value', '1');
});
watch(endValue, (newVal) => {
emit('change', props.item.field + '_end', newVal);
emit('update:value', '1');
});
function getDictOptionKey(item) {
console.log('ddictOptions', props.dictOptions);
if (item.dbField) {
return item.dbField;
} else {
return item.field;
}
}
function isEasySelect() {
let item = props.item;
if (!item) {
return false;
}
return item.view == 'list' || item.view == 'radio' || item.view == 'switch';
}
function getDictCode() {
let item = props.item;
if (item.dictTable && item.dictTable.length > 0) {
return item.dictTable + ',' + item.dictText + ',' + item.dictCode;
} else {
return item.dictCode;
}
}
function getSqlByDictCode() {
let item = props.item;
let { dictTable, dictCode, dictText } = item;
let temp = dictTable.toLowerCase();
let arr = temp.split('where');
let condition = '';
if (arr.length > 1) {
condition = ' where' + arr[1];
}
let sql = 'select ' + dictCode + " as 'value', " + dictText + " as 'text' from " + arr[0] + condition;
console.log('sql', sql);
return sql;
}
function getPopupFieldConfig(item) {
let { dictText: destFields, dictCode: orgFields } = item;
if (!destFields || destFields.length == 0) {
return [];
}
let arr1 = destFields.split(',');
let arr2 = orgFields.split(',');
let config: any[] = [];
for (let i = 0; i < arr1.length; i++) {
config.push({
target: arr1[i],
source: arr2[i],
});
}
return config;
}
function setFieldsValue<T>(values: T) {
let { dictText: destFields } = props.item;
let arr1 = destFields.split(',');
let field = arr1[0];
emit('change', field, values[field]);
}
function handleCategoryTreeChange(value) {
emit('update:value', value);
}
function getComponentProps(item, labelKey, rowKey) {
let props = {
labelKey,
rowKey,
};
let fieldExtendJson = item.fieldExtendJson;
if (fieldExtendJson) {
if (typeof fieldExtendJson == 'string') {
let json = JSON.parse(fieldExtendJson);
let extend = { ...json };
if (extend.text) {
props['labelKey'] = extend.text;
}
if (extend.store) {
props['rowKey'] = extend.store;
}
}
}
return props;
}
let userSelectProp = getComponentProps(props.item, 'realname', 'username');
console.log('userSelectProp', userSelectProp);
let depSelectProp = getComponentProps(props.item, 'departName', 'id');
function handleSelectChange(array) {
if (array && array.length > 0) {
emit('update:value', array.join(','));
} else {
emit('update:value', '');
}
}
return {
getPopupFieldConfig,
userSelectProp,
depSelectProp,
handleSelectChange,
setFieldsValue,
innerValue,
beginValue,
endValue,
isEasySelect,
getDictOptionKey,
getDictCode,
labelTextMaxWidth,
labelCol,
single_mode,
getSqlByDictCode,
handleCategoryTreeChange,
};
},
});
</script>
<style lang="less" scoped>
.group-query-strig {
width: 30px;
text-align: center;
display: inline-block;
}
/* 查询条件左对齐样式设置 */
.jeecg-online-search :deep(.ant-form-item-label) {
flex: 0 0 auto !important;
width: auto;
}
.jeecg-online-search :deep(.ant-form-item-control) {
max-width: 100%;
padding-right: 16px;
}
/* label显示宽度 超出显示... */
.jeecg-online-search :deep(.label-text) {
max-width: v-bind(labelTextMaxWidth);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
overflow-wrap: break-word;
}
</style>

View File

@ -0,0 +1,234 @@
<template>
<!-- 级联下拉框 form组件 暂且只在online使用 不对外提供api -->
<a-select show-search :filter-option="filterOption" :placeholder="placeholder" :value="selectedValue" @change="handleChange" allowClear style="width: 100%">
<a-select-option v-for="(item, index) in dictOptions" :key="index" :value="item.store">
<span style="display: inline-block; width: 100%" :title="item.label">{{ item.label }}</span>
</a-select-option>
</a-select>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
/**获取下拉选项*/
const SELECT_OPTIONS_URL = '/online/cgform/api/querySelectOptions';
export default defineComponent({
name: 'OnlineSelectCascade',
props: {
table: { type: String, default: '' },
txt: { type: String, default: '' },
store: { type: String, default: '' },
idField: { type: String, default: '' },
pidField: { type: String, default: '' },
pidValue: { type: String, default: '-1' },
origin: { type: Boolean, default: false },
condition: { type: String, default: '' },
value: { type: String, default: '' },
isNumber: { type: Boolean, default: false },
placeholder: { type: String, default: '请选择' },
},
emits: ['change', 'next'],
setup(props, { emit }) {
const { createMessage: $message } = useMessage();
//
const selectedValue = ref<any>('');
//
const dictOptions = ref<any[]>([]);
const optionsLoad = ref(true);
//
function handleChange(value) {
console.log('handleChange', value);
// value id
let temp = value || '';
emit('change', temp);
valueChangeThenEmitNext(temp);
}
// condition
watch(
() => props.condition,
(val) => {
optionsLoad.value = true;
if (val) {
loadOptions();
}
},
{ immediate: true }
);
// pidValue
watch(
() => props.pidValue,
(val) => {
if (val === '-1') {
dictOptions.value = [];
} else {
loadOptions();
}
}
);
//
watch(
() => props.value,
(newVal, oldVal) => {
console.log('值改变事件', newVal, oldVal);
if (!newVal) {
// value--
selectedValue.value = [];
if (oldVal) {
// oldVal
emit('change', '');
emit('next', '-1');
}
} else {
// value
selectedValue.value = newVal;
}
if (newVal && !oldVal) {
// options
handleFirstValueSetting(newVal);
}
},
{ immediate: true }
);
/**
* 第一次加载赋值
*/
async function handleFirstValueSetting(value) {
if (props.idField === props.store) {
// id
emit('next', value);
} else {
if (props.origin === true) {
// optionsoptions
await getSelfOptions();
valueChangeThenEmitNext(value);
} else {
// value
let arr = await loadValueText();
valueChangeThenEmitNext(value, arr);
}
}
}
function loadOptions() {
let params = getQueryParams();
if (props.origin === true) {
params['condition'] = props.condition;
} else {
params['pidValue'] = props.pidValue;
}
console.log('请求参数', params);
dictOptions.value = [];
defHttp.get({ url: SELECT_OPTIONS_URL, params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
dictOptions.value = [...res.result];
console.log('请求结果', res.result, dictOptions);
} else {
$message.warning('联动组件数据加载失败,请检查配置!');
}
});
}
function getQueryParams() {
let params = {
table: props.table,
txt: props.txt,
key: props.store,
idField: props.idField,
pidField: props.pidField,
};
return params;
}
function loadValueText() {
return new Promise((resolve) => {
if (!props.value) {
selectedValue.value = [];
resolve([]);
} else {
let params = getQueryParams();
if (props.isNumber === true) {
params['condition'] = `${props.store} = ${props.value}`;
} else {
params['condition'] = `${props.store} = '${props.value}'`;
}
defHttp.get({ url: SELECT_OPTIONS_URL, params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
resolve(res.result);
} else {
$message.warning('联动组件数据加载失败,请检查配置!');
resolve([]);
}
});
}
});
}
/**
* 获取下拉选项
*/
function getSelfOptions() {
return new Promise((resolve) => {
let index = 0;
(function next(index) {
if (index > 10) {
resolve([]);
}
let arr = dictOptions.value;
if (arr && arr.length > 0) {
resolve(arr);
} else {
setTimeout(() => {
next(index++);
}, 300);
}
})(index);
});
}
/**
* 值改变后 需要往外抛事件 触发下级节点的选项改变
*/
function valueChangeThenEmitNext(value, arr: any = []) {
if (value && value.length > 0) {
if (!arr || arr.length == 0) {
arr = dictOptions.value;
}
let selected = arr.filter((item) => item.store === value);
if (selected && selected.length > 0) {
let id = selected[0].id;
emit('next', id);
}
}
}
/**
* 下拉框筛选
* @param input
* @param option
*/
const filterOption = (input: string, option: any) => {
let labelIf = option.children()[0]?.children.toLowerCase().indexOf(input.toLowerCase()) >= 0;
if (labelIf) {
return true;
}
return false;
};
return {
selectedValue,
dictOptions,
handleChange,
filterOption,
};
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,301 @@
<template>
<BasicForm ref="onlineFormRef" @register="registerForm" />
</template>
<script>
import { useMessage } from '/@/hooks/web/useMessage';
import { computed, defineComponent, ref, unref, watch, nextTick, toRaw } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { SUBMIT_FLOW_ID, SUBMIT_FLOW_KEY, VALIDATE_FAILED } from '../../types/onlineRender';
import { defHttp } from '/@/utils/http/axios';
import { pick } from 'lodash-es';
import { useFormItems, getRefPromise } from '../../hooks/auto/useAutoForm';
import { Loading } from '/@/components/Loading';
import { loadFormFieldsDefVal } from '../../util/FieldDefVal';
const urlObject = {
optPre: '/online/cgform/api/form/',
urlButtonAction: '/online/cgform/api/doButton',
};
const baseUrl = '/online/cgform/api/subform';
export default {
name: 'OnlineSubForm',
components: {
BasicForm,
Loading,
},
props: {
properties: {
type: Object,
required: true,
},
mainId: {
type: String,
default: '',
},
table: {
type: String,
default: '',
},
formTemplate: {
type: Number,
default: 1,
},
requiredFields: {
type: Array,
default: [],
},
isUpdate: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
id: {
type: String,
default: '',
},
},
emits: ['formChange'],
setup(props, { emit }) {
console.log('进入online子表表单页面》》》》' + props.table);
// ref
const onlineFormRef = ref(null);
//
const formRendered = ref(false);
const { createMessage: $message } = useMessage();
const {
formSchemas,
defaultValueFields,
changeDataIfArray2String,
tableName,
dbData,
checkOnlyFieldValue,
fieldDisplayStatus,
createFormSchemas,
baseColProps,
labelCol,
wrapperCol,
labelWidth,
} = useFormItems(props, onlineFormRef);
//
const [registerForm, { setProps, validate, resetFields, setFieldsValue, getFieldsValue, updateSchema, scrollToField }] = useForm({
schemas: formSchemas,
showActionButtonGroup: false,
baseColProps: baseColProps,
// update-begin--author:liaozhiyang---date:20240429---forQQYUN-7632 labellabelwidth
labelWidth,
// update-end--author:liaozhiyang---date:20240429---forQQYUN-7632 labellabelwidth
// update-begin--author:liaozhiyang---date:20240105---forQQYUN-7499markdown
labelCol,
wrapperCol
// update-end--author:liaozhiyang---date:20240105---forQQYUN-7499markdown
});
const getFormItem = () => {
return new Promise((resolve, reject) => {
defHttp.get({ url: `online/cgform/api/getFormItem/${props.id}` }, { isTransformResponse: false }).then((res) => {
resolve(res.result);
});
});
}
let extConfigJson;
watch(
() => props.table,
() => {
tableName.value = props.table;
},
{ immediate: true }
);
//
watch(
() => props.properties,
async (valueObj) => {
//
console.log('主表properties改变', props.properties);
formRendered.value = false;
addFormChangeEvent();
// update-begin--author:liaozhiyang---date:20260318---for:issues/9414label
if (!extConfigJson) {
try {
const data = await getFormItem();
extConfigJson = JSON.parse(data.head.extConfigJson);
extConfigJson = {
formLabelLength: extConfigJson.formLabelLength,
formLabelLengthShow: extConfigJson.formLabelLengthShow,
};
} catch (error) {
console.log(error);
}
}
// update-end--author:liaozhiyang---date:20260318---for:issues/9414label
createFormSchemas(props.properties, props.requiredFields, checkOnlyFieldValue, extConfigJson);
formRendered.value = true;
},
{ deep: true, immediate: true }
);
//ID
watch(
() => props.mainId,
(valueObj) => {
//
console.log('主表ID改变', props.mainId);
// 100properties
setTimeout(() => {
resetSubForm();
}, 100);
},
{ immediate: true }
);
watch(
() => props.disabled,
(val) => {
setProps({ disabled: val });
}
);
/**
* 监听表单改变事件
*/
async function addFormChangeEvent() {
let formRefObject = await getRefPromise(onlineFormRef);
formRefObject.$formValueChange = (field, value, changeFormData) => {
let emitArgument = { [field]: value };
// update-begin--author:liaozhiyang---date:20260317---for:QQYUN-9441online
//
if(changeFormData){
setFieldsValue(changeFormData);
}
// update-end--author:liaozhiyang---date:20260317---for:QQYUN-9441online
emit('formChange', emitArgument);
};
}
/**
* 当前表单默认值逻辑-进入新增页面触发
*/
function handleDefaultValue() {
if (unref(props.isUpdate) === false) {
let fieldProperties = toRaw(defaultValueFields[tableName.value]);
loadFormFieldsDefVal(fieldProperties, (values) => {
setFieldsValue(values);
});
}
}
/**
* 当主表数据ID发生改变子表重现获取数据
* @returns {Promise<void>}
*/
async function resetSubForm() {
//TODO
// update-begin--author:sunjianlei --- date:20191111 --- for: -----------
//this.$emit('executeFillRule', {form:this.form, target: this})
// update-end--author:sunjianlei --- date:20191111 --- for: -----------
await getRefPromise(formRendered);
await resetFields();
handleDefaultValue();
const { table, mainId } = props;
if (!table || !mainId) {
return;
}
let values = await loadData(table, mainId);
dbData.value = values;
// VUEN-1033
await setFieldsValue(values);
}
function loadData(table, mainId) {
let url = `${baseUrl}/${table}/${mainId}`;
return new Promise((resolve, reject) => {
defHttp.get({ url }, { isTransformResponse: false }).then((res) => {
console.log(res);
if (res.success) {
resolve(res.result);
} else {
console.log(res.message);
reject();
}
});
}).finally(() => {
//resetFields()
dbData.value = '';
});
}
function getAll() {
return new Promise((resolve, reject) => {
validate()
.then(() => {
let formData = getFieldsValue();
formData = changeDataIfArray2String(formData);
resolve(formData);
})
.catch((e) => {
if (e.errorFields) {
e.scrollToField = () => e.errorFields[0] && scrollToField(e.errorFields[0].name, { behavior: 'smooth', block: 'center' });
}
reject(e);
});
});
}
//
function getFormEvent() {
let row = getFieldsValue();
if (!row.id) {
row.id = 'sub-change-temp-id';
}
return {
row,
target: context,
};
}
//
function setValues(values) {
setFieldsValue(values);
}
function executeFillRule() {
let formData = getFieldsValue();
let fieldProperties = toRaw(defaultValueFields[tableName.value]);
loadFormFieldsDefVal(fieldProperties, (values) => {
setFieldsValue(values);
}, formData);
}
const context = {
onlineFormRef,
baseColProps,
formSchemas,
registerForm,
setFieldsValue,
getFieldsValue,
getFormEvent,
setValues,
getAll,
executeFillRule,
sh: fieldDisplayStatus,
resetFields,
updateSchema,
};
return context;
},
};
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20240527---forTV360X-263tab
:deep(.ant-upload-list-item-container) {
&.ant-motion-collapse {
height: auto !important;
}
}
// update-end--author:liaozhiyang---date:20240527---forTV360X-263tab
</style>

View File

@ -0,0 +1,167 @@
<template>
<detail-form :schemas="detailFormSchemas" :data="subFormData" :span="formSpan"></detail-form>
</template>
<script lang="ts">
import { useMessage } from '/@/hooks/web/useMessage';
import { ref, watch } from 'vue';
import { BasicForm } from '/@/components/Form/index';
import { defHttp } from '/@/utils/http/axios';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import { Loading } from '/@/components/Loading';
import DetailForm from '../../extend/form/DetailForm.vue';
import { getDetailFormSchemas } from '../../hooks/auto/useAutoForm';
const baseUrl = '/online/cgform/api/subform';
export default {
name: 'OnlineSubFormDetail',
components: {
BasicForm,
Loading,
DetailForm,
},
props: {
properties: {
type: Object,
required: true,
},
mainId: {
type: String,
default: '',
},
table: {
type: String,
default: '',
},
formTemplate: {
type: Number,
default: 1,
},
},
emits: ['formChange'],
setup(props) {
//
const formRendered = ref(false);
const { createMessage: $message } = useMessage();
const tableName = ref('');
const subFormData = ref<any>({});
const { detailFormSchemas, createFormSchemas, formSpan } = getDetailFormSchemas(props);
watch(
() => props.table,
() => {
tableName.value = props.table;
},
{ immediate: true }
);
//
watch(
() => props.properties,
() => {
//
console.log('主表properties改变', props.properties);
formRendered.value = false;
createFormSchemas(props.properties);
formRendered.value = true;
},
{ deep: true, immediate: true }
);
//ID
watch(
() => props.mainId,
() => {
//
console.log('主表ID改变', props.mainId);
// 100properties
setTimeout(() => {
resetSubForm();
}, 100);
},
{ immediate: true }
);
/**
* 当主表数据ID发生改变子表重现获取数据
* @returns {Promise<void>}
*/
async function resetSubForm() {
await getRefPromise(formRendered);
subFormData.value = {};
const { table, mainId } = props;
if (!table || !mainId) {
return;
}
// update-begin--author:liaozhiyang---date:20260413---forQQYUN-14951
let data: any = (await loadData(table, mainId)) || {};
await fillLinkTableFields(data);
subFormData.value = data;
// update-end--author:liaozhiyang---date:20260413---forQQYUN-14951
}
// update-begin--author:liaozhiyang---date:20260413---forQQYUN-14951
/**
* 详情页一对一子表中根据关联记录字段(link_table)的值查询关联数据自动填充他表字段(link_table_field)的值
*/
async function fillLinkTableFields(data) {
const schemas = detailFormSchemas.value;
for (const schema of schemas) {
if (schema.view === 'link_table' && (schema as any).linkFields?.length > 0) {
const fieldValue = data[schema.field];
if (fieldValue) {
const valueField = (schema as any).dictCode || 'id';
const vals = String(fieldValue).split(',');
const params = {
pageSize: vals.length,
pageNo: 1,
superQueryMatchType: 'and',
superQueryParams: encodeURI(JSON.stringify([{ field: valueField, rule: 'in', val: fieldValue }])),
};
try {
const result = await defHttp.get({ url: '/online/cgform/api/getData/' + (schema as any).dictTable, params });
const records = result?.records || [];
for (const linkField of (schema as any).linkFields) {
const [formField, tableField] = linkField.split(',');
if (records.length > 0) {
data[formField] = records.map((r: any) => r[tableField] ?? '').join(',');
} else {
data[formField] = '';
}
}
} catch (e) {
console.warn('填充他表字段失败:', e);
}
}
}
}
}
// update-end--author:liaozhiyang---date:20260413---forQQYUN-14951
async function loadData(table, mainId) {
let url = `${baseUrl}/${table}/${mainId}`;
return new Promise((resolve, reject) => {
defHttp.get({ url }, { isTransformResponse: false }).then((res) => {
console.log(res);
if (res.success) {
resolve(res.result);
} else {
reject(res.message);
}
});
}).catch((e) => {
console.warn('子表获取数据失败:', e);
return Promise.resolve({});
});
}
return {
detailFormSchemas,
subFormData,
formSpan,
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,187 @@
<template>
<div class="cust-onl-form">
<a-spin :spinning="spinLoading">
<online-form
ref="onlineFormCompRef"
:id="formId"
:disabled="disabled"
:form-template="formTemplate"
:isTree="isTreeForm"
:pidField="pidFieldName"
:taskId="taskId"
@rendered="renderSuccess"
@success="handleSuccess"
@validate="validateBack"
@close="handleClose"
>
<template #bottom>
<div style="width: 100%; text-align: center; margin-top: 5px" v-if="!disabled && !spinLoading && showSubmitButton">
<a-button preIcon="ant-design:check" style="width: 126px" type="primary" @click="handleSubmit" :loading="buttonLoading"> </a-button>
</div>
<!-- 任务办理意见 -->
<TaskOpinionList class="task-opinion" :taskId="taskOriginalId" :procInsId="procInsId" :processTabType="processTabType" />
</template>
</online-form>
</a-spin>
</div>
</template>
<script>
/**
* 工作流online表单调--中转的意义在于在此查询表单信息
*/
import OnlineForm from './OnlineForm.vue';
import { defineComponent, ref, watch, nextTick, computed } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import TaskOpinionList from "@/views/super/online/cgform/auto/comp/TaskOpinionList.vue";
import {useGlobSetting} from "@/hooks/setting";
export default defineComponent({
name: 'ProcessOnlineForm',
inheritAttrs: false,
components: {
TaskOpinionList,
OnlineForm,
},
props: {
dataId: {
type: String,
default: '',
},
tableName: {
type: String,
default: '',
},
taskId: {
type: String,
default: '',
},
taskOriginalId: {
type: String,
default: '',
},
procInsId: {
type: String,
default: '',
},
processTabType: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
},
emits: ['success','validate'],
setup(props, { emit }) {
const onlineFormCompRef = ref();
const formId = ref('');
const formTemplate = ref(1);
const isTreeForm = ref(false);
const pidFieldName = ref('');
const spinLoading = ref(false);
//
watch(
() => props.tableName,
(val) => {
if (!val) {
return;
}
loadFormItems();
},
{ immediate: true }
);
//update-begin-author:liusq---date:2026-01-20--for: online
const showSubmitButton = computed(() => {
return !useGlobSetting().useNewTaskModal;
});
//update-end-author:liusq---date:2026-01-20--for: online
//
async function loadFormItems() {
spinLoading.value = true;
const url = `/online/cgform/api/getFormItemBytbname/${props.tableName}`;
const params = { taskId: props.taskId };
try {
let result = await defHttp.get({ url, params });
console.log('动态表单查询结果是:', result);
formId.value = result.head.id;
formTemplate.value = Number(result.head.formTemplate || 1);
isTreeForm.value = result.head.isTree === 'Y';
pidFieldName.value = result.head.treeParentIdField || '';
await nextTick(async () => {
let myForm = await getRefPromise(onlineFormCompRef);
myForm.createRootProperties(result);
});
} catch (e) {
console.error('流程表单查询异常', e);
}
}
//
async function renderSuccess() {
let myForm = await getRefPromise(onlineFormCompRef);
spinLoading.value = false;
myForm.show(true, {
id: props.dataId,
});
}
//
const buttonLoading = ref(false);
async function handleSubmit(showTip = true) {
buttonLoading.value = true;
onlineFormCompRef.value.handleSubmit(showTip);
}
function handleSuccess(data = null) {
buttonLoading.value = false;
emit('success', data);
}
//
function handleValidate() {
onlineFormCompRef.value.handleValidate();
}
//
function validateBack(data = null) {
emit('validate', data);
}
function handleClose() {
buttonLoading.value = false;
}
return {
onlineFormCompRef,
formId,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
handleSuccess,
handleClose,
handleSubmit,
handleValidate,
validateBack,
buttonLoading,
spinLoading,
showSubmitButton,
};
},
});
</script>
<style scoped>
.cust-onl-form .ant-input-disabled {
background-color: #fff;
color: #000;
}
.cust-onl-form .ant-select-disabled .ant-select-selection {
background: #fff;
color: #000;
}
.cust-onl-form .ant-input-number-disabled {
background-color: #fff;
color: #000;
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<!-- 意见区域 -->
<div class="opinion-area" v-if="opinionPrint">
<!-- 标题头 -->
<div class="opinion-header">
<h3>审批意见</h3>
<div class="header-line"></div>
</div>
<!-- 意见列表 -->
<div class="opinion-list">
<a-list itemLayout="vertical" :split="false">
<template v-for="(item, index) in bpmLogList" :key="index">
<a-list-item class="opinion-item">
<a-list-item-meta :description="item.remarks">
<template #title>
<div class="opinion-meta">
<span class="user-name">{{ item.opUserName }}</span>
<span class="task-tag">[{{ item.taskName }}]</span>
<span class="op-time">{{ formatTime(item.opTime) }}</span>
</div>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</div>
</div>
</template>
<script lang="ts" setup>
//
import {inject, ref, watchEffect} from 'vue';
import { taskTransInfo } from '@/views/super/bpm/process/personalOffice/myHandleTask/task.handle.api';
import dayjs from 'dayjs';
//
const props = defineProps({
taskId: {
type: String,
default: '',
},
procInsId: {
type: String,
default: '',
},
processTabType: {
type: String,
default: '',
}
});
const opinionPrint = inject('opinionPrint');
// /
const bpmLogList = ref([]);
//
watchEffect(() => {
props.procInsId && getTaskTransInfo();
});
//
async function getTaskTransInfo() {
let taskType = props.processTabType || 'history';
//-runtaskId historyprocInstId
let params = { taskId: props.taskId, procInstId: props.procInsId };
let data = await taskTransInfo(params, taskType);
bpmLogList.value = data.bpmLogList;
}
//
function formatTime(time) {
if (!time) return '';
return dayjs(time).format('YYYY-MM-DD HH:mm');
}
</script>
<style lang="less" scoped>
/* 意见区域整体样式 */
.opinion-area {
margin-top: 24px;
margin-bottom: 24px;
padding: 16px;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* 标题头样式 */
.opinion-header {
margin-bottom: 16px;
position: relative;
}
.opinion-header h3 {
color: #333;
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.header-line {
height: 2px;
background: linear-gradient(to right, #1890ff, transparent);
width: 100%;
}
/* 意见列表样式 */
.opinion-list {
overflow-y: auto;
padding-right: 8px;
}
/* 单个意见项样式 */
.opinion-item {
padding: 12px 16px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 6px;
transition: all 0.3s;
}
.opinion-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* 用户信息样式 */
.opinion-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.user-name {
font-weight: 500;
color: #333;
}
.task-tag {
color: #ff6d75;
font-size: 12px;
}
.op-time {
color: #999;
font-size: 12px;
}
/* 意见内容样式 */
:deep(.ant-list-item-meta-description) {
padding-left: 42px;
color: #555;
line-height: 1.6;
}
@media print {
body * {
visibility: hidden;
}
.daily-opinion,
.daily-opinion * {
visibility: visible;
}
.daily-opinion {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
}
table {
page-break-inside: avoid;
}
ul,
ol {
page-break-inside: avoid;
}
</style>

View File

@ -0,0 +1,177 @@
import InputWidget from './impl/InputWidget';
import { FormSchema } from '/@/components/Form';
import DateWidget from './impl/DateWidget';
import SelectWidget from './impl/SelectWidget';
import PasswordWidget from './impl/PasswordWidget';
import FileWidget from './impl/FileWidget';
import ImageWidget from './impl/ImageWidget';
import TextAreaWidget from './impl/TextAreaWidget';
import SelectMultiWidget from './impl/SelectMultiWidget';
import SelectSearchWidget from './impl/SelectSearchWidget';
import PopupWidget from './impl/PopupWidget';
// update-begin--author:liaozhiyang---date:20240130---forQQYUN-7961popupDict
import PopupDictWidget from './impl/PopupDictWidget';
// update-end--author:liaozhiyang---date:20240130---forQQYUN-7961popupDict
import TreeCategoryWidget from './impl/TreeCategoryWidget';
import SelectDepartWidget from './impl/SelectDepartWidget';
import SelectUserWidget from './impl/SelectUserWidget';
import EditorWidget from './impl/EditorWidget';
import MarkdownWidget from './impl/MarkdownWidget';
import PcaWidget from './impl/PcaWidget';
import AreaLinkage from './impl/AreaLinkage';
import TreeSelectWidget from './impl/TreeSelectWidget';
import RadioWidget from './impl/RadioWidget';
import CheckboxWidget from './impl/CheckboxWidget';
import SwitchWidget from './impl/SwitchWidget';
import TimeWidget from './impl/TimeWidget';
import LinkDownWidget from './impl/LinkDownWidget';
import SlotWidget from './impl/SlotWidget';
import NumberWidget from './impl/NumberWidget';
import LinkTableWidget from './impl/LinkTableWidget'
import LinkTableFieldWidget from './impl/LinkTableFieldWidget'
import LinkTableForQueryWidget from './impl/LinkTableForQueryWidget'
import CascaderPcaForQueryWidget from './impl/CascaderPcaForQueryWidget'
import SelectUser2Widget from './impl/SelectUser2Widget'
import RangeWidget from "./impl/RangeWidget";
export default class FormSchemaFactory {
static createFormSchema(key, data, queryItem) {
let view = data.view;
switch (view) {
case 'password':
//2.
return new PasswordWidget(key, data);
case 'list':
//3.
return new SelectWidget(key, data);
case 'radio':
// 4.
return new RadioWidget(key, data);
case 'checkbox':
// 5.
return new CheckboxWidget(key, data);
case 'date':
case 'datetime':
// 6.
// 7.
return new DateWidget(key, data, queryItem);
case 'time':
// 8
return new TimeWidget(key, data);
case 'file':
// 9.
return new FileWidget(key, data);
case 'image':
// 10.
return new ImageWidget(key, data);
case 'textarea':
// 11.
return new TextAreaWidget(key, data);
case 'list_multi':
// 12.
return new SelectMultiWidget(key, data);
case 'sel_search':
// 13.
return new SelectSearchWidget(key, data);
case 'popup':
// 14. popup
return new PopupWidget(key, data);
case 'cat_tree':
// 15.
return new TreeCategoryWidget(key, data);
case 'sel_depart':
// 16.
return new SelectDepartWidget(key, data);
case 'sel_user':
// 17.
return new SelectUserWidget(key, data);
case 'umeditor':
// 18.
return new EditorWidget(key, data);
case 'markdown':
// 19.MarkDown
return new MarkdownWidget(key, data);
case 'pca':
// 20.
// update-begin--author:liaozhiyang---date:20240607---forTV360X-501
// return new PcaWidget(key, data);
return new AreaLinkage(key, data);
// update-end--author:liaozhiyang---date:20240607---forTV360X-501
case 'link_down':
// 21.
return new LinkDownWidget(key, data);
case 'sel_tree':
// 22.
return new TreeSelectWidget(key, data);
case 'switch':
// 23.
return new SwitchWidget(key, data);
case 'link_table':
// 24.
return new LinkTableWidget(key, data);
case 'link_table_field':
// 25.
return new LinkTableFieldWidget(key, data);
// update-begin--author:liaozhiyang---date:20240130---forQQYUN-7961popupDict
case 'popup_dict':
// 14. popup
return new PopupDictWidget(key, data);
// update-end--author:liaozhiyang---date:20240130---forQQYUN-7961popupDict
case 'slot':
// slot
return new SlotWidget(key, data);
case 'LinkTableForQuery':
return new LinkTableForQueryWidget(key, data);
case 'CascaderPcaForQuery':
return new CascaderPcaForQueryWidget(key, data, queryItem);
case 'select_user2':
return new SelectUser2Widget(key, data);
case 'rangeDate':
case 'rangeTime':
case 'rangeNumber':
return new RangeWidget(key, data);
case 'hidden':
//
return new InputWidget(key, data).isHidden();
default:
if (data.type == 'number') {
return new NumberWidget(key, data);
} else {
//1.
return new InputWidget(key, data);
}
}
}
static createSlotFormSchema(key, data) {
let slotFs = new SlotWidget(key, data);
let view = data.view;
if ('date' == view) {
slotFs.groupDate();
} else if ('datetime' == view) {
slotFs.groupDatetime();
} else if ('time' == view) {
// update-begin--author:liaozhiyang---date:20240517---forQQYUN-9348online
slotFs.groupTime();
// update-end--author:liaozhiyang---date:20240517---forQQYUN-9348online
} else {
let type = data.type;
if (type == 'number' || type == 'integer') {
slotFs.groupNumber();
}
}
return slotFs;
}
/**
* 表单ID 默认是隐藏的
*/
static createIdField(): FormSchema {
return {
label: '',
field: 'id',
component: 'Input',
show: false,
};
}
}

View File

@ -0,0 +1,469 @@
import {computed, watch} from 'vue'
import { FormSchema, Rule } from '/@/components/Form';
import { FieldExtends, POP_CONTAINER } from '../../../types/onlineRender';
import { LABELLENGTH } from '../../../util/constant';
import {replaceUserInfoByExpression} from "@/utils/common/compUtils";
/**
* 1.部门选择/用户选择 单选配置
* 控件类
*/
export default abstract class IFormSchema {
_data;
field: string;
label: string;
labelLength: number;
formRef: any;
hidden: boolean;
order: number;
required: boolean;
onlyValidator: any;
hasChange: boolean;
pre: string;
setFieldsValue: any;
schemaProp: any;
searchForm: boolean;
disabled: boolean;
popContainer: string;
inPopover: boolean;
constructor(key, data) {
// data
this._data = data;
this.field = key;
this.label = data.title;
this.hidden = false;
this.order = data.order || 999;
this.required = false;
this.onlyValidator = '';
this.setFieldsValue = '';
this.hasChange = true;
if (key.indexOf('@') > 0) {
this.pre = key.substring(0, key.indexOf('@') + 1);
} else {
this.pre = '';
}
this.schemaProp = {};
this.searchForm = false;
this.disabled = false;
this.popContainer = '';
this.handleWidgetAttr(data);
this.inPopover = false;
this.labelLength = LABELLENGTH;
this.initLabelLength();
}
/**
* 获取最终的表单配置项外面获取调用此方法
*/
getFormItemSchema(): FormSchema {
let schema = this.getItem();
this.addDefaultChangeEvent(schema);
return schema;
}
/**
* 获取表单配置子类重写此方法
*/
getItem(): FormSchema {
let fs: FormSchema = {
field: this.field,
label: this.label,
labelLength: this.labelLength,
component: 'Input',
itemProps:{
labelCol:{
class: 'online-form-label'
}
}
};
let rules = this.getRule();
if (rules.length > 0 && this.onlyValidator) {
fs['rules'] = rules;
}
if (this.hidden === true) {
fs['show'] = false;
}
return fs;
}
/**
* 设置表单ref
* popup分类树需要关联设置其他表单值的时候用到
* @param ref
*/
setFormRef(ref) {
this.formRef = ref;
}
/**
* 设置表单元素隐藏
*/
isHidden() {
this.hidden = true;
return this;
}
/**
* 设置是否必填项
* @param array
*/
isRequired(array) {
// TODO
if (array && array.length > 0) {
if (array.indexOf(this.field) >= 0) {
this.required = true;
}
}
return this;
}
/**
* 初始化 label长度
*/
initLabelLength(){
let obj = this.getExtendData()
if(obj && obj.labelLength){
this.labelLength = obj.labelLength;
}
}
/**
* 获取扩展参数
*/
getExtendData() {
let extend: FieldExtends = {};
let { fieldExtendJson } = this._data;
if (fieldExtendJson) {
if (typeof fieldExtendJson == 'string') {
try {
let json = JSON.parse(fieldExtendJson);
extend = { ...json };
} catch (e) {
console.error(e);
}
}
}
return extend;
}
/***
* 获取和此字段相关的其他字段 需要设置其为隐藏
*/
getRelatedHideFields(): string[] {
return [];
}
/**
* placeholder
*/
getPlaceholder(view) {
let text = '请输入';
// update-begin--author:liaozhiyang---date:20240521---forTV360X-218
if (
[
'list',
'radio',
'checkbox',
'date',
'datetime',
'time',
'list_multi',
'sel_search',
'popup',
'cat_tree',
'sel_depart',
'sel_user',
'pca',
'link_down',
'sel_tree',
'switch',
'link_table',
'link_table_field',
'popup_dict',
'LinkTableForQuery',
'CascaderPcaForQuery',
'select_user2',
'rangeDate',
'rangeTime',
'rangeNumber',
].includes(view)
) {
text = '请选择';
} else if (['file', 'image'].includes(view)) {
text = '请上传';
}
// update-end--author:liaozhiyang---date:20240521---forTV360X-218
return text + this.label;
}
/**
* 唯一校验
*/
setOnlyValidateFun(validateFun) {
if (validateFun) {
this.onlyValidator = async (rule, value) => {
let error = await validateFun(rule, value);
if (!error) {
return Promise.resolve();
} else {
return Promise.reject(error);
}
};
}
}
/**
* 获取校验规则
*/
getRule(): any[] {
let rules: Rule[] = [];
const { view, errorInfo, pattern, type, fieldExtendJson } = this._data;
if (this.required === true) {
let msg = this.getPlaceholder(view);
// update-begin--author:liaozhiyang---date:20240520---forTV360X-80
if (fieldExtendJson) {
const json = JSON.parse(fieldExtendJson);
if (json.validateError) {
msg = json.validateError;
}
}
// update-end--author:liaozhiyang---date:20240520---forTV360X-80
if (errorInfo) {
msg = errorInfo;
}
if (view == 'sel_depart' || view == 'sel_user') {
// 使 requiredtrue
this.schemaProp['required'] = true;
// update-begin--author:liaozhiyang---date:20240429---forQQYUN-9109online使label*
rules.push({ required: true, message: msg });
// update-end--author:liaozhiyang---date:20240429---forQQYUN-9109online使label*
} else {
rules.push({ required: true, message: msg });
}
}
if ('sel_user' == view) {
if (pattern === 'only' && this.onlyValidator) {
rules.push({ validator: this.onlyValidator });
}
}
if ('list' === view || 'radio' === view || 'markdown' === view || 'pca' === view || view.indexOf('sel') >= 0 || 'time' === view) {
return rules;
}
if (view.indexOf('upload') >= 0 || view.indexOf('file') >= 0 || view.indexOf('image') >= 0) {
return rules;
}
if (pattern) {
if (pattern === 'only') {
if (this.onlyValidator) {
rules.push({ validator: this.onlyValidator });
}
} else if (pattern === 'z') {
if (type == 'number' || type == 'integer') {
// this.onlyInteger=true TODO
} else {
rules.push({ pattern: /^-?\d+$/, message: '请输入整数' });
}
} else {
let msg = errorInfo || '正则校验失败';
let reg
try {
reg = new RegExp(pattern);
if (!reg) {
reg = pattern;
}
} catch {
reg = pattern;
}
rules.push({ pattern: reg, message: msg });
}
}
return rules;
}
/**
* 添加默认的change事件
* @param schema
*/
addDefaultChangeEvent(schema) {
if (this.hasChange) {
if (!schema.componentProps) {
schema.componentProps = {};
}
//update-begin-author:taoyan date:2022-5-24 for: VUEN-1095
if (this.disabled == true) {
schema.componentProps.disabled = true;
}
//update-end-author:taoyan date:2022-5-24 for: VUEN-1095
if (!schema.componentProps.hasOwnProperty('onChange')) {
schema.componentProps['onChange'] = (value, formData) => {
if (value instanceof Event) {
// valueevent
value = (value.target as any).value;
}
// value
if (value instanceof Array) {
value = value.join(',');
}
// VUEN-1467vue3 tab ---
if(!this.formRef || !this.formRef.value || !this.formRef.value.$formValueChange){
console.log('当前表单无法触发change事件,field'+this.field)
}else{
this.formRef.value.$formValueChange(this.field, value, formData)
}
};
// update-begin--author:liaozhiyang---date:20251011---forissues/8791jspopuponlChange()
if (schema.component === 'JPopup') {
schema.componentProps['onPopUpChange'] = schema.componentProps['onChange']
}
// update-end--author:liaozhiyang---date:20251011---forissues/8791jspopuponlChange()
}
}
// schemaProp
Object.keys(this.schemaProp).map((k) => {
schema[k] = this.schemaProp[k];
});
}
noChange() {
this.hasChange = false;
}
updateField(field) {
this.field = field;
}
/**
* 高级查询 没有表单ref对象 手动设置setFieldValue方法用于 popup设置表单值
*/
setFunctionForFieldValue(func) {
if (func) {
this.setFieldsValue = func;
}
}
asSearchForm() {
this.searchForm = true;
}
/**获取modal作为类下拉组件pop的父容器*/
getModalAsContainer() {
let ele = this.getPopContainer();
// update-begin--author:liaozhiyang---date:20231205---forQQYUN-7150online
if (ele != 'body') {
const elems = document.querySelectorAll(ele);
if (elems && elems.length > 1) {
const data: HTMLElement[] = [];
elems.forEach((item: HTMLElement) => {
if (!(item.offsetWidth == 0 && item.offsetHeight == 0)) {
data.push(item);
}
});
if (data.length === 1) {
return data[0];
}
}
}
// update-end--author:liaozhiyang---date:20231205---forQQYUN-7150online
return document.querySelector(ele);
}
/**区分modal表单和查询表单*/
getPopContainer() {
if (this.searchForm === true) {
return 'body';
} else if(this.inPopover === true){
return `.${this.popContainer}`;
}else if(this.popContainer){
return `.${this.popContainer} .ant-modal-content`
}else {
return POP_CONTAINER;
}
}
handleWidgetAttr(data) {
if (data.ui) {
if (data.ui.widgetattrs) {
if (data.ui.widgetattrs.disabled == true) {
this.disabled = true;
}
}
}
}
/**
* 设置 popContainer
*/
setCustomPopContainer(modalClass){
this.popContainer = modalClass;
}
//update-begin-author:taoyan date:2022-8-5 for: /
//
getLinkFieldInfo():any{
return '';
}
// 1.
setOtherInfo(_arg){
}
//update-end-author:taoyan date:2022-8-5 for: /
//
isInPopover(){
this.inPopover = true;
}
handleDictTableParams() {
if (!this.formRef.value) {
return
}
const dictTable = this._data.dictTable as string
if (!dictTable) {
return
}
const matches = dictTable.match(/\${([^}]+)}/g)
if (!matches || matches.length == 0) {
return
}
// ${}
const keys = matches.map((item: string) => item.replace('${', '').replace('}', ''))
const values = computed(() => {
const formModel = this.formRef.value.formModel
return keys.map((key) => formModel[key]).join('');
})
let timer: ReturnType<typeof setTimeout> | null = null;
watch(values, () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
const formModel = this.formRef.value.formModel
// ${xxx}
let tempDictTable = dictTable.replace(/\${([^}]+)}/g, (_$0, $1) => {
if (formModel[$1] == null) {
return ''
}
return formModel[$1]
});
this.updateDictTable(tempDictTable)
}, 150)
}, {immediate: true})
}
updateDictTable(_dictTable: string) {
console.log('请在子类实现 updateDictTable 方法')
}
/**
* 获取表字典的编码可替换系统变量
* @param dictTable
* @param dictText
* @param dictCode
*/
genDictTableCode(dictTable: string, dictText: string, dictCode: string) {
//
dictTable = replaceUserInfoByExpression(dictTable)
return encodeURI(`${dictTable},${dictText},${dictCode}`);
}
}

View File

@ -0,0 +1,26 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 省市区
*/
export default class PcaWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
// update-begin--author:liaozhiyang---date:20260204---for:QQYUN-14694online
const extendData: any = this.getExtendData();
const componentProps: any = {}
if (extendData.displayLevel) {
componentProps.displayLevel = extendData.displayLevel;
componentProps.saveCode = extendData.displayLevel === 'all' ? 'region' : componentProps.displayLevel;
}
// update-end--author:liaozhiyang---date:20260204---for:QQYUN-14694online
return Object.assign({}, item, {
component: 'JAreaLinkage',
componentProps: {
saveCode: 'region',
...componentProps,
},
});
}
}

View File

@ -0,0 +1,38 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 表单设计器-省市区查询
*/
export default class CascaderPcaForQueryWidget extends IFormSchema {
schema: Recordable;
//
areaLevel: number;
//
allowChangeLevel: boolean;
constructor(key: string, data: Recordable, queryItem: Recordable) {
super(key, data);
this.schema = data
this.areaLevel = data['areaLevel'] ?? 3;
//
this.allowChangeLevel = ['eq', 'ne'].includes(queryItem?.rule)
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'CascaderPcaInFilter',
componentProps:{
areaLevel: this.areaLevel,
allowChangeLevel: this.allowChangeLevel,
placeholder: '请选择…',
style: {
width: '100%',
}
}
});
}
}

View File

@ -0,0 +1,60 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* checkbox
*/
export default class CheckboxWidget extends IFormSchema {
/*title-value*/
options: any[];
constructor(key, data) {
super(key, data);
this.options = this.getOptions(data['enum']);
}
setFormRef(ref) {
super.setFormRef(ref);
this.handleDictTableParams();
}
updateDictTable(dictTable: string) {
this.formRef.value.updateSchema(({
field: this.field,
componentProps: {
options:[],
dictCode: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode),
}
}))
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'JCheckbox',
componentProps: {
options: this.options,
triggerChange: true,
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
useDicColor: true,
// update-end--author:liaozhiyang---date:20230110---forQQYUN-7799
},
});
}
getOptions(array) {
if (!array || array.length == 0) {
return [];
}
let arr: any[] = [];
for (let item of array) {
arr.push({
value: item.value,
label: item.title,
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
color: item.color,
// update-end--author:liaozhiyang---date:20230110---forQQYUN-7799
});
}
return arr;
}
}

View File

@ -0,0 +1,59 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
enum DateFormat {
datetime = 'YYYY-MM-DD HH:mm:ss',
date = 'YYYY-MM-DD',
}
/**
* 日期时间
*/
export default class DateWidget extends IFormSchema {
format: string;
showTime: boolean;
picker: string | undefined;
allowSelectRange: boolean;
constructor(key, data, queryItem) {
super(key, data);
this.format = DateFormat[data.view];
this.showTime = data.view == 'date' ? false : true;
// update-begin--author:liaozhiyang---date:20240430---forissues/6094online ()
let fieldExtendJson = data.fieldExtendJson;
if (data.view == 'date' && fieldExtendJson) {
fieldExtendJson = JSON.parse(fieldExtendJson);
if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') {
this.picker = fieldExtendJson.picker;
} else {
this.picker = undefined;
}
}
// update-end--author:liaozhiyang---date:20240430---forissues/6094online ()
//
this.allowSelectRange = ['eq', 'ne'].includes(queryItem?.rule)
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'DatePickerInFilter',
componentProps: {
placeholder: `请选择${this.label}`,
showTime: this.showTime,
valueFormat: this.format,
allowSelectRange: this.allowSelectRange,
// update-begin--author:liaozhiyang---date:20240430---forissues/6094online ()
picker: this.picker,
// update-end--author:liaozhiyang---date:20240430---forissues/6094online ()
style: {
width: '100%',
},
getPopupContainer: (_node) => {
return this.getModalAsContainer();
},
},
});
}
}

View File

@ -0,0 +1,25 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 富文本
*/
export default class EditorWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'JEditor',
componentProps: {
//update-begin-author:taoyan date:2022-6-1 for: VUEN-1159
options: {
auto_focus: false,
},
//update-end-author:taoyan date:2022-6-1 for: VUEN-1159
// fileMax:1,
// showImageUpload:false,
// width:"966px",
// height:"200px"
},
});
}
}

View File

@ -0,0 +1,26 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 文件
*/
export default class FileWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JUpload',
componentProps,
});
}
getComponentProps() {
let json = this.getExtendData();
if (json && json.uploadnum) {
return {
maxCount: Number(json.uploadnum),
};
}
return {};
}
}

View File

@ -0,0 +1,28 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
import { UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload';
/**
* 图片
*/
export default class ImageWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JUpload',
componentProps,
});
}
getComponentProps() {
let props = {
fileType: UploadTypeEnum.image,
};
let json = this.getExtendData();
if (json && json.uploadnum) {
props['maxCount'] = Number(json.uploadnum);
}
return props;
}
}

View File

@ -0,0 +1,15 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 输入框
*/
export default class InputWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
if (this.hidden === true) {
item['show'] = false;
}
return item;
}
}

View File

@ -0,0 +1,108 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 下拉联动- 原理是
* 使用JDictSelectTag组件(2022-03-09测试可行版 后续如有改动请注意)
* 监听表单的change事件清空下级表单值并改变props
* 问题在于1.没有code的时候 不需要设置选项
* 优势在于 可以不考虑组件位置但是需要改后台接口
*/
export default class LinkDownWidget extends IFormSchema {
/*title-value*/
options: any[];
next: string;
type: string;
table: string;
txt: string;
store: string;
pidField: string;
idField: string;
origin: boolean;
condition: string;
constructor(key, data) {
super(key, data);
const { dictTable, dictText, dictCode, pidField, idField, origin, condition } = data;
this.table = dictTable;
this.txt = dictText;
this.store = dictCode;
this.idField = idField;
this.pidField = pidField;
this.origin = origin;
this.condition = condition;
//
this.options = [];
this.next = data.next || '';
this.type = data.type;
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'OnlineSelectCascade',
componentProps,
});
}
getComponentProps() {
let baseProp = {
table: this.table,
txt: this.txt,
store: this.store,
pidField: this.pidField,
idField: this.idField,
origin: this.origin,
pidValue: '-1',
style: {
width: '100%',
},
onChange: (value) => {
console.log('级联组件-onChange', value);
this.valueChange(value);
},
onNext: (pidValue) => {
console.log('级联组件-onNext', pidValue);
this.nextOptionsChange(pidValue);
},
};
if (this._data.origin === true) {
baseProp['condition'] = this.condition;
}
return baseProp;
}
async nextOptionsChange(pidValue) {
if (!this.formRef) {
console.error('表单引用找不到');
return;
}
if (!this.next) {
return;
}
let ref = this.formRef.value;
await ref.updateSchema({
field: this.next,
componentProps: {
pidValue,
},
});
}
async valueChange(value) {
if (!this.formRef) {
console.error('表单引用找不到');
return;
}
// update-begin--author:liaozhiyang---date:20240717---forTV360X-1856jsonchang
let ref = this.formRef.value;
// formchange
ref.$formValueChange(this.field, value);
if (this.next) {
// value
await ref.setFieldsValue({ [this.next]: '' });
}
// update-end--author:liaozhiyang---date:20240717---forTV360X-1856jsonchang
}
}

View File

@ -0,0 +1,42 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 他表字段
*/
export default class LinkTableFieldWidget extends IFormSchema {
dictTable: string;
dictText: string;
constructor(key, data) {
super(key, data);
this.dictTable = data['dictTable'];
this.dictText = data['dictText'];
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
componentProps: {
readOnly: true,
allowClear: false,
disabled: true,
style:{
background: 'none',
color:'rgba(0, 0, 0, 0.85)',
border:'none'
}
}
});
return item;
}
/**
* 获取他表字段的关联信息
*/
getLinkFieldInfo(){
let arr = [this.dictTable, `${this.field},${this.dictText}`];
return arr;
}
}

View File

@ -0,0 +1,35 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 表单设计器-关联记录查询 使用下拉搜索
*/
export default class LinkTableForQueryWidget extends IFormSchema {
code: string;
titleField: string;
multi: boolean;
constructor(key, data) {
super(key, data);
this.code = data['code'];
this.titleField = data['titleField'];
this.multi = data['multi']||false;
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'LinkTableForQuery',
componentProps:{
code: this.code,
multi: this.multi,
field: this.titleField,
style: {
width: '100%',
}
}
});
}
}

View File

@ -0,0 +1,72 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 关联记录
*/
export default class LinkTableWidget extends IFormSchema {
dictTable: string;
dictText: string;
dictCode: string;
view: string;
componentString: string;
linkFields: Array<string>;
constructor(key, data) {
super(key, data);
this.dictTable = data.dictTable;
this.dictText = data.dictText;
this.dictCode = data.dictCode;
this.view = data.view;
this.componentString = ''
this.linkFields = []
}
getItem(): FormSchema {
let item = super.getItem();
const componentProps = this.getComponentProps()
return Object.assign({}, item, {
component: this.componentString,
componentProps: componentProps
});
}
getComponentProps() {
let props = {
textField: this.dictText,
tableName: this.dictTable,
valueField: this.dictCode,
};
let extend = this.getExtendData();
//
if (extend.multiSelect) {
props['multi'] = true;
}else{
props['multi'] = false;
}
//
if (extend.imageField) {
props['imageField'] = extend.imageField;
}else{
props['imageField'] = ''
}
//
if (extend.showType=='select') {
this.componentString = 'LinkTableSelect'
let popContainer = this.getPopContainer();
props['popContainer'] = popContainer
}else{
this.componentString = 'LinkTableCard'
}
if(this.linkFields.length>0){
props['linkFields'] = this.linkFields;
}
return props;
}
//
setOtherInfo(arr){
// [",",","]
this.linkFields = arr;
}
}

View File

@ -0,0 +1,17 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* markdown
*/
export default class MarkdownWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'JMarkdownEditor',
componentProps: {
// height: 300,
},
});
}
}

View File

@ -0,0 +1,49 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 输入框-数字
*/
export default class NumberWidget extends IFormSchema {
dbPointLength: number;
constructor(key, data) {
super(key, data);
this.dbPointLength = data.dbPointLength;
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
const safeIntRule = {
validator: (_rule, value) => {
if (value !== null && value !== undefined && value !== '') {
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
return Promise.reject(`数值超出安全范围(${Number.MIN_SAFE_INTEGER}~${Number.MAX_SAFE_INTEGER}),精度将丢失,请重新输入`);
}
}
return Promise.resolve();
},
};
const existingRules = item.rules || [];
return Object.assign({}, item, {
component: 'InputNumber',
componentProps,
// update-begin--author:liaozhiyang---date:20260413---for:QQYUN-9790onlinejs
rules: [...existingRules, safeIntRule],
// update-end--author:liaozhiyang---date:20260413---for:QQYUN-9790onlinejs
});
}
getComponentProps() {
const props = {
style: {
width: '100%',
},
};
if (this.dbPointLength >= 0) {
props['precision'] = this.dbPointLength;
}
return props;
}
}

View File

@ -0,0 +1,14 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 输入框- 密码
*/
export default class PasswordWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'InputPassword',
});
}
}

View File

@ -0,0 +1,14 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 省市区
*/
export default class PcaWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'JAreaSelect',
});
}
}

View File

@ -0,0 +1,43 @@
import {unref} from 'vue'
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* popupDict
*/
export default class PopupDictWidget extends IFormSchema {
dictCode: string;
multi: boolean;
constructor(key, data) {
super(key, data);
this.dictCode = `${data['code']},${data['destFields']},${data['orgFields']}`;
this.multi = data['popupMulti'];
}
getItem(): FormSchema {
const item = super.getItem();
const componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JPopupDict',
componentProps,
});
}
getComponentProps() {
const props = {
dictCode: this.dictCode,
multi: this.multi,
};
// popuppop
if (this.inPopover) {
props['getContainer'] = () => {
return this.getModalAsContainer();
};
}
//
props['getFormValues'] = () => unref(this.formRef).getFieldsValue();
return props;
}
}

View File

@ -0,0 +1,72 @@
import {unref} from 'vue'
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* popup
*/
export default class PopupWidget extends IFormSchema {
code: string;
multi: boolean;
fieldConfig: any[];
constructor(key, data) {
super(key, data);
this.code = data['code'];
this.multi = data['popupMulti'];
this.fieldConfig = this.getFieldConfig(data);
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JPopup',
componentProps,
});
}
getComponentProps() {
let props = {
code: this.code,
multi: this.multi,
fieldConfig: this.fieldConfig,
};
if (this.formRef) {
props['formElRef'] = this.formRef;
} else {
props['setFieldsValue'] = this.setFieldsValue;
}
// popuppop
if(this.inPopover === true){
props['getContainer'] = ()=>{
return this.getModalAsContainer();
}
}
//
props['getFormValues'] = () => unref(this.formRef).getFieldsValue();
return props;
}
getFieldConfig(data) {
let { destFields, orgFields, dictText } = data;
if (!destFields || destFields.length == 0) {
return [];
}
let arr1 = destFields.split(',');
let arr2 = orgFields.split(',');
let arr3 = dictText ? dictText.split(',') : null;
let config: any[] = [];
const pre = this.pre;
for (let i = 0; i < arr1.length; i++) {
config.push({
target: pre + arr1[i],
source: arr2[i],
label: arr3 ? arr3[i] : void 0,
});
}
return config;
}
}

View File

@ -0,0 +1,66 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* radio
* 没有现成的 只能借用JDictSelectTag
*/
export default class RadioWidget extends IFormSchema {
dictTable: string;
dictText: string;
dictCode: string;
constructor(key, data) {
super(key, data);
//
//this.options = this.getOptions(data['enum'])
this.dictTable = data['dictTable'];
this.dictText = data['dictText'];
this.dictCode = data['dictCode'];
}
setFormRef(ref) {
super.setFormRef(ref);
this.handleDictTableParams();
}
updateDictTable(dictTable: string) {
this.formRef.value.updateSchema(({
field: this.field,
componentProps: {
dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode),
}
}))
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JDictSelectTag',
componentProps,
});
}
getComponentProps() {
if (!this.dictTable && !this.dictCode) {
//
return {};
} else {
if (!this.dictTable) {
return {
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
useDicColor: true,
// update-end--author:liaozhiyang---date:20230110---forQQYUN-7799
dictCode: this.dictCode,
type: 'radio',
};
} else {
return {
dictCode: this.genDictTableCode(this.dictTable, this.dictText, this.dictCode),
type: 'radio',
};
}
}
}
}

View File

@ -0,0 +1,43 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 日期时间数值-范围
*/
export default class RangeWidget extends IFormSchema {
componentType: string;
datetime: boolean;
format: string;
constructor(key, data) {
super(key, data);
let view = data.view;
this.format = data.format;
this.datetime = false;
if('rangeNumber'===view){
this.componentType = 'JRangeNumber'
}else if('rangeTime'===view){
this.componentType = 'RangeTime'
}else{
this.componentType = 'RangeDate'
if(data.datetime===true){
this.datetime = true;
}
}
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: this.componentType,
componentProps: {
datetime: this.datetime,
format: this.format,
getPopupContainer: (_node) => {
return this.getModalAsContainer();
},
},
});
}
}

View File

@ -0,0 +1,49 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 部门选择
*/
export default class SelectDepartWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JSelectDept',
componentProps,
});
}
getComponentProps() {
let extend = this.getExtendData();
let props = {
// update-begin--author:liaozhiyang---date:20260414---for:QQYUN-9801online
sync: false,
// update-end--author:liaozhiyang---date:20260414---for:QQYUN-9801online
checkStrictly: true,
showButton: false,
};
if (extend.text) {
props['labelKey'] = extend.text;
}
if (extend.store) {
props['rowKey'] = extend.store;
}
if (extend.multiSelect === false) {
props['multiple'] = false;
}
if (extend.multiSelect === true) {
props['multiple'] = true;
}
props['maxTagCount'] = 3;
// popuppop
if(this.inPopover === true){
props['getContainer'] = ()=>{
return this.getModalAsContainer();
}
}
return props;
}
}

View File

@ -0,0 +1,67 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 下拉多选框
*/
export default class SelectMultiWidget extends IFormSchema {
dictTable: string;
dictText: string;
dictCode: string;
constructor(key, data) {
super(key, data);
//
//this.options = this.getOptions(data['enum'])
this.dictTable = data['dictTable'];
this.dictText = data['dictText'];
this.dictCode = data['dictCode'];
}
setFormRef(ref) {
super.setFormRef(ref);
this.handleDictTableParams();
}
updateDictTable(dictTable: string) {
this.formRef.value.updateSchema(({
field: this.field,
componentProps: {
dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode)
}
}))
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JSelectMultiple',
componentProps: componentProps,
});
}
getComponentProps() {
if (!this.dictTable && !this.dictCode) {
//
return {};
} else {
let props = {};
if (!this.dictTable) {
props['dictCode'] = this.dictCode;
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
props['useDicColor'] = true;
// update-end--author:liaozhiyang---date:20230110---forQQYUN-7799
} else {
props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode);
// update-begin--author:liaozhiyang---date:20260204---for:issues/9307online
//
props['scrollLoad'] = true;
// update-end--author:liaozhiyang---date:20260204---for:issues/9307online
}
props['triggerChange'] = true;
props['popContainer'] = this.getPopContainer();
return props;
}
}
}

View File

@ -0,0 +1,53 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 下拉搜索
*/
export default class SelectSearchWidget extends IFormSchema {
dict: string;
type: number;
constructor(key, data) {
super(key, data);
if (data.dictTable && data.dictText && data.dictCode) {
//
this.dict = this.genDictTableCode(data.dictTable, data.dictText, data.dictCode);
this.type = 1;
} else {
//
this.dict = encodeURI(`${data.dictCode}`);
this.type = 0;
}
}
setFormRef(ref) {
super.setFormRef(ref);
this.handleDictTableParams();
}
updateDictTable(dictTable: string) {
this.formRef.value.updateSchema(({
field: this.field,
componentProps: {
dict: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode),
}
}))
}
getItem(): FormSchema {
let item = super.getItem();
let popContainer = this.getPopContainer();
return Object.assign({}, item, {
component: 'JSearchSelect',
componentProps: {
dict: this.dict,
pageSize: 10,
// update-begin--author:liaozhiyang---date:20240628---forissues/6336online
async: this.type ? true : false,
// update-end--author:liaozhiyang---date:20240628---forissues/6336online
useDicColor: true,
popContainer: popContainer,
},
});
}
}

View File

@ -0,0 +1,44 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 用户选择
*/
export default class SelectUser2Widget extends IFormSchema {
multi: boolean;
store: string;
query: boolean;
constructor(key, data) {
super(key, data);
this.multi = data.multi === true ? true : false;
this.store = data.store||'';
//
this.query = data.query||false;
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'UserSelect',
componentProps
});
}
getComponentProps() {
let props = {
multi: this.multi,
store: this.store,
query: this.query,
}
// popuppop
if(this.inPopover === true){
props['getContainer'] = ()=>{
return this.getModalAsContainer();
}
}
return props;
}
}

View File

@ -0,0 +1,54 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 用户选择
*/
export default class SelectUserWidget extends IFormSchema {
showButton: boolean;
constructor(key, data) {
super(key, data);
this.showButton = data.showButton === false ? false : true;
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JSelectUser',
componentProps,
});
}
getComponentProps() {
let extend = this.getExtendData();
let props = {
showSelected: false,
allowClear: true,
isRadioSelection: false,
showButton: this.showButton
};
if (extend.text) {
props['labelKey'] = extend.text;
}
if (extend.store) {
props['rowKey'] = extend.store;
}
if (extend.multiSelect === false) {
//props['multiple'] = false
props['isRadioSelection'] = true;
}
props['maxTagCount'] = 3;
// popuppop
if(this.inPopover === true){
props['getContainer'] = ()=>{
return this.getModalAsContainer();
}
}
return props;
}
}

View File

@ -0,0 +1,150 @@
import { FormSchema } from '/@/components/Form';
import { h } from 'vue';
import IFormSchema from '../IFormSchema';
/**
* 下拉框
* //
*/
export default class SelectWidget extends IFormSchema {
schema: Recordable;
/*title-value*/
options: any[];
dictTable: string;
dictText: string;
dictCode: string;
multi: boolean;
constructor(key, data) {
super(key, data);
this.schema = data;
// enum JSelectSingle
this.options = data['enum'] ? this.getOptions(data['enum'], '') : [];
this.dictTable = data['dictTable'];
this.dictText = data['dictText'];
this.dictCode = data['dictCode'];
this.multi = data['multi'] || false;
}
getItem(): FormSchema {
let item = super.getItem();
let component = this.getFormComponent()
let componentProps = this.getComponentProps()
return Object.assign({}, item, {
component,
componentProps,
renderComponentContent: this.getSlots(componentProps),
});
}
getFormComponent(){
// update-begin--author:liaozhiyang---date:20260204---for:issues/9307online
// if(this.options.length>0){
// return 'Select'
// }else{
// return 'JDictSelectTag'
// }
return 'JSelectSingle'
// update-end--author:liaozhiyang---date:20260204---for:issues/9307online
}
setFormRef(ref) {
super.setFormRef(ref);
this.handleDictTableParams();
}
updateDictTable(dictTable: string) {
this.formRef.value.updateSchema(({
field: this.field,
componentProps: {
dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode),
}
}))
}
getComponentProps() {
let mode = this.multi===true?'multiple':'combobox'
let props: any = {
allowClear: true,
mode,
style: {
width: '100%',
},
getPopupContainer: (_node) => {
return this.getModalAsContainer();
},
// update-begin--author:liaozhiyang---date:20260203---for:issues/9307online
// /
// onDropdownVisibleChange: (visible: boolean)=> {
// if (visible && typeof this.schema.updateOptions === 'function') {
// this.schema.updateOptions()
// }
// },
// update-end--author:liaozhiyang---date:20260203---for:issues/9307online
}
// update-begin--author:liaozhiyang---date:20260203---for:issues/9307online
if (!this.dictTable) {
props['dictCode'] = this.dictCode;
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
props['useDicColor'] = true;
// update-end--author:liaozhiyang---date:20230110---forQQYUN-7799
// dictCode enum
if (!this.dictCode && this.options.length > 0) {
props['options'] = this.options;
}
} else {
props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode);
props['scrollLoad'] = true;
delete props.onDropdownVisibleChange;
}
// update-end--author:liaozhiyang---date:20260203---for:issues/9307online
return props
}
getSlots(componentProps: Recordable) {
const {useDicColor} = componentProps;
return function () {
return {
option(option: Recordable) {
const style: Recordable = {};
if (useDicColor && option.color) {
style.color = '#fff';
style.height = '20px';
style.lineHeight = '20px';
style.padding = '0 6px';
style.fontSize = '12px';
style.borderRadius = '8px';
style.backgroundColor = option.color;
style.display = 'inline-block';
}
return h('span', {
style,
}, option.text || option.label);
},
};
}
}
getOptions(array, type) {
if (!array || array.length == 0) {
return [];
}
let isNum = 'number' == type;
let arr: any[] = [];
for (let item of array) {
// update-begin--author:liaozhiyang---date:20240517---forQQYUN-9359null
if (item == null) break;
// update-end--author:liaozhiyang---date:20240517---forQQYUN-9359null
let value = item.value;
if(isNum){
value = parseInt(value)
}
arr.push({
...item,
value,
label: item.title,
});
}
return arr;
}
}

View File

@ -0,0 +1,68 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* slot
*/
export default class SlotWidget extends IFormSchema {
slot: string;
picker: string | undefined;
precision: number | undefined;
constructor(key, data) {
super(key, data);
this.slot = '';
// update-begin--author:liaozhiyang---date:20240520---forTV360X-180
let fieldExtendJson = data.fieldExtendJson;
if (data.view == 'date' && fieldExtendJson) {
fieldExtendJson = JSON.parse(fieldExtendJson);
if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') {
this.picker = fieldExtendJson.picker;
} else {
this.picker = undefined;
}
}
// update-end--author:liaozhiyang---date:20240520---forTV360X-180
// update-begin--author:liaozhiyang---date:20240606---forTV360X-214
this.precision = data.dbPointLength;
// update-end--author:liaozhiyang---date:20240606---forTV360X-214
}
getItem(): FormSchema {
let item = super.getItem();
let slot = this.slot;
const componentProps: any = {};
this.picker && (componentProps.picker = this.picker);
// update-begin--author:liaozhiyang---date:20240606---forTV360X-214
this.precision && (componentProps.precision = this.precision);
// update-end--author:liaozhiyang---date:20240606---forTV360X-214
// update-begin--author:liaozhiyang---date:20240520---forTV360X-180
return Object.assign({}, item, {
slot,
componentProps,
});
// update-end--author:liaozhiyang---date:20240520---forTV360X-180
}
groupDate() {
this.slot = 'groupDate';
return this;
}
groupDatetime() {
this.slot = 'groupDatetime';
return this;
}
groupTime() {
// update-begin--author:liaozhiyang---date:20240517---forQQYUN-9348online
this.slot = 'groupTime';
return this;
// update-end--author:liaozhiyang---date:20240517---forQQYUN-9348online
}
groupNumber() {
this.slot = 'groupNumber';
return this;
}
}

View File

@ -0,0 +1,43 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
import { isArray, isObject } from '/@/utils/is';
/**
* 开关
*/
export default class SwitchWidget extends IFormSchema {
constructor(key, data) {
super(key, data);
// update-begin--author:liaozhiyang---date:20240517---forTV360X-54
// this.hasChange = false;
// update-end--author:liaozhiyang---date:20240517---forTV360X-54
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
component: 'JSwitch',
componentProps,
});
}
getComponentProps() {
let { fieldExtendJson } = this._data;
let options = ['Y', 'N'];
if (fieldExtendJson) {
if (typeof fieldExtendJson == 'string') {
// update-begin--author:liaozhiyang---date:20240522---forTV360X-25
const json = JSON.parse(fieldExtendJson);
if (isArray(json) && json.length == 2) {
options = json;
} else if (isObject(json) && isArray(json.switchOptions)) {
options = json.switchOptions;
}
// update-end--author:liaozhiyang---date:20240522---forTV360X-25
}
}
return {
options,
};
}
}

View File

@ -0,0 +1,19 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 输入框-textarea
*/
export default class TextAreaWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'InputTextArea',
componentProps:{
autoSize : {
minRows: 4, maxRows: 10
}
}
});
}
}

View File

@ -0,0 +1,24 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 日期时间
*/
export default class TimeWidget extends IFormSchema {
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'TimePicker',
componentProps: {
placeholder: `请选择${this.label}`,
valueFormat: 'HH:mm:ss',
getPopupContainer: (_node) => {
return this.getModalAsContainer();
},
style: {
width: '100%',
},
},
});
}
}

View File

@ -0,0 +1,73 @@
import { FormSchema } from '/@/components/Form';
import IFormSchema from '../IFormSchema';
/**
* 分类字典
*/
export default class TreeCategoryWidget extends IFormSchema {
pid: string;
multi: boolean;
textField: string;
pcode: string;
constructor(key, data) {
super(key, data);
this.multi = false;
this.pid = data['pidValue'];
this.pcode = data['pcode'];
this.textField = data['textField'];
}
getItem(): FormSchema {
let item = super.getItem();
let componentProps = this.getComponentProps();
return Object.assign({}, item, {
componentProps,
component: 'JCategorySelect',
});
}
/**
* 1. 不带返回值的
* 2. 带文本返回的
*/
getComponentProps() {
// VUEN-1049 -->
let param = {
placeholder: '请选择' + this.label
}
if(this.pcode){
param['pcode'] = this.pcode;
}else{
let pidValue = this.pid || 'EMPTY_PID';
param['pid'] = pidValue;
}
if (!this.textField) {
return {
multiple: this.multi,
...param
};
} else {
return {
loadTriggleChange: true,
multiple: this.multi,
...param,
back: this.textField,
onChange: (val, backVal) => {
if (this.formRef) {
this.formRef.value.setFieldsValue(backVal);
this.formRef.value.$formValueChange(this.field, val)
}
},
};
}
}
getRelatedHideFields(): string[] {
let arr: string[] = [];
if (this.textField) {
arr.push(this.textField);
}
return arr;
}
}

View File

@ -0,0 +1,41 @@
import IFormSchema from '../IFormSchema';
import { FormSchema } from '/@/components/Form';
/**
* 自定义树
*/
export default class TreeSelectWidget extends IFormSchema {
/*表名、显示字段、存储字段*/
dict: string;
/*父级ID的字段名*/
pidField: string;
/*父级ID的字段值*/
pidValue: string;
/*是否有子节点*/
hasChildField: string;
constructor(key, data) {
super(key, data);
this.dict = data['dict'];
this.pidField = data['pidField'];
this.pidValue = data['pidValue'];
// update-begin--author:liaozhiyang---date:20240509---forissues/6197
this.hasChildField = data['hasChildField'];
// update-end--author:liaozhiyang---date:20240509---forissues/6197
}
getItem(): FormSchema {
let item = super.getItem();
return Object.assign({}, item, {
component: 'JTreeSelect',
componentProps: {
dict: this.dict,
pidField: this.pidField,
pidValue: this.pidValue,
// update-begin--author:liaozhiyang---date:20240509---forissues/6197
hasChildField: this.hasChildField,
// update-end--author:liaozhiyang---date:20240509---forissues/6197
},
});
}
}

View File

@ -0,0 +1,13 @@
import type { App } from 'vue';
import {defineAsyncComponent} from 'vue'
const SuperQuery = defineAsyncComponent(() => import('./superquery/SuperQuery.vue'))
const JOnlineSearchSelect = defineAsyncComponent(() => import('./JOnlineSearchSelect.vue'))
export const registerOnlineComp = {
install(app: App) {
app.component('JOnlineSearchSelect', JOnlineSearchSelect);
app.component('SuperQuery', SuperQuery);
console.log("---初始化--- 全局注册Online部分组件--------------")
},
};

View File

@ -0,0 +1,716 @@
<template>
<!-- 按钮区域 -->
<div class="j-super-query-button">
<a-tooltip v-if="superQueryFlag" :mouseLeaveDelay="0.2">
<template #title>
<span>执行查询中...</span>
<divider type="vertical" style="background-color: #fff"/>
<a @click="handleStop">取消查询</a>
</template>
<a-button-group>
<a-button type="primary" @click="handleOpen">
<AppstoreTwoTone :spin="true"/>
<span>{{queryBtnCfg.buttonName}}</span>
</a-button>
</a-button-group>
</a-tooltip>
<a-button v-else type="primary" :preIcon="queryBtnCfg.buttonIcon" @click="handleOpen">
<span>{{queryBtnCfg.buttonName}}</span>
</a-button>
</div>
<!-- 高级查询弹框 -->
<teleport to="body">
<BasicModal :title="queryBtnCfg.buttonName + '构造器'" wrap-class-name="j-super-query-modal" :canFullscreen="true" :width="850" @register="registerFormModal" @ok="handleSubmit" @fullScreen="handleFullScreen">
<template #footer>
<div style="float: left">
<a-button :loading="loading" @click="handleReset">清空</a-button>
<a-button :loading="loading" @click="handleSave">保存查询</a-button>
</div>
<a-button key="submit" type="primary" @click="handleSubmit">执行查询</a-button>
<a-button key="back" @click="handleCancel">关闭</a-button>
</template>
<a-empty v-if="dynamicRowValues.values.length == 0">
<div slot="description">
<span>没有任何查询条件</span>
<a-divider type="vertical"/>
<a @click="addOne(-1)">点击新增</a>
</div>
</a-empty>
<a-row :class="'j-super-query-modal-content'">
<a-col :sm="24" :md="24">
<a-row v-show="dynamicRowValues.values.length > 0">
<a-col :md="12" :xs="24">
<a-form-item label="匹配模式" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
<a-select v-model:value="matchType" :getPopupContainer="node=>node?.parentNode" style="width: 100%;">
<a-select-option value="and">AND所有条件匹配</a-select-option>
<a-select-option value="or">OR任意一个匹配</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form v-show="dynamicRowValues.values.length > 0" ref="formRef" :class="'jee-super-query-form'" :model="dynamicRowValues" @finish="onFinish" :style="queryFormStyle">
<a-space v-for="(item, index) in dynamicRowValues.values" :key="item.key" style="display: flex; margin-bottom: 8px" :align="item.curLineAlign ? item.align : 'baseline'">
<a-form-item class="field-clos" :name="['values', index, 'field']" >
<a-tree-select
:popupClassName="getTreePopupClass"
style="width:100%"
placeholder="请选择字段"
v-model:value="item.field"
show-search
tree-node-filter-prop="title"
allow-clear
tree-default-expand-all
:dropdown-style="{ maxHeight: `${fieldTreeSelectHeight}px`, overflow: 'auto' }"
:listHeight="fieldTreeSelectHeight - 20"
@change="handleChangeField(item)"
:tree-data="fieldTreeData">
</a-tree-select>
</a-form-item>
<a-form-item class="rule-clos" :name="['values', index, 'rule']">
<a-select style="width:100%" placeholder="请选择匹配规则" v-model:value="item.rule">
<a-select-option :value="qItem.value" v-for="qItem in getQueryCondition(item)" :key="qItem.value">{{qItem.label}}</a-select-option>
<!-- 当前表单设计器内专用 -->
<!-- <a-select-option value="empty">为空</a-select-option>
<a-select-option value="not_empty">不为空</a-select-option>-->
<!-- 当前表单设计器内专用 -->
</a-select>
</a-form-item>
<a-form-item class="component-clos" :name="['values', index, 'val']">
<online-super-query-val-component style="width:100%" :schema="getSchema(item, index)" :formModel="item" :setFormModel="(key, value)=>{setFormModel(key, value, item)}" @submit="handleSubmit" />
</a-form-item>
<a-form-item>
<a-button @click="addOne(index)" style="margin-right: 6px">
<PlusOutlined #icon/>
</a-button>
<a-button @click="removeOne(item)">
<MinusCircleOutlined #icon/>
</a-button>
</a-form-item>
</a-space>
</a-form>
</a-col>
</a-row>
<!-- 查询记录 -->
<a-card :class="['j-super-query-history-card', {'collapsed': historyCollapsed}]" :bordered="false">
<template #title><div>保存的查询</div></template>
<a-empty
v-if="saveTreeData.length === 0"
class="j-super-query-history-empty"
:image="simpleImage"
description="没有保存的查询"
/>
<a-tree
v-else
class="j-super-query-history-tree"
:treeData="saveTreeData"
:selectedKeys="[]"
:show-icon="true"
@select="handleTreeSelect">
<template #title="{title}">
<div>
<span :title="title">{{(title.length>10)?(title.substring(0, 10)+'...'):title }}</span>
<a-popconfirm title="确定删除吗?" @confirm="handleRemoveSaveInfo(title)">
<span class="icon-cancle" @click="(e)=>e.stopPropagation()"><close-circle-outlined /></span>
</a-popconfirm>
</div>
</template>
<!-- antd-2是这么写的 升级到3会也许会改变写法 -->
<template #custom>
<file-text-outlined/>
</template>
</a-tree>
<div class="collapse-box" @click="historyCollapsed=!historyCollapsed">
<Icon v-if="historyCollapsed" icon="ant-design:caret-left"/>
<Icon v-else icon="ant-design:caret-right"/>
</div>
</a-card>
</BasicModal>
</teleport>
<!-- 保存信息弹框 -->
<a-modal title="请输入保存的名称" :open="saveInfo.visible" @cancel="saveInfo.visible=false" @ok="doSaveQueryInfo" :confirmLoading="saveModalLoading">
<div style="height:80px;line-height:75px;width:100%;text-align: center">
<a-input v-model:value="saveInfo.title" style="width:90%" placeholder="请输入保存的名称"></a-input>
</div>
</a-modal>
</template>
<script lang="ts">
import {ref, watch, computed} from 'vue'
import { BasicModal, useModal } from '/@/components/Modal';
import { useSuperQuery } from './useSuperQuery';
import OnlineSuperQueryValComponent from './SuperQueryValComponent.vue';
import { MinusCircleOutlined, PlusOutlined, FileTextOutlined, CloseCircleOutlined, AppstoreTwoTone } from '@ant-design/icons-vue';
import { Divider, Empty, Popconfirm } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash-es';
import { useConditionFilter } from '/@/utils/index';
import { useDesign } from '/@/hooks/web/useDesign';
//const { BasicModal, useModal } = defineAsyncComponent(()=>import('/@/components/Modal'));
export default{
name: "OnlineSuperQuery",
props: {
config:{
type: Object,
default: []
},
status:{
type: Boolean,
default: false
},
online: {
type: Boolean,
default: false
},
// update-begin--author:liaozhiyang---date:20240514---forissues/6205
isCustomSave: {
type: Boolean,
default: false,
},
saveSearchData: {
type: Array,
default: () => [],
},
save: {
type: Function,
},
// update-end--author:liaozhiyang---date:20240514---forissues/6205
queryBtnCfg: {
type: Object,
default: () => {
return {
buttonName: '高级查询',
buttonIcon: 'ant-design:filter-outlined',
}
}
},
},
components: {
BasicModal,
MinusCircleOutlined,
PlusOutlined,
OnlineSuperQueryValComponent,
FileTextOutlined,
CloseCircleOutlined,
AppstoreTwoTone,
Divider,
Popconfirm,
},
emits:['search'],
setup(props, {emit}) {
console.log("-------初始化 OnlineSuperQuery注册--------------")
const [registerFormModal, formModal] = useModal()
const { createMessage: $message } = useMessage();
const queryFormStyle = ref({});
const fieldTreeSelectHeight = ref(180);
const { prefixCls } = useDesign('super-query');
const treePopupClass = `${prefixCls}-tree-popup`;
//
const historyCollapsed = ref(true)
let currentQueryInfo = null;
// -update-begin--author:liaozhiyang---date:20240614---forTV360X-76
const { filterCondition } = useConditionFilter();
const getQueryCondition = (data) => {
const auto = (arr, field) => {
const findItem = arr.find((item) => item.value === field) ?? {};
return filterCondition({ view: findItem.originView || findItem.view, fieldType: findItem.fieldType });
};
if (data.field?.indexOf('@') == -1 || !data.field) {
return auto(fieldTreeData.value, data.field);
} else {
const tableName = data.field.split('@')[0];
const findTableItem = fieldTreeData.value.find((item) => item.value === tableName);
if (findTableItem?.children?.length) {
return auto(findTableItem.children, data.field);
}
}
};
// -update-end--author:liaozhiyang---date:20240614---forTV360X-76
/**
* 关闭按钮事件
*/
function handleCancel() {
formModal.closeModal();
}
/**
* 确认按钮事件
*/
function handleSubmit() {
if(props.online === true){
let dataArray = getQueryInfo(true)
if(dataArray && dataArray.length>0){
// update-begin--author:liaozhiyang---date:20240517---forTV360X-86
transformDateValus(dataArray);
// update-end--author:liaozhiyang---date:20240517---forTV360X-86
emit('search', dataArray, matchType.value)
// update-begin--author:liaozhiyang---date:220230802---forQQYUN-5995
handleCancel()
// update-end--author:liaozhiyang---date:220230802---forQQYUN-5995
currentQueryInfo = dataArray;
}else{
$message.warning('空条件无法查询!')
}
}else{
//console.log('handleSubmit', dynamicRowValues.values)
let dataArray = getQueryInfo(true)
if(dataArray && dataArray.length>0){
//update-begin---author:wangshuai---date:2025-07-18---for:issues/8548----
transformDateValus(dataArray);
//update-end---author:wangshuai---date:2025-07-18---for:issues/8548----
let result = getSuperQueryParams(dataArray);
//console.log('1', dataArray)
//console.log('2', result)
emit('search', result)
// update-begin--author:wangshuai---date:20251112---forissues/9060superQuery""
handleCancel()
// update-end--author:wangshuai---date:20251112---forissues/9060superQuery""
}else{
$message.warning('空条件无法查询!')
}
}
historyCollapsed.value = true;
}
/**
* 2024-05-17
* liaozhiyang
* 把年年月周等时间重置到当前格式的第一天因为存的时候也是第一天
*/
const transformDateValus = (date) => {
date.forEach((item) => {
const value = item['val'];
if (item.type === 'date' && typeof value === 'string' && value != '') {
const obj = fieldProperties.value[item.field];
if (obj) {
let fieldExtendJson = obj.fieldExtendJson;
if (fieldExtendJson) {
fieldExtendJson = JSON.parse(fieldExtendJson);
if (fieldExtendJson.picker && fieldExtendJson.picker !== 'default') {
const picker = fieldExtendJson.picker;
if (picker === 'year') {
item['val'] = dayjs(value).set('month', 0).set('date', 1).format('YYYY-MM-DD');
} else if (picker === 'month') {
item['val'] = dayjs(value).set('date', 1).format('YYYY-MM-DD');
} else if (picker === 'week') {
item['val'] = dayjs(value).startOf('week').format('YYYY-MM-DD');
}
}
}
}
}
});
};
function getSuperQueryParams(dataArray){
let arr:any = []
for(let item of dataArray){
let field = item.field;
let val = item.val
if(val instanceof Array){
val = val.join(",")
}
arr.push({
...item,
field,
val
})
}
if(arr.length>0){
superQueryFlag.value = true;
}else{
superQueryFlag.value = false;
}
let result = {
superQueryMatchType: matchType.value,
superQueryParams: encodeURI(JSON.stringify(arr))
}
return result;
}
function handleStop(){
let result = getSuperQueryParams([]);
emit('search', result)
}
/**
* 重置按钮事件
*/
function handleReset(){
dynamicRowValues.values = []
addOne(false);
let result = getSuperQueryParams([])
currentQueryInfo = null;
historyCollapsed.value = true;
emit('search', result)
}
// update-begin--author:liaozhiyang---date:20240524---forTV360X-524
const handleFullScreen = (val) => {
// update-begin--author:liaozhiyang---date:20240603---forTV360X-810
if (val) {
const contentHeight = document.documentElement.clientHeight - 165;
queryFormStyle.value = { maxHeight: `${contentHeight}px` };
fieldTreeSelectHeight.value = contentHeight * 0.62;
} else {
queryFormStyle.value = {};
fieldTreeSelectHeight.value = 180;
}
// update-end--author:liaozhiyang---date:20240603---forTV360X-810
};
// update-end--author:liaozhiyang---date:20240524---forTV360X-524
const {
formRef,
init,
dynamicRowValues,
matchType,
registerModal,
handleSave,
doSaveQueryInfo,
saveInfo,
saveTreeData,
handleTreeSelect,
handleRemoveSaveInfo,
fieldTreeData,
addOne,
removeOne,
setFormModel,
getSchema,
loading,
getQueryInfo,
initDefaultValues,
saveModalLoading,
fieldProperties,
} = useSuperQuery(props)
/*--------------------按钮区域-beign------------------*/
const superQueryFlag = ref(false)
watch(()=>props.status, (val)=>{
superQueryFlag.value = val;
}, {immediate: true});
function handleOpen(){
// update-begin--author:liaozhiyang---date:20240604---forTV360X-204
if (superQueryFlag.value && currentQueryInfo) {
dynamicRowValues.values = cloneDeep(currentQueryInfo);
}
// update-end--author:liaozhiyang---date:20240604---forTV360X-204
formModal.openModal();
historyCollapsed.value = true;
addOne(true)
}
/*--------------------按钮区域-end------------------*/
function getPopupContainer(){
return document.getElementsByClassName('jee-super-query-form')[0]
}
function onFinish(a){
console.log('onfinish', a)
}
/**
* 2024-06-04
* liaozhiyang
* TV360X-461字段类型是string则默认模糊查询
* */
function handleChangeField(data) {
data['val'] = '';
const auto = (arr, field) => {
const findItem = arr.find((item) => item.value === field);
if (findItem?.fieldType === 'string' && ['text'].includes(findItem?.originView || findItem?.view)) {
data['rule'] = 'like';
} else if (['file', 'image', 'password'].includes(findItem?.originView || findItem?.view)) {
data['rule'] = 'empty';
} else {
data['rule'] = 'eq';
}
};
if (data.field?.indexOf('@') == -1) {
auto(fieldTreeData.value, data.field);
} else {
const tableName = data.field.split('@')[0];
const findTableItem = fieldTreeData.value.find((item) => item.value === tableName);
if (findTableItem?.children?.length) {
auto(findTableItem.children, data.field);
}
}
}
watch(()=>props.config, (val)=>{
if(val){
// console.log('123', val)
// console.log('123', val)
// console.log('123', val)
// Object.keys(val).map(k=>{
// console.log(k, val[k])
// })
// console.log('123', val)
// console.log('123', val)
// console.log('123', val)
init(val);
}
}, {immediate: true});
// update-begin--author:liaozhiyang---date:20240603---forTV360X-342
const getTreePopupClass = computed(() => {
const findItem = fieldTreeData.value.find((item) => item.children);
return findItem ? `${treePopupClass} containTable` : `${treePopupClass} noTable`;
});
// update-end--author:liaozhiyang---date:20240603---forTV360X-342
return {
formRef,
registerFormModal,
init,
handleChangeField,
dynamicRowValues,
matchType,
historyCollapsed,
registerModal,
handleSubmit,
handleCancel,
handleSave,
handleReset,
doSaveQueryInfo,
saveInfo,
saveTreeData,
handleTreeSelect,
handleRemoveSaveInfo,
fieldTreeData,
addOne,
removeOne,
setFormModel,
getSchema,
loading,
onFinish,
getPopupContainer,
superQueryFlag,
handleOpen,
initDefaultValues,
simpleImage: Empty.PRESENTED_IMAGE_SIMPLE,
saveModalLoading,
queryFormStyle,
handleFullScreen,
fieldTreeSelectHeight,
getTreePopupClass,
handleStop,
getQueryCondition,
}
}
}
</script>
<style lang="less">
.j-super-query-modal {
.scrollbar__wrap {
margin-bottom: 0 !important;
}
}
</style>
<style scoped lang="less">
:deep(.jee-super-query-form) {
max-height: 400px;
overflow-y: auto;
min-height: 300px;
.ant-form-item{
margin-bottom: 9px;
}
}
:deep(.j-super-query-history-tree) {
.ant-tree-switcher{
width: 0px;
}
.ant-tree-node-content-wrapper{
width:100%;
&:hover{
background-color: #e6f7ff !important;
border-radius: 0;
}
}
.ant-tree-treenode-switcher-close{
.ant-tree-title{
display: inline-block;
width: calc(100% - 30px);
>div{
display: flex;
justify-content: space-between;
.icon-cancle{
display: none;
color: #666666;
&:hover{
color: black;
}
}
}
}
&:hover {
.icon-cancle{
display: inline-block !important;
}
}
}
.ant-card-body{
padding: 0;
}
//
.ant-tree-treenode { width: 100%;}
}
.j-super-query-history-card {
position: absolute;
top: 0;
right: -0;
bottom: 0;
z-index: 2;
//QQYUN-7526
width: 180px;
min-height: 200px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2);
&.collapsed {
//QQYUN-7526
right: -180px;
}
.collapse-box {
position: absolute;
top: calc(50% - 15px);
left: -20px;
width: 20px;
height: 30px;
background-color: #fff;
border: 1px solid #e8e8e8;
border-left: none;
border-right-color: #ffffff;
border-radius: 15px 0 0 15px;
cursor: pointer;
transition: all 0.3s;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
box-shadow: -4px 0 6px rgba(0, 0, 0, 0.1);
&:hover {
background-color: #f5f5f5;
}
.app-iconify {
font-size: 16px;
position: relative;
right: -3px;
color: #666;
}
}
&.collapsed .app-iconify{
right: 0;
}
:deep(.ant-card-body),
:deep(.ant-card-head){
padding: 8px;
min-height: 43px;
.ant-card-head-title {
padding: 0;
}
}
}
/*VUEN-1087 【移动端】高级查询显示不全 */
@media only screen and(max-width: 1050px) {
:deep(.jee-super-query-form){
.ant-space{
flex-direction:column;
gap: 0 !important;
margin-bottom: 16px !important;
}
.ant-space-item{
width: 100%;
}
.ant-form-item{
width: 100% !important;
}
}
}
// update-begin--author:liaozhiyang---date:20240524---forTV360X-524
.ant-form {
& > .ant-space {
width: 100%;
:deep(& > .ant-space-item) {
&:nth-child(1) {
width: 20%;
}
&:nth-child(2) {
width: 15%;
}
&:nth-child(3) {
width: 40%;
}
}
}
}
// update-end--author:liaozhiyang---date:20240524---forTV360X-524
.containTable {
:deep(.ant-select-tree-treenode) {
> .ant-select-tree-switcher { display: none;}
}
}
</style>
<style lang="less">
@tree-popup: ~'@{namespace}-super-query-tree-popup';
.@{tree-popup} {
// update-begin--author:liaozhiyang---date:20240603---forTV360X-342
&.noTable {
.ant-select-tree-treenode {
> .ant-select-tree-switcher {
display: none;
}
.ant-select-tree-node-content-wrapper {
display: block;
max-width: 100%;
}
.ant-select-tree-title {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
// update-end--author:liaozhiyang---date:20240603---forTV360X-342
// update-end--author:liaozhiyang---date:20240612---forTV360X-1005
&.containTable {
.ant-select-tree-indent-unit {
display: none;
}
.ant-select-tree-node-content-wrapper {
display: block;
max-width: calc(100% - 24px);
}
.ant-select-tree-title {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
// update-end--author:liaozhiyang---date:20240612---forTV360X-1005
}
</style>

View File

@ -0,0 +1,108 @@
<script lang="tsx">
import { computed, defineComponent, PropType, unref } from 'vue';
import { FormSchema } from '/@/components/Form';
import { upperFirst } from 'lodash-es';
import { componentMap } from '/@/components/Form/src/componentMap';
import { createPlaceholderMessage } from '/@/components/Form/src/helper';
import { isFunction } from '/@/utils/is';
export default defineComponent({
name: 'SuperQueryValComponent',
inheritAttrs: false,
props: {
schema: {
type: Object as PropType<FormSchema>,
default: () => ({}),
},
formModel: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
setFormModel: {
type: Function as PropType<(key: string, value: any) => void>,
default: null,
}
},
emits: ['submit'],
setup(props, { emit }) {
const getComponentsProps = computed(() => {
const {schema, formModel} = props;
let {componentProps = {}} = schema;
if (isFunction(componentProps)) {
componentProps = componentProps({schema, formModel}) ?? {};
}
return componentProps as Recordable;
});
const getValues = computed(() => {
const {formModel, schema} = props;
let obj = {
field: schema.field,
model: formModel,
values: {
...formModel,
} as Recordable,
schema: schema,
};
return obj
});
function renderComponent() {
const {
component,
changeEvent = 'change',
valueField,
} = props.schema;
const field = 'val';
const isCheck = component && ['Switch', 'Checkbox'].includes(component);
const eventKey = `on${upperFirst(changeEvent)}`;
const on = {
[eventKey]: (...args: Nullable<Recordable>[]) => {
const [e] = args;
if (propsData[eventKey]) {
propsData[eventKey](...args);
}
const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e;
props.setFormModel(field, value);
},
};
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
const propsData: Recordable = {
allowClear: true,
getPopupContainer: (trigger: Element) => trigger?.parentNode,
...unref(getComponentsProps)
};
const isCreatePlaceholder = !propsData.disabled;
// RangePicker place
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
//placeholder
propsData.placeholder =
unref(getComponentsProps)?.placeholder ||
createPlaceholderMessage(component) + props.schema.label;
}
propsData.codeField = field;
propsData.formValues = unref(getValues);
const bindValue: Recordable = {
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
};
const compAttr: Recordable = {
...propsData,
...on,
...bindValue,
allowClear: true,
onPressEnter() {
emit('submit');
},
};
return <Comp {...compAttr} />;
}
return ()=>{
return (<div style="width:100%">{renderComponent()}</div>)
}
}
})
</script>

View File

@ -0,0 +1,703 @@
import { useModalInner } from '/@/components/Modal';
import { randomString } from '/@/utils/common/compUtils';
import { reactive, ref, toRaw, watch } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { Modal } from 'ant-design-vue';
import { createLocalStorage } from '/@/utils/cache';
import { useRoute } from 'vue-router';
import FormSchemaFactory from '../factory/FormSchemaFactory';
import {useExtendComponent} from '../../../hooks/auto/useExtendComponent'
import { cloneDeep } from 'lodash-es';
/**
* 表单类型转换成查询类型
* 普通查询和高级查询组件区别 高级查询不支持联动组件
*/
const FORM_VIEW_TO_QUERY_VIEW = {
"password": "text",
"file": "text",
"image": "text",
"textarea": "text",
"umeditor": "text",
"markdown": "text",
"checkbox": "list_multi",
"radio": "list",
}
//
const SAVE_CODE_PRE = 'JSuperQuerySaved_';
/**
* 查询项
* */
interface SuperQueryItem {
field: string|undefined;
rule: string|undefined;
val: string|number;
key: string;
// inputNumber
curLineAlign: string | undefined;
fileType: string;
// update-begin--author:liaozhiyang---date:20240611---forTV360X-461stringtext
view: string;
// view
originView?: string;
// update-end--author:liaozhiyang---date:20240611---forTV360X-461stringtext
}
/**
* 查询项-第一个控件树model
* */
interface TreeModel {
title: string,
value: string,
isLeaf?: boolean,
disabled?: boolean,
children?: TreeModel[],
order?: number,
fieldType?: string;
// update-begin--author:liaozhiyang---date:20240611---forTV360X-461stringtext
view: string;
originView?: string;
// update-end--author:liaozhiyang---date:20240611---forTV360X-461stringtext
}
/**
* 查询信息保存结构
* */
interface SaveModel{
title: string,
content: string,
type: string,
}
export function useSuperQuery(props){
//
const {linkTableCard2Select} = useExtendComponent();
const { createMessage: $message } = useMessage();
/** 表单ref*/
const formRef = ref<any>();
/** 数据*/
const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({
values: [],
});
/** and/or */
const matchType = ref('and');
// loading
const saveModalLoading = ref(false);
//
const [registerModal, {setModalProps}] = useModalInner(() => {
setModalProps({confirmLoading: false});
})
// viewtext
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<any>({})
// --
const fieldTreeData = ref<any>([])
// update-begin--author:liaozhiyang---date:20240607---forTV360X-503
const filterComponent = (data) => {
const { properties = {} } = data;
Object.entries(properties).forEach(([field, value]) => {
if (value.view === 'table') {
filterComponent(value);
}
if (['link_down'].includes(value.originView || value.view)) {
delete properties[field];
}
});
};
// update-end--author:liaozhiyang---date:20240607---forTV360X-503
/**
* 初始化数据-最开始的方法
* 1.获取 表名@字段名-->配置 这样的一个map
* 2.获取树形结构的数据 显示:文本 存储:表名@字段名
* 当树改变时及时获取配置更新表单
* @param json
*/
function init(json) {
console.log('=============')
console.log('=============', json)
console.log('=============')
// update-begin--author:liaozhiyang---date:20240607---forTV360X-503
filterComponent(json);
// update-end--author:liaozhiyang---date:20240607---forTV360X-503
let { allFields, treeData } = getAllFields(json);
fieldProperties.value = allFields;
// update-end--author:liaozhiyang---date:20240612---forTV360X-1005
const properties = json.properties ?? {};
const subTable: string[] = [];
const tableName = json.table;
Object.entries(properties).forEach(([key, value]: [string, any]) => {
if (value.view === 'table') {
subTable.push(key);
}
});
if (subTable.length) {
let arr: TreeModel[] = [];
arr = treeData.filter((item) => !subTable.includes(item.value));
for (let i = 0, len = treeData.length; i < len; i++) {
const item = treeData[i];
if (!subTable.includes(item.value)) {
treeData.splice(i, 1);
i--;
len--;
}
}
treeData.unshift({ title: '主表', value: tableName, disabled: true, order: 200, children: arr, view: 'table' });
}
// update-end--author:liaozhiyang---date:20240612---forTV360X-1005
fieldTreeData.value = treeData;
}
/**
* 左侧查询项 添加一行
* @param index
*/
function addOne(index) {
let item = {
field: undefined,
rule: 'eq',
val:'',
key: randomString(16)
}
if(index===false){
//
dynamicRowValues.values = []
dynamicRowValues.values.push(item)
}else if(index===true){
//
if(dynamicRowValues.values.length==0){
dynamicRowValues.values.push(item)
}
}else{
//
dynamicRowValues.values.splice(++index, 0, item)
}
}
/**
* 左侧查询项 删除一行
*/
function removeOne(item: SuperQueryItem) {
let arr = toRaw(dynamicRowValues.values);
let index = -1;
for(let i=0;i<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()
// popContainerparentNode
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'
//
//
if (['empty', 'not_empty'].includes(item.rule)) {
schema.componentProps = { ...schema.componentProps, disabled: true };
}
linkTableCard2Select(schema);
// update-begin--author:liaozhiyang---date:20240607---forTV360X-389
if (schema.component === 'LinkTableSelect') {
let componentProps = schema.componentProps ?? {};
schema.componentProps = { ...componentProps, editBtnShow: false };
}
// update-end--author:liaozhiyang---date:20240607---forTV360X-389
// update-begin--author:liaozhiyang---date:20231219---forQQYUN-7640
if (schema && schema.component === 'InputNumber') {
item.curLineAlign = 'start';
}
// update-end--author:liaozhiyang---date:20231219---forQQYUN-7640
// update-begin--author:liaozhiyang---date:20240223---forQQYUN-8229
if (schema?.component === 'JTreeSelect') {
let componentProps: any = schema.componentProps;
if (componentProps) {
componentProps.getPopupContainer = () => document.body
} else {
schema.componentProps = { getPopupContainer: () => document.body }
};
}
// update-end--author:liaozhiyang---date:20240223---forQQYUN-8229
// update-begin--author:liaozhiyang---date:20240529---forTV360X-499
if (schema?.component === 'JSwitch') {
const componentProps = schema.componentProps ?? {};
schema.componentProps = { ...componentProps, query: true };
}
if (schema?.component === 'JSelectUser') {
const componentProps = schema.componentProps ?? {};
schema.componentProps = { ...componentProps, showButton: false };
}
// update-end--author:liaozhiyang---date:20240529---forTV360X-499
return schema
}
/*-----------------------右侧保存信息相关-begin---------------------------*/
/**
* 右侧树 数据
*/
const saveTreeData = ref<any>('')
//
const $ls = createLocalStorage();
//
const saveInfo = reactive({
visible: false,
title: '',
content: '',
saveCode: ''
});
//loading
const loading = ref(false)
//
const route = useRoute();
// update-begin--author:liaozhiyang---date:20240514---forissues/6205
if (props.isCustomSave) {
watch(props.saveSearchData, () => {
currentPageSavedArray.value = props.saveSearchData;
});
} else {
// -->currentPageSavedArray
watch(()=>route.fullPath, (val)=>{
console.log('fullpath', val);
initSaveQueryInfoCode();
});
}
// update-end--author:liaozhiyang---date:20240514---forissues/6205
//
const currentPageSavedArray = ref<SaveModel[]>([]);
// ->saveTreeData
watch(()=>currentPageSavedArray.value, (val)=>{
let temp:any[] = []
if(val && val.length>0){
val.map(item=>{
let key = randomString(16)
temp.push({
title: item.title,
slots: { icon: 'custom' },
value: key
})
})
}
saveTreeData.value = temp
}, {immediate:true, deep: true})
//
function initSaveQueryInfoCode(){
// update-begin--author:liaozhiyang---date:20240514---forissues/6205
if (props.isCustomSave) {
currentPageSavedArray.value = cloneDeep(props.saveSearchData);
} else {
let code = SAVE_CODE_PRE + route.fullPath;
saveInfo.saveCode = code;
let list = $ls.get(code);
if(list && list instanceof Array){
currentPageSavedArray.value = list
}
}
// update-end--author:liaozhiyang---date:20240514---forissues/6205
}
//
initSaveQueryInfoCode();
/**
* 保存按钮事件
*/
function handleSave(){
//
let fieldArray = getQueryInfo();
if(!fieldArray){
$message.warning('空条件不能保存')
return;
}
let content = JSON.stringify(fieldArray)
openSaveInfoModal(content)
}
//
function openSaveInfoModal(content){
saveInfo.visible = true;
saveInfo.title = '';
saveInfo.content = content
}
/**
* 确认保存查询信息
*/
function doSaveQueryInfo(){
let { title, content, saveCode } = saveInfo;
let index = getTitleIndex(title);
const saveSearchCondition = (type) => {
// update-begin--author:liaozhiyang---date:20240514---forissues/6205
const curPageSave: any = cloneDeep(currentPageSavedArray.value);
saveModalLoading.value = true;
if (type) {
//
curPageSave.splice(index, 1, {
content,
title,
type: matchType.value,
});
} else {
curPageSave.push({
content,
title,
type: matchType.value,
});
}
const run = () => {
saveInfo.visible = false;
$message.success('保存成功');
currentPageSavedArray.value = curPageSave;
saveModalLoading.value = false;
};
if (props.isCustomSave) {
props
.save(curPageSave, 0)
.then(() => {
run();
})
.catch((err) => {
saveModalLoading.value = false;
});
} else {
// update-begin--author:liaozhiyang---date:20240306---forQQYUN-8357
const expire = 60 * 60 * 24 * 30;
// update-end--author:liaozhiyang---date:20240306---forQQYUN-8357
$ls.set(saveCode, curPageSave, expire);
run();
}
// update-end--author:liaozhiyang---date:20240514---forissues/6205
};
if (index >= 0) {
//
Modal.confirm({
title: '提示',
content: `${title} 已存在,是否覆盖?`,
okText: '确认',
cancelText: '取消',
onOk: () => {
saveSearchCondition(1);
},
});
} else {
saveSearchCondition(0);
}
}
// title
function getTitleIndex(title){
let savedArray = currentPageSavedArray.value
let index = -1;
for(let i=0;i<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){
let allowEmpty = ['empty', 'not_empty'].includes(item.rule!)
if(item.field && (allowEmpty || item.val || item.val===0) && item.rule){
let prop = fieldProps[item.field]
//
let formatValue = prop?.formatValue ?? (v => v)
let tempVal:any = toRaw(item.val)
if(tempVal instanceof Array){
tempVal = tempVal.map(v => formatValue(v)).join(',')
} else {
tempVal = formatValue(tempVal)
}
let fieldName = getRealFieldName(item)
let obj = {
field: fieldName,
rule: item.rule,
val: tempVal,
fileType: item.fileType ,
};
if(isEmit===true){
//emitdbtypetype
let prop = fieldProps[item.field]
if(prop){
obj['type'] = prop.view
obj['dbType'] = prop.type
}
}
fieldArray.push(obj)
}
}
if(fieldArray.length==0){
return false;
}
return fieldArray
}
//update-begin-author:taoyan date:2022-5-31 for: VUEN-1148
/**
* 高级查询参数 字段名
* 获取后台需要的 字段名格式表名,字段名
* @param item
*/
function getRealFieldName(item){
let fieldName = item.field
if(fieldName.indexOf('@')>0){
fieldName = fieldName.replace('@', ',')
}
return fieldName;
}
//update-end-author:taoyan date:2022-5-31 for: VUEN-1148
/**
* 右侧数据 点击事件重新将数据显示到左侧
* @param key
* @param node
*/
function handleTreeSelect(key, {node}){
console.log(key, node)
let title = node.dataRef.title
let arr = currentPageSavedArray.value.filter(item=>item.title==title)
if(arr && arr.length>0){
//
let { content, type } = arr[0]
let data = JSON.parse(content)
let rowsValues: SuperQueryItem[] = []
for(let item of data){
// update-begin--author:liaozhiyang---date:20240108---forissues/962
item.field = item.field.replace(',','@');
// update-end--author:liaozhiyang---date:20240108---forissues/962
rowsValues.push(Object.assign({}, {key: randomString(16)}, item))
}
dynamicRowValues.values = rowsValues
matchType.value = type
}
}
/**
* 右侧数据 删除事件
*/
function handleRemoveSaveInfo(title){
console.log(title)
let index = getTitleIndex(title)
if(index>=0){
// update-begin--author:liaozhiyang---date:20240513---forissues/6205
if (props.isCustomSave) {
const curPageSave = cloneDeep(currentPageSavedArray.value);
curPageSave.splice(index, 1);
props
.save(curPageSave, 1)
.then(() => {
currentPageSavedArray.value = curPageSave;
})
.catch((err) => {
console.log(`删除是吧~${err}`);
});
} else {
currentPageSavedArray.value.splice(index, 1);
$ls.set(saveInfo.saveCode, currentPageSavedArray.value);
}
// update-end--author:liaozhiyang---date:20240513---forissues/6205
}
}
/*-----------------------右侧保存信息相关-end---------------------------*/
//
function getAllFields(properties){
//
// const {properties, table, title } = json;
let allFields = {}
let order = 1;
let treeData:TreeModel[] = []
/* let mainNode:TreeModel = {
title,
value: table,
disabled: true,
children: []
};*/
//treeData.push(mainNode)
if(properties.properties){
properties = properties.properties
}
Object.keys(properties).map(field=>{
let item = properties[field]
if(item.view == 'table'){
//
//
let subProps = item['properties'] || item['fields']
let subTableOrder = order * 100;
let subNode:TreeModel = {
title: item.title,
value: field,
disabled: true,
children: [],
order: subTableOrder,
// update-begin--author:liaozhiyang---date:20240306---forTV360X-461string
fieldType: item.type,
// update-end--author:liaozhiyang---date:20240306---forTV360X-461string
}
Object.keys(subProps).map(subField=>{
let subItem = subProps[subField];
//
subItem['order'] = subTableOrder + subItem['order']
let subFieldKey = field+'@'+subField
allFields[subFieldKey] = subItem
subNode.children!.push({
title: subItem.title,
value: subFieldKey,
isLeaf: true,
order: subItem['order'],
// update-begin--author:liaozhiyang---date:20240306---forTV360X-461string
fieldType: subItem.type,
view: subItem.view,
originView: subItem.view,
// update-end--author:liaozhiyang---date:20240306---forTV360X-461string
})
});
orderField(subNode);
treeData.push(subNode);
order++;
}else{
//
//let fieldKey = table+'@'+field
let fieldKey = field
allFields[fieldKey] = item
treeData.push({
title: item.title,
value: fieldKey,
isLeaf: true,
order: item.order,
// update-begin--author:liaozhiyang---date:20240306---forTV360X-461string
fieldType: item.type,
view: item.view,
originView: item.view,
// update-end--author:liaozhiyang---date:20240306---forTV360X-461string
});
}
});
orderField(treeData);
return {allFields, treeData}
}
//order
function orderField(data){
let arr = data.children || data;
arr.sort(function (a, b) {
return a.order - b.order
});
}
function initDefaultValues(values) {
const { params, matchType } = values
if(params){
let rowsValues: SuperQueryItem[] = []
for(let item of params){
rowsValues.push(Object.assign({}, {key: randomString(16)}, item))
}
dynamicRowValues.values = rowsValues
matchType.value = matchType
}
}
return {
formRef,
init,
dynamicRowValues,
matchType,
registerModal,
handleSubmit,
handleCancel,
handleSave,
doSaveQueryInfo,
saveInfo,
saveTreeData,
handleRemoveSaveInfo,
handleTreeSelect,
fieldTreeData,
addOne,
removeOne,
setFormModel,
getSchema,
loading,
getQueryInfo,
initDefaultValues,
saveModalLoading,
fieldProperties,
}
}

View File

@ -0,0 +1,532 @@
<template>
<div :class="['p-2', `online-list-${ID}`]">
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
@loaded="onQueryFormLoaded"
/>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
>
<template #tableTitle>
<a-button
v-if="buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAdd"
>
<span>{{cgBIBtnMap['add'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.import && cgBIBtnMap['import'].enabled"
type="primary"
:preIcon="cgBIBtnMap['import'].buttonIcon"
@click="onImportExcel"
>
<span>{{cgBIBtnMap['import'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
:loading="exportLoading"
@click="onExportExcelOverride"
>
<span>{{cgBIBtnMap['export'].buttonName}}</span>
</a-button>
<!-- update-begin--author:liaozhiyang---date:20250403---forQQYUN-11801生成测试数据 -->
<a-tooltip
v-if="!isConfigCurRoute && (tableType == 1 || tableType == 2) && buttonSwitch.aigc_mock_data && cgBIBtnMap['aigc_mock_data']?.enabled && dataSource.length == 0 && !loading"
:open="false"
placement="bottom"
>
<template #title>
<span>当有数据时或生成测试数据后该按钮会隐藏</span>
</template>
<a-button preIcon="mdi:robot-love-outline" :loading="testDataLoading" @click="handleAddTestData(currentTableName, reload)">
{{ cgBIBtnMap['aigc_mock_data'].buttonName }}
</a-button>
</a-tooltip>
<!-- update-end--author:liaozhiyang---date:20250403---forQQYUN-11801生成测试数据 -->
<!-- 自定义按钮 -->
<template v-if="cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
<online-super-query
v-if="buttonSwitch.super_query && cgBIBtnMap['super_query'].enabled"
ref="superQueryButtonRef"
online
:status="superQueryStatus"
:queryBtnCfg="cgBIBtnMap['super_query']"
@search="handleSuperQuery"
/>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</BasicTable>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="reload"
@formConfig="handleFormConfig"
/>
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal"/>
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false"/>
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
<!-- 页面loading[主要为了js增强使用] -->
<Loading :loading="pageLoading" :absolute="true" />
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from './OnlineAutoModal.vue';
import OnlineCustomModal from './OnlineCustomModal.vue';
import OnlineDetailModal from './OnlineDetailModal.vue'
import { watch } from 'vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import OnlineSuperQuery from '../comp/superquery/SuperQuery.vue';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import { NORMAL } from "../../util/constant";
import { Loading } from '/@/components/Loading';
export default {
name: 'OnlineAutoList',
components: {
BasicTable,
TableAction,
OnlineAutoModal,
JImportModal,
OnlineQueryForm,
OnlineSuperQuery,
OnlineCustomModal,
OnlineDetailModal,
OnlinePopModal,
Loading,
},
setup() {
const { createMessage: $message } = useMessage();
const currentTableName = ref('');
const tableType = ref('');
// --
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
onlineExtConfigJson,
handleFormConfig,
registerCustomModal,
tableReloading,
isConfigCurRoute,
pageLoading,
} = useOnlineTableContext();
// ID
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
throw new Error('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgLinkButtonList,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleEdit,
handleBatchDelete,
handleAddTestData,
testDataLoading,
testDataBtnShow,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleSubmitFlow,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal
} = useListButton(onlineTableContext, onlineExtConfigJson);
const exportLoading = ref(false)
//
async function onExportExcelOverride() {
try {
exportLoading.value = true
await onExportExcel()
} finally {
// 1.5sloading
setTimeout(() => exportLoading.value = false, 1500)
}
}
// BasicTable
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
// ID
watch(
ID,
() => {
console.log('watched id is change...');
initAutoList();
},
{ immediate: true }
);
/**重新加载online配置*/
async function initAutoList() {
loading.value = true;
// 1.
let columnResult: any = await getColumnList(NORMAL);
handleTableConfig(columnResult);
// update-begin--author:liaozhiyang---date:20250403---forQQYUN-11801
currentTableName.value = columnResult.currentTableName;
tableType.value = columnResult.tableType;
// update-end--author:liaozhiyang---date:20250403---forQQYUN-11801
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data) {
onlineTableContext['queryParam'] = data;
reload({mode:'search'});
}
/**
* 查询组件加载完成事件获取高级查询需要的字段信息
*/
async function onQueryFormLoaded(json) {
console.log('onQueryFormLoaded', json)
await getRefPromise(superQueryButtonRef)
superQueryButtonRef.value.init(json);
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params){
console.log('openOnlinePopModal', params)
popTableId.value = params.id;
let data = {
title: params.describe
}
if(params.record && params.record.id){
data['record'] = params.record
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
const that = {
ID,
//
onlineQueryFormOuter,
queryWithCondition,
onQueryFormLoaded,
reload,
//
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
// table
loading,
columns,
dataSource,
pagination,
actionColumn,
rowSelection,
selectedKeys,
tableScroll,
enableScrollBar,
handleChangeInTable,
//
buttonSwitch,
handleAdd,
handleEdit,
onImportExcel,
onExportExcel,
exportLoading,
onExportExcelOverride,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
cgLinkButtonList,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleBatchDelete,
handleAddTestData,
testDataLoading,
testDataBtnShow,
currentTableName,
loadData,
tableType,
isConfigCurRoute,
// table-slot
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
//
getActions,
getDropDownActions,
//
registerModal,
registerCustomModal,
registerImportModal,
registerDetailModal,
importUrl,
handleFormConfig,
onlinePopModalRef,
//
tableReloading,
handleSubmitFlow,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
onlineExtConfigJson,
registerOnlineHrefModal,
registerPopModal,
popTableId,
registerBpmModal,
handleClickFieldHref,
pageLoading,
};
return that;
},
// 1loadsh console.log(that.simpleDateFormat(new Date().getTime(),'yyyy-MM-dd'));
// 2. value
// 3. api
// 1 js
// 2css js document headcss
};
</script>
<style lang="less">
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<BasicModal :title="title" @cancel="onCloseEvent" :enableComment="enableComment" :width="modalWidth" v-bind="$attrs" :maxHeight="600" @register="registerModal" wrapClassName="jeecg-online-modal" @ok="handleSubmit" @commentOpen="handleCommentOpen">
<template #footer>
<a-row>
<a-col :span='24 - commentSpan'>
<a-button
v-for="btn in cgButtonList"
:key="btn.id"
type="primary"
@click="handleCgButtonClick(btn.optType, btn.buttonCode)"
:preIcon="btn.buttonIcon ? 'ant-design:' + btn.buttonIcon : ''"
>
{{ btn.buttonName }}
</a-button>
<a-button
v-if="!disableSubmit && confirmBtnCfg.enabled"
key="submit"
type="primary"
:preIcon="confirmBtnCfg.buttonIcon"
:loading="submitLoading"
@click="handleSubmit"
>
<span>{{ confirmBtnCfg.buttonName }}</span>
</a-button>
<a-button v-if="cancelBtnCfg.enabled" key="back" @click="handleCancel">
<span>{{ cancelBtnCfg.buttonName }}</span>
</a-button>
</a-col>
</a-row>
</template>
<online-form
v-bind="$attrs"
ref="onlineFormCompRef"
:id="id"
:disabled="disableSubmit"
:form-template="formTemplate"
:isTree="isTreeForm"
:pidField="pidFieldName"
:themeTemplate="themeTemplate"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
@rendered="renderSuccess"
@success="handleSuccess"
>
</online-form>
<template #comment>
<comment-panel ref="commentPanelRef" :tableId="tableId" :tableName="tableName" :dataId="formDataId"></comment-panel>
</template>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlineForm from '../comp/OnlineForm.vue';
import { useAutoModal } from '../../hooks/auto/useAutoModal';
import CommentPanel from '/@/components/jeecg/comment/CommentPanel.vue'
import { ERPSUBTABLE } from '../../util/constant';
export default defineComponent({
name: 'OnlineAutoModal',
props: {
id: {
type: String,
default: '',
},
// erp
source: {
type: String,
default: '',
},
buttonSwitch: Object,
cgBIBtnMap: Object,
confirmBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '确定',
buttonIcon: '',
}
}
},
cancelBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '关闭',
buttonIcon: '',
}
}
},
},
components: {
BasicModal,
OnlineForm,
CommentPanel
},
emits: ['success', 'register', 'formConfig'],
setup(props, { emit }) {
console.log('进入表单弹框》》》》modal');
const commentPanelRef = ref();
const commentSpan = ref(0);
function reloadComment(){
if(commentPanelRef.value)
commentPanelRef.value.reload();
}
const {
title,
modalWidth,
registerModal,
closeModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
tableName,
tableId,
formDataId,
enableComment,
onCloseEvent,
themeTemplate,
} = useAutoModal(false, { emit }, reloadComment);
function handleSuccess(formData) {
emit('success', formData);
closeModal();
//
onCloseEvent();
}
// id
watch(() => props.id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!props.id) {
return;
}
console.log('重新渲染表单》》》》modal');
// update-begin--author:liaozhiyang---date:20240426---forissues/6124Online
const params: any = {};
if (props.source === ERPSUBTABLE) {
params.tabletype = 3;
}
await handleFormConfig(props.id, params);
// update-end--author:liaozhiyang---date:20240426---forissues/6124Online
}
// update-begin--author:liaozhiyang---date:20240528---forTV360X-485
const handleCommentOpen = (visible, span) => {
console.log('评论是否展开:', visible);
commentSpan.value = span;
}
// update-end--author:liaozhiyang---date:20240528---forTV360X-485
const that = {
title,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleSuccess,
handleCancel,
modalWidth,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
tableName,
tableId,
formDataId,
enableComment,
commentPanelRef,
onCloseEvent,
themeTemplate,
handleCommentOpen,
commentSpan,
};
return that;
},
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,286 @@
<template>
<BasicModal
v-bind="modalProps"
:style="modalStyle"
@register="registerModal"
wrapClassName="jeecg-online-modal2"
@ok="handleSubmit"
>
<template #footer>
<a-button key="submit" type="primary" @click="handleSubmit"> {{modalProps.okText ?? '确定'}}</a-button>
<a-button key="back" @click="handleCancel">{{ modalProps.cancelText ?? '关闭' }}</a-button>
</template>
<a-spin :spinning="confirmLoading">
<!-- online表单 -->
<online-form
v-if="isOnlineForm"
ref="onlineFormCompRef"
:id="id"
:form-template="formTemplate"
@rendered="renderSuccess"
@success="handleSuccess"
modalClass="jeecg-online-modal2"
>
</online-form>
<!-- 自定义表单 -->
<component
v-else
ref="customFormRef"
:url="customFormComponent.url"
:is="customFormComponent.is"
:row="customFormComponent.row"
@close="handleSuccess"
>
</component>
</a-spin>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, watch, nextTick, defineAsyncComponent, markRaw } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import { defHttp } from '/@/utils/http/axios';
import OnlineForm from '../comp/OnlineForm.vue';
import { importViewsFile } from '/@/utils';
import { omit } from 'lodash-es';
/**
* open函数的参数
*/
interface ModalConfig {
row: any;
hide?: string[];
show?: string[];
requestUrl?: string;
tableType?: string;
foreignKeys?: string;
formComponent?: string;
code?: string;
}
/**
* 默认配置
*/
const DEF_CONFIG: ModalConfig = {
code: '',
row: {},
hide: [],
show: [],
requestUrl: '',
tableType: '',
foreignKeys: '',
formComponent: '',
};
/**
* 自定义弹窗
*/
export default defineComponent({
name: 'OnlineCustomModal',
components: {
OnlineForm,
BasicModal,
},
setup(_props, { emit, attrs }) {
const onlineFormCompRef = ref();
const modalProps = ref({});
// onlineid
const id = ref('');
let onlineHideFields = [];
let onlineShowFields = [];
let onlineFormEditUrl = '';
//
let currentRowData = {};
const url = {
loadFormItems: '/online/cgform/api/getFormItem/',
optPre: '/online/cgform/api/form/',
};
const modalStyle = { position: 'relative' };
const confirmLoading = ref(false);
//
const formRendered = ref(false);
//
function renderSuccess() {
formRendered.value = true;
}
//online
const isOnlineForm = ref(true);
// onlineFormCompRef---show
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (params) => {
setModalProps({ confirmLoading: false });
resetParams(params);
await nextTick(async () => {
if (!params.formComponent) {
// online
await showOnlineForm();
} else {
showCustomForm(params.formComponent);
}
});
});
/**
* 打开弹窗
* @param params
*/
function resetParams(params) {
// code row formComponent hide show
let options = Object.assign({}, DEF_CONFIG, params);
id.value = options.code;
// update-begin--author:liaozhiyang---date:20250818---forissues/8672jsbasicModalprops
modalProps.value = {
title: '自定义弹框',
width: 600,
...attrs,
...omit(params, ['row', 'formComponent', 'hide', 'show', 'requestUrl']),
};
// update-end--author:liaozhiyang---date:20250818---forissues/8672jsbasicModalprops
onlineHideFields = options.hide || [];
onlineShowFields = options.show || [];
onlineFormEditUrl = getOnlineFormEditUrl(options.requestUrl);
currentRowData = options.row;
}
/**
* 获取表单数据编辑地址-对两个表单都有效
*/
function getOnlineFormEditUrl(requestUrl) {
if (requestUrl) {
return requestUrl;
} else {
return url.optPre + id.value;
}
}
const submitLoading = ref(false);
function handleSubmit() {
submitLoading.value = true;
if (isOnlineForm.value === true) {
onlineFormCompRef.value.handleSubmit();
} else {
customFormRef.value.handleSubmit();
}
setTimeout(() => {
submitLoading.value = true;
}, 3500);
}
function handleCancel() {
closeModal();
}
function handleSuccess(formData) {
emit('success', formData);
closeModal();
}
/*-------------------------------------online表单-----------------------------------------------*/
/**
* 显示online表单
*/
async function showOnlineForm() {
isOnlineForm.value = true;
await getRefPromise(formRendered);
//
onlineFormCompRef.value.handleCustomFormSh(onlineShowFields, onlineHideFields);
//
onlineFormCompRef.value.handleCustomFormEdit(currentRowData, onlineFormEditUrl);
}
// -1
const formTemplate = ref(1);
// id
watch(id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!id.value) {
return;
}
console.log('重新渲染表单》》》》modal');
let result: any = await loadFormItems();
// modalJS
let dataFormTemplate = result.head.formTemplate;
formTemplate.value = dataFormTemplate ? Number(dataFormTemplate) : 1;
nextTick(async () => {
let myForm = (await getRefPromise(onlineFormCompRef)) as any;
myForm.createRootProperties(result);
});
}
function loadFormItems() {
let url = `/online/cgform/api/getFormItem/${id.value}`;
return new Promise((resolve, reject) => {
defHttp
.get({ url }, { isTransformResponse: false })
.then((res) => {
console.log('表单结果》》modal:', res);
if (res.success) {
resolve(res.result);
} else {
reject(res.message);
}
})
.catch(() => {
reject();
});
});
}
/*-------------------------------------online表单-----------------------------------------------*/
/*-------------------------------------自定义表单-----------------------------------------------*/
const customFormRef = ref();
const customFormComponent = reactive({
url: '',
is: '',
row: {},
});
/**
* 显示自定义表单
*/
function showCustomForm(formComponent) {
isOnlineForm.value = false;
customFormComponent.url = onlineFormEditUrl;
customFormComponent.row = currentRowData;
customFormComponent.is = markRaw(defineAsyncComponent(() => importViewsFile(formComponent)));
}
/*-------------------------------------自定义表单-----------------------------------------------*/
return {
//modal
registerModal,
modalProps,
modalStyle,
handleSubmit,
handleCancel,
// online
id,
onlineFormCompRef,
formTemplate,
renderSuccess,
//
customFormRef,
customFormComponent,
//
open,
isOnlineForm,
confirmLoading,
submitLoading,
handleSuccess,
};
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,135 @@
<template>
<BasicModal :title="title" :width="modalWidth" :maxHeight="600" :enableComment="enableComment" :defaultFullscreen="false" v-bind="$attrs" @register="registerModal" wrapClassName="jeecg-online-detail-modal">
<template #footer>
<a-button v-if="cancelBtnCfg.enabled" key="back" @click="handleCancel">
<span>{{ cancelBtnCfg.buttonName }}</span>
</a-button>
<slot name="footerBtn"></slot>
</template>
<online-form-detail ref="onlineFormCompRef" :id="id" :form-template="formTemplate" :show-sub="showSub" :themeTemplate="themeTemplate" @rendered="renderSuccess" />
<template #comment>
<comment-panel ref="commentPanelRef" :tableName="tableName" :dataId="formDataId"></comment-panel>
</template>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlineFormDetail from '../comp/OnlineFormDetail.vue';
import { useAutoModal } from '../../hooks/auto/useAutoModal';
import CommentPanel from '/@/components/jeecg/comment/CommentPanel.vue'
import { ERPSUBTABLE, INNER_TABLE } from '../../util/constant';
export default defineComponent({
name: 'OnlineDetailModal',
props: {
id: {
type: String,
required: false,
default: '',
},
// erp
source: {
type: String,
default: '',
},
cancelBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '关闭',
buttonIcon: '',
}
}
},
},
components: {
BasicModal,
OnlineFormDetail,
CommentPanel
},
emits: ['success', 'register','formConfig'],
setup(props, {emit}) {
console.log('进入表单弹框》》》》modal');
const commentPanelRef = ref();
function reloadComment(){
if(commentPanelRef.value)
commentPanelRef.value.reload();
}
const {
title,
modalWidth,
registerModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
showSub,
tableName,
formDataId,
enableComment,
themeTemplate,
} = useAutoModal(false, { emit }, reloadComment);
// id
watch(() => props.id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!props.id) {
return;
}
console.log('重新渲染表单》》》》modal');
// update-begin--author:liaozhiyang---date:20240426---forissues/6124Online
let params: any = {};
//update-begin---author:wangshuai---date:2025-10-21---for:issues/8933+访()---
if (props.source === ERPSUBTABLE || props.source === INNER_TABLE) {
//update-end---author:wangshuai---date:2025-10-21---for:issues/8933+访()---
params.tabletype = 3;
}
await handleFormConfig(props.id, params);
// update-end--author:liaozhiyang---date:20240426---forissues/6124Online
}
const that = {
title,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleCancel,
modalWidth,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
showSub,
tableName,
formDataId,
enableComment,
commentPanelRef,
themeTemplate
};
return that;
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,153 @@
<template>
<div :class="['p-2']" class="online-wrap">
<Card :bordered="false">
<spin :spinning="loading">
<div :class="['wrap', `online-add-${ID}`]" ref="wrapRef">
<OnlineForm v-if="false"></OnlineForm>
<!-- 新增(编辑)弹框 -->
<OnlineAutoModal
v-if="token"
:id="ID"
:maskClosable="false"
@register="registerModal"
:getContainer="getContainer"
@formConfig="handleFormConfig"
@success="success"
:height="contentHeight"
/>
</div>
</spin>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref, watch, unref } from 'vue';
import { Card, Spin } from 'ant-design-vue';
// OnlineForm setuptemplate使url
import OnlineForm from '../comp/OnlineForm.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from './OnlineAutoModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { useRouter } from 'vue-router';
import { useFormUrl } from '../../hooks/auto/useFormUrl';
const { ID, onlineTableContext, onlineExtConfigJson, handleFormConfig } = useOnlineTableContext();
const { createMessage: $message } = useMessage();
const wrapRef = ref(null);
const contentHeight = ref(400);
const loading = ref(true);
const router = useRouter();
const { token } = useFormUrl();
if (!unref(token)) {
throw new Error('token不存在~');
}
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
throw new Error('地址错误, 配置ID不存在!');
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
watch(
() => wrapRef.value,
(elem: HTMLElement) => {
//
contentHeight.value = elem.offsetHeight - 60;
}
);
// button
const { registerModal, handleAdd } = useListButton(onlineTableContext, onlineExtConfigJson);
const getContainer = (node) => {
return document.querySelector(`.online-add-${ID.value}`);
};
const success = () => {
setTimeout(() => {
handleAdd(false);
setTimeout(() => {
router.push({ path: '/online/formUrlSuccess' });
}, 1e3);
}, 0);
};
setTimeout(() => {
handleAdd(false);
loading.value = false;
}, 1e3);
</script>
<style lang="less" scoped>
.online-wrap {
height: 100%;
// position: relative;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1000;
padding: 0;
:deep(.ant-card) {
padding: 0;
height: 100%;
border: none;
.ant-card-body {
height: 100%;
padding: 40px 16px;
}
.ant-modal-footer {
& > :last-child {
display: none;
}
}
}
:deep(.ant-spin-nested-loading) {
height: 100%;
width: 100%;
.ant-spin-container {
height: 100%;
}
}
}
.wrap {
height: 100%;
> div {
height: 100%;
}
:deep(.ant-modal-root) {
height: 100%;
.ant-modal-mask {
height: 0;
}
.ant-modal-wrap {
position: static;
height: 100%;
}
.ant-modal {
position: static;
height: 100%;
width: 100% !important;
height: 100% !important;
transform: scale(1) !important;
opacity: 1;
animation-name: none !important;
}
.ant-modal-content {
position: static;
box-shadow: none;
.scroll-container {
padding: 0;
.scrollbar__view > div {
overflow: auto;
}
}
}
.ant-modal-header {
display: none;
}
.ant-modal-close {
display: none;
}
}
}
</style>

View File

@ -0,0 +1,139 @@
<template>
<div :class="['p-2']" class="online-wrap">
<Card :bordered="false">
<spin :spinning="loading">
<div :class="['wrap', `online-detail-${ID}`]" ref="wrapRef">
<OnlineForm v-if="false"></OnlineForm>
<!-- 详情弹框 -->
<OnlineDetailModal
v-if="token"
:id="ID"
:maskClosable="false"
@register="registerDetailModal"
@success="success"
:getContainer="getContainer"
:height="contentHeight"
/>
</div>
</spin>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref, watch, unref } from 'vue';
import { Card, Spin } from 'ant-design-vue';
// OnlineForm setuptemplate使url
import OnlineForm from '../comp/OnlineForm.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useRoute } from 'vue-router';
import OnlineDetailModal from './OnlineDetailModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { useFormUrl } from '../../hooks/auto/useFormUrl';
const route = useRoute();
const { ID, onlineTableContext, onlineExtConfigJson } = useOnlineTableContext();
const { createMessage: $message } = useMessage();
const wrapRef = ref(null);
const contentHeight = ref(400);
const loading = ref(true);
const { token } = useFormUrl();
if (!unref(token)) {
throw new Error('token不存在~');
}
if (!ID.value || !route.params.dataId) {
$message.warning('地址错误, 配置ID不存在!');
throw new Error('地址错误, 配置ID不存在!');
}
watch(
() => wrapRef.value,
(elem: HTMLElement) => {
//
contentHeight.value = elem.offsetHeight;
}
);
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const { registerDetailModal, openDetailModal } = useListButton(onlineTableContext, onlineExtConfigJson);
const getContainer = (node) => {
return document.querySelector(`.online-detail-${ID.value}`);
};
setTimeout(() => {
openDetailModal(true, {
isUpdate: true,
disableSubmit: true,
record: {
id: route.params.dataId,
},
});
loading.value = false;
}, 1e3);
</script>
<style lang="less" scoped>
.online-wrap {
height: 100%;
position: relative;
:deep(.ant-card) {
padding: 0;
height: 100%;
border: none;
.ant-card-body {
height: 100%;
padding: 5px;
}
}
:deep(.ant-spin-nested-loading) {
height: 100%;
width: 100%;
.ant-spin-container {
height: 100%;
}
}
}
.wrap {
height: 100%;
> div {
height: 100%;
}
:deep(.ant-modal-root) {
height: 100%;
.ant-modal-mask {
height: 0;
}
.ant-modal-wrap {
position: static;
height: 100%;
}
.ant-modal {
position: static;
height: 100%;
width: 100% !important;
height: 100% !important;
transform: scale(1) !important;
opacity: 1;
animation-name: none !important;
}
.ant-modal-content {
position: static;
box-shadow: none;
.scroll-container {
padding: 0;
.scrollbar__view > div {
overflow: auto;
}
}
}
.ant-modal-header,
.ant-modal-footer {
display: none;
}
.ant-modal-close {
display: none;
}
}
}
</style>

View File

@ -0,0 +1,152 @@
<template>
<div :class="['p-2']" class="online-wrap">
<Card :bordered="false">
<spin :spinning="loading">
<div :class="['wrap', `online-edit-${ID}`]" ref="wrapRef">
<OnlineForm v-if="false"></OnlineForm>
<!-- 新增(编辑)弹框 -->
<OnlineAutoModal
v-if="token"
:id="ID"
:maskClosable="false"
@register="registerModal"
:getContainer="getContainer"
@formConfig="handleFormConfig"
@success="success"
:height="contentHeight"
/>
</div>
</spin>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref, unref, watch } from 'vue';
import { Card, Spin } from 'ant-design-vue';
// OnlineForm setuptemplate使url
import OnlineForm from '../comp/OnlineForm.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useRoute, useRouter } from 'vue-router';
import OnlineAutoModal from './OnlineAutoModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { useFormUrl } from '../../hooks/auto/useFormUrl';
const { ID, onlineTableContext, onlineExtConfigJson, handleFormConfig } = useOnlineTableContext();
const { createMessage: $message } = useMessage();
const route = useRoute();
const wrapRef = ref(null);
const contentHeight = ref(400);
const loading = ref(true);
const router = useRouter();
const { token } = useFormUrl();
if (!unref(token)) {
throw new Error('token不存在~');
}
if (!ID.value || !route.params.dataId) {
$message.warning('地址错误, 配置ID不存在!');
throw new Error('地址错误, 配置ID不存在!');
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
watch(
() => wrapRef.value,
(elem: HTMLElement) => {
//
contentHeight.value = elem.offsetHeight - 60;
}
);
// button
const { registerModal, handleEdit } = useListButton(onlineTableContext, onlineExtConfigJson);
const getContainer = (node) => {
return document.querySelector(`.online-edit-${ID.value}`);
};
const success = () => {
setTimeout(() => {
handleEdit({ id: route.params.dataId });
router.push({ path: '/online/formUrlSuccess' });
}, 0);
};
setTimeout(() => {
token && handleEdit({ id: route.params.dataId });
loading.value = false;
}, 1e3);
</script>
<style lang="less" scoped>
.online-wrap {
height: 100%;
// position: relative;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1000;
padding: 0;
:deep(.ant-card) {
padding: 0;
height: 100%;
border: none;
.ant-card-body {
height: 100%;
padding: 40px 16px;
}
.ant-modal-footer {
& > :last-child {
display: none;
}
}
}
:deep(.ant-spin-nested-loading) {
height: 100%;
width: 100%;
.ant-spin-container {
height: 100%;
}
}
}
.wrap {
height: 100%;
> div {
height: 100%;
}
:deep(.ant-modal-root) {
height: 100%;
.ant-modal-mask {
height: 0;
}
.ant-modal-wrap {
position: static;
height: 100%;
}
.ant-modal {
position: static;
height: 100%;
width: 100% !important;
height: 100% !important;
transform: scale(1) !important;
opacity: 1;
animation-name: none !important;
}
.ant-modal-content {
position: static;
box-shadow: none;
.scroll-container {
padding: 0;
.scrollbar__view > div {
overflow: auto;
}
}
}
.ant-modal-header {
display: none;
}
.ant-modal-close {
display: none;
}
}
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div :class="['p-2']" class="online-wrap">
<div class="contentArea">
<svg t="1709697840296" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1485">
<path
d="M874.119618 149.859922A510.816461 510.816461 0 0 0 511.997 0.00208a509.910462 509.910462 0 0 0-362.119618 149.857842c-199.817789 199.679789-199.817789 524.581447 0 724.260236a509.969462 509.969462 0 0 0 362.119618 149.857842A508.872463 508.872463 0 0 0 874.119618 874.120158c199.836789-199.679789 199.836789-524.581447 0-724.260236zM814.94268 378.210681L470.999043 744.132295a15.359984 15.359984 0 0 1-5.887994 4.095996c-1.751998 1.180999-2.913997 2.362998-5.276994 2.913997a34.499964 34.499964 0 0 1-13.469986 2.914997 45.547952 45.547952 0 0 1-12.897986-2.303998l-4.095996-2.363997a45.291952 45.291952 0 0 1-7.009992-4.095996l-196.902793-193.789796a34.126964 34.126964 0 0 1-10.555989-25.186973c0-9.37399 3.583996-18.74698 9.98399-25.186974a36.429962 36.429962 0 0 1 50.372947 0l169.98382 167.423824L763.389735 330.220732a37.059961 37.059961 0 0 1 50.371947-1.732998 33.647965 33.647965 0 0 1 11.165988 25.186973 35.544963 35.544963 0 0 1-9.98399 24.575974v-0.04z m0 0"
fill="currentColor"
p-id="1486"
></path>
</svg>
<h1>提交成功</h1>
</div>
<div class="btnArea">
<a-button @click="getBack()">返回</a-button>
<a-button @click="getBack()">关闭</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
const getBack = () => {
router.go(-1);
};
</script>
<style lang="less" scoped>
.online-wrap {
height: 100%;
// position: relative;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1000;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
.contentArea {
text-align: center;
padding-top: 10%;
margin-bottom: 24px;
svg {
width: 80px;
color: #40c409;
margin-bottom: 24px;
}
h1 {
font-size: 24px;
line-height: 1;
}
}
.btnArea {
width: 150px;
display: flex;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,537 @@
<template>
<div ref="cgformErpListRef" class="p-2 cgformErpList">
<div class="content">
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
@loaded="onQueryFormLoaded"
/>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="true"
:scroll="scroll"
@table-redo="reload"
:tableSetting="tableSetting"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
>
<template #tableTitle>
<a-button
v-if="buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAdd"
>
<span>{{ cgBIBtnMap['add'].buttonName }}</span>
</a-button>
<a-button
v-if="buttonSwitch.import && cgBIBtnMap['import'].enabled"
type="primary"
:preIcon="cgBIBtnMap['import'].buttonIcon"
@click="onImportExcel"
>
<span>{{ cgBIBtnMap['import'].buttonName }}</span>
</a-button>
<a-button
v-if="buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
:loading="exportLoading"
@click="onExportExcelOverride"
>
<span>{{ cgBIBtnMap['export'].buttonName }}</span>
</a-button>
<!-- 自定义按钮 -->
<template v-if="cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
<online-super-query
v-if="buttonSwitch.super_query && cgBIBtnMap['super_query'].enabled"
ref="superQueryButtonRef"
online
:status="superQueryStatus"
:queryBtnCfg="cgBIBtnMap['super_query']"
@search="handleSuperSearch"
/>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</BasicTable>
<!--子表表格tab-->
<a-tabs v-if="subTableData?.length" animated v-model:activeKey="tabActiveKey" style="margin: 10px">
<a-tab-pane v-for="(item, index) in subTableData" :tab="item.description" :key="index" forceRender>
<OnlCgformErpSubTable :data="item" :mainTableSelectedRowRcord="selectedRowRcord" @getSource="getSource" />
</a-tab-pane>
</a-tabs>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:subTableSource="erpAllSubTableSource"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="reload"
@formConfig="handleFormConfig"
/>
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal" />
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false" />
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
<!-- 页面loading[主要为了js增强使用] -->
<Loading :loading="pageLoading" :absolute="true" />
</div>
</div>
</template>
<script setup name="CgformErpList">
import { ref, nextTick, computed, watchEffect } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from '../default/OnlineAutoModal.vue';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import OnlineDetailModal from '../default/OnlineDetailModal.vue';
import { watch } from 'vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import OnlineSuperQuery from '../comp/superquery/SuperQuery.vue';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import OnlCgformErpSubTable from './OnlCgformErpSubTable.vue';
import { ERP } from "../../util/constant";
import { cloneDeep } from 'lodash-es';
import { Loading } from '/@/components/Loading';
import { isObject } from '/@/utils/is';
const subTableData = ref([]);
const tabActiveKey = ref(0);
const selectedRowRcord = ref(null);
const tableSetting = ref({});
const { createMessage: $message } = useMessage();
const cgformErpListRef = ref(null);
const onlineTable = ref(null);
// update-begin--author:liaozhiyang---date:20231128---forQQYUN-7260erp
const erpAllSubTableSource = ref({});
const getSource = (tableName, data) => {
erpAllSubTableSource.value[tableName] = data;
};
// update-end--author:liaozhiyang---date:20231128---forQQYUN-7260erp
// --
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
onlineExtConfigJson,
handleFormConfig,
registerCustomModal,
tableReloading,
pageLoading,
} = useOnlineTableContext({ themeTemplate: ERP });
const scroll = computed(() => {
const reuslt = { y: 300 };
if (isObject(tableScroll.value)) {
return { ...tableScroll.value, ...reuslt };
}
return reuslt;
});
//
watch(dataSource, (value) => {
if (selectedKeys.value.length > 0) {
// TV360X-2701
selectedKeys.value = selectedKeys.value.filter((key) => value.some((item) => item['jeecg_row_key'] === key));
}
// update-begin--author:liaozhiyang---date:20250722---forissues/8575erp
if (pagination.value.current == 1 && selectedKeys.value.length === 0) {
setTimeout(() => {
const tableTbodyElem = document.querySelector('.ant-table-wrapper .ant-table-tbody');
tableTbodyElem?.querySelector('.ant-table-row')?.click();
}, 100);
}
// update-end--author:liaozhiyang---date:20250722---forissues/8575erp
});
// ID
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
throw new Error('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgLinkButtonList,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleEdit,
handleBatchDelete,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleSubmitFlow,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson, {
singleDelCallback: (id) => {
// key
if (Array.isArray(onlineTableContext['selectedRowKeys']) && onlineTableContext['selectedRowKeys'].includes(id)) {
onlineTableContext['clearSelectedRow']();
}
},
editClickCallback: (id, e) => {
//
if (Array.isArray(onlineTableContext['selectedRowKeys']) && onlineTableContext['selectedRowKeys'].includes(id)) {
e.stopPropagation();
}
},
});
const exportLoading = ref(false);
//
async function onExportExcelOverride() {
try {
exportLoading.value = true;
await onExportExcel();
} finally {
// 1.5sloading
setTimeout(() => (exportLoading.value = false), 1500);
}
}
// BasicTable
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
watch(
selectedKeys,
(value) => {
if (selectedKeys.value?.length) {
selectedRowRcord.value = dataSource.value.find((item) => item['id'] === selectedKeys.value[0]);
}else{
selectedRowRcord.value = null;
}
},
{
immediate: true,
}
);
// ID
watch(
ID,
() => {
console.log('watched id is change...');
initAutoList();
},
{ immediate: true }
);
/**重新加载online配置*/
async function initAutoList() {
loading.value = true;
// 1.
let columnResult = await getColumnList(ERP);
handleTableConfig(columnResult.main);
subTableData.value = columnResult.subList;
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result, 'radio');
//
handleSpecialConfig(result);
// update-begin--author:liaozhiyang---date:20240611---forTV360X-1004erp
tableSetting.value = {
cacheKey: `online_erp_mainTable_${result.currentTableName}`,
};
// update-end--author:liaozhiyang---date:20240611---forTV360X-1004erp
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data) {
onlineTableContext['queryParam'] = data;
reload({mode:'search'});
}
/**
* 查询组件加载完成事件获取高级查询需要的字段信息
*/
async function onQueryFormLoaded(json = {}) {
console.log('onQueryFormLoaded', json);
await getRefPromise(superQueryButtonRef);
// update-begin--author:liaozhiyang---date:20230904---forQQYUN-6425erp
const cJson = cloneDeep(json),
{ properties = {} } = cJson;
Object.entries(properties).forEach(([key, item]) => {
if (item.view == 'table') {
delete properties[key];
}
});
superQueryButtonRef.value.init(cJson);
// update-end--author:liaozhiyang---date:20230904---forQQYUN-6425erp
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params) {
console.log('openOnlinePopModal', params);
popTableId.value = params.id;
let data = {
title: params.describe,
};
if (params.record && params.record.id) {
data['record'] = params.record;
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
console.log('111111111-------onlineTableContext====111111111111111111', onlineTableContext);
const handleSuperSearch = (params, matchType) => {
handleSuperQuery(params, matchType);
selectedKeys.value = [];
};
// erp5
watchEffect(() => {
const size = onlineTable?.value?.getBindValuesRef().value?.size
setTimeout(() => {
const tableTbodyElem = cgformErpListRef.value.querySelector('.ant-table-wrapper .ant-table-body');
if (tableTbodyElem) {
tableTbodyElem.style.height = 'auto';
if (dataSource.value.length > 0) {
if (tableTbodyElem.offsetHeight < 300) {
tableTbodyElem.style.height = `${tableTbodyElem.offsetHeight + 1}px`;
console.log(size);
}
}
}
}, 0);
}, {
flush: 'post'
});
// 1loadsh console.log(that.simpleDateFormat(new Date().getTime(),'yyyy-MM-dd'));
// 2. value
// 3. api
// 1 js
// 2css js document headcss
</script>
<style lang="less">
// update-begin--author:liaozhiyang---date:20240313---forQQYUN-8493onlineErp
html[data-theme='light'] {
.cgformErpList {
height: 100%;
.content {
background-color: #fff;
height: 100%;
}
}
}
// update-end--author:liaozhiyang---date:20240313---forQQYUN-8493onlineErp
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>
<style lang="less" scoped>
// 5
:deep(.ant-table-body) {
line-height: 22px;
}
</style>

View File

@ -0,0 +1,403 @@
<template>
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-if="mainTableSelectedRowRcord"
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
/>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="false"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
:tableSetting="tableSetting"
@change="handleChangeInTable"
:minHeight="300"
>
<template #tableTitle>
<a-button
v-if="btnShow && buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAddRcord"
>
<span>{{cgBIBtnMap['add'].buttonName}}</span>
</a-button>
<a-button
v-if="btnShow && buttonSwitch.import && cgBIBtnMap['import'].enabled"
type="primary"
:preIcon="cgBIBtnMap['import'].buttonIcon"
@click="onImportExcel"
>
<span>{{cgBIBtnMap['import'].buttonName}}</span>
</a-button>
<a-button
v-if="btnShow && buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
:loading="exportLoading"
@click="onExportExcelOverride"
>
<span>{{cgBIBtnMap['export'].buttonName}}</span>
</a-button>
<!-- 自定义按钮 -->
<template v-if="btnShow && cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</BasicTable>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:source="ERPSUBTABLE"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="reload"
@formConfig="handleFormConfig"
/>
<!-- 详情弹框 -->
<online-detail-modal :source="ERPSUBTABLE" :id="ID" @register="registerDetailModal" />
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false" />
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
</template>
<script setup>
import { ref, computed, provide } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from '../default/OnlineAutoModal.vue';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import OnlineDetailModal from '../default/OnlineDetailModal.vue';
import { watch } from 'vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import { ERP, ERPSUBTABLE } from '../../util/constant';
import { cloneDeep } from 'lodash-es';
const props = defineProps(['data', 'mainTableSelectedRowRcord']);
const emit = defineEmits(['getSource']);
const btnShow = ref(false);
// update-begin--author:liaozhiyang---date:20240611---forTV360X-1004erp
const tableSetting = {
cacheKey: `online_erp_subTable_${props.data.currentTableName}`,
};
// update-end--author:liaozhiyang---date:20240611---forTV360X-1004erp
const { createMessage: $message } = useMessage();
// --
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
onlineExtConfigJson,
handleFormConfig,
registerCustomModal,
tableReloading,
} = useOnlineTableContext({ code: props.data.code, themeTemplate: ERP });
onlineTableContext['isErpSubTable'] = true;
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgLinkButtonList,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleEdit,
handleBatchDelete,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleSubmitFlow,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson);
const exportLoading = ref(false);
//
async function onExportExcelOverride() {
try {
exportLoading.value = true;
await onExportExcel();
} finally {
// 1.5sloading
setTimeout(() => (exportLoading.value = false), 1500);
}
}
// BasicTable
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
initAutoList(cloneDeep(props.data));
const foreignkeyRef = ref(null);
const foreignKeys = props.data.foreignKeys;
let $key;
if (foreignKeys?.length) {
// .
const item = foreignKeys[0];
const field = item.field;
$key = item.key;
onlineTableContext['foreignKeyField'] = field;
} else {
onlineTableContext['foreignKeyField'] = null;
onlineTableContext['foreignKeyValue'] = null;
}
provide('foreignkey', foreignkeyRef);
watch(
() => props.mainTableSelectedRowRcord,
(newVal) => {
//
pagination.value.current = 1;
selectedKeys.value = [];
// update-begin--author:liaozhiyang---date:20240523---forTV360X-124erp
onlineQueryFormOuter.value?.clearSearch();
// update-end--author:liaozhiyang---date:20240523---forTV360X-124erp
if (newVal) {
if (onlineTableContext['foreignKeyField']) {
const value = newVal[$key];
onlineTableContext['foreignKeyValue'] = value;
foreignkeyRef.value = { field: onlineTableContext['foreignKeyField'], value };
}
btnShow.value = true;
loading.value = true;
loadData().finally(() => {
loading.value = false;
});
} else {
btnShow.value = false;
dataSource.value = [];
}
},
{
immediate: true,
}
);
// update-begin--author:liaozhiyang---date:20231128---forQQYUN-7260erp
watch(
() => dataSource.value,
() => {
emit('getSource', props.data.currentTableName, dataSource.value);
},
{ immediate: true }
);
// update-end--author:liaozhiyang---date:20231128---forQQYUN-7260erp
/**重新加载online配置*/
async function initAutoList(data) {
// 1.
handleTableConfig(data);
loading.value = false;
// 2.
// await loadData();
// loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data) {
onlineTableContext['queryParam'] = data;
reload({mode:'search'});
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params) {
console.log('openOnlinePopModal', params);
popTableId.value = params.id;
let data = {
title: params.describe,
};
if (params.record && params.record.id) {
data['record'] = params.record;
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
const handleAddRcord = () => {
if(props.data.relationType ==1 && dataSource.value.length) {
$message.warning('一对一的表只能新增一条数据');
} else {
handleAdd();
}
}
</script>

View File

@ -0,0 +1,246 @@
<template>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="false"
:showActionColumn="false"
:showTableSetting="false"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
</BasicTable>
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal" :source="INNER_TABLE"/>
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false" :source="INNER_TABLE" />
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
</template>
<script setup name="OnlCgformInnerSubTable">
import { ref, watch } from 'vue';
import { BasicTable } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import OnlineDetailModal from '../default/OnlineDetailModal.vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import { INNER_TABLE } from '../../util/constant';
import { ACTION_COLUMN_FLAG } from '/@/components/Table/src/const';
/**
1.内嵌子表单没有分页
2.内嵌子表单没有查询区域
3.内嵌子表单没有导出导入等按钮
4.查询始终都是主表数据id外键不起作用
5.内嵌子表单没有新增和编辑没有操作列
*/
const props = defineProps(['subTableId', 'subTableName', 'mTableSelectedRcordId']);
const { createMessage: $message } = useMessage();
// --
const {
ID,
onlineTableContext,
loading,
reload,
dataSource,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
onlineExtConfigJson,
registerCustomModal,
tableReloading,
} = useOnlineTableContext({ code: props.subTableId });
onlineTableContext['isInnerSubTable'] = true;
onlineTableContext['innerSubTableName'] = props.subTableName;
onlineTableContext['innerSubTableId'] = ID.value;
onlineTableContext['mTableSelectedRcordId'] = props.mTableSelectedRcordId;
// ID
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
importUrl,
registerImportModal,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson);
// BasicTable
const {
columns,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
// ID
watch(
ID,
() => {
console.log('watched id is change...');
initAutoList();
},
{ immediate: true }
);
/**重新加载online配置*/
async function initAutoList() {
loading.value = true;
// 1.
let columnResult = await getColumnList(INNER_TABLE);
handleTableConfig(columnResult);
// update-begin--author:liaozhiyang---date:20240514---forQQYUN-9340
if (columnResult.foreignKeys?.length) {
onlineTableContext['innerSubTableFk'] = columnResult.foreignKeys[0].field;
}
// update-end--author:liaozhiyang---date:20240514---forQQYUN-9340
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params) {
console.log('openOnlinePopModal', params);
popTableId.value = params.id;
let data = {
title: params.describe,
};
if (params.record && params.record.id) {
data['record'] = params.record;
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
</script>
<style lang="less">
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,458 @@
<template>
<div class="p-2">
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
@loaded="onQueryFormLoaded"
/>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
:expandedRowKeys="expandedRowKeys"
@expand="handleExpand"
>
<template #tableTitle>
<a-button
v-if="buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAdd"
>
<span>{{cgBIBtnMap['add'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.import && cgBIBtnMap['import'].enabled"
type="primary"
:preIcon="cgBIBtnMap['import'].buttonIcon"
@click="onImportExcel"
>
<span>{{cgBIBtnMap['import'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
:loading="exportLoading"
@click="onExportExcelOverride"
>
<span>{{cgBIBtnMap['export'].buttonName}}</span>
</a-button>
<!-- 自定义按钮 -->
<template v-if="cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
<online-super-query
v-if="buttonSwitch.super_query && cgBIBtnMap['super_query'].enabled"
ref="superQueryButtonRef"
online
:status="superQueryStatus"
:queryBtnCfg="cgBIBtnMap['super_query']"
@search="handleSuperQuery"
/>
</template>
<!-- 子表 -->
<template #expandedRowRender="{ record }">
<a-tabs v-model:activeKey="innerSubTable.tabIndex" v-if="expandedRowKeys[0] && record.id == expandedRowKeys[0]">
<a-tab-pane v-if="expandedRowKeys.length" v-for="(item, index) in innerSubTable.tabNav" :tab="item.tableTxt" :key="index + ''">
<OnlCgformInnerSubTable
v-if="innerSubTable.tabIndex == index"
:subTableId="item.id"
:mTableSelectedRcordId="expandedRowKeys[0]"
:subTableName="item.tableName"
></OnlCgformInnerSubTable>
</a-tab-pane>
</a-tabs>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</BasicTable>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="hanldeSuccess"
@formConfig="getSubColunm"
/>
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal" />
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false" />
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
<!-- 页面loading[主要为了js增强使用] -->
<Loading :loading="pageLoading" :absolute="true" />
</div>
</template>
<script setup name="OnlCgformInnerTableList">
import { ref, reactive, watch, nextTick } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from '../default/OnlineAutoModal.vue';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import OnlineDetailModal from '../default/OnlineDetailModal.vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import OnlineSuperQuery from '../comp/superquery/SuperQuery.vue';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import OnlCgformInnerSubTable from './OnlCgformInnerSubTable.vue';
import { INNER_TABLE } from '../../util/constant';
import { Loading } from '/@/components/Loading';
const innerSubTable = reactive({
tabNav: [],
tabIndex: '0',
});
const expandedRowKeys = ref([]);
const mainTableSelectedRowRcord = ref(null);
const { createMessage: $message } = useMessage();
// --
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
onlineExtConfigJson,
handleFormConfig,
registerCustomModal,
tableReloading,
pageLoading,
} = useOnlineTableContext();
// ID
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
throw new Error('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleBatchDelete,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson);
const exportLoading = ref(false);
//
async function onExportExcelOverride() {
try {
exportLoading.value = true;
await onExportExcel();
} finally {
// 1.5sloading
setTimeout(() => (exportLoading.value = false), 1500);
}
}
// BasicTable
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
// ID
watch(
ID,
() => {
console.log('watched id is change...');
initAutoList();
},
{ immediate: true }
);
/**重新加载online配置*/
async function initAutoList() {
loading.value = true;
// 1.
let columnResult = await getColumnList(INNER_TABLE);
handleTableConfig(columnResult);
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data) {
onlineTableContext['queryParam'] = data;
reload({mode:'search'});
}
/**
* 查询组件加载完成事件获取高级查询需要的字段信息
*/
async function onQueryFormLoaded(json) {
console.log('onQueryFormLoaded', json);
await getRefPromise(superQueryButtonRef);
superQueryButtonRef.value.init(json);
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params) {
console.log('openOnlinePopModal', params);
popTableId.value = params.id;
let data = {
title: params.describe,
};
if (params.record && params.record.id) {
data['record'] = params.record;
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
const getSubColunm = (data) => {
handleFormConfig(data);
const { schema } = data;
const { properties } = schema;
const subMenu = [];
Object.entries(properties).forEach(([key, value]) => {
if (value.view == 'tab') {
subMenu.push({ tableName: key, tableTxt: value.describe, id: value.id, order: value.order });
}
});
subMenu.sort((a, b) => a.order - b.order);
innerSubTable.tabNav = subMenu;
};
const handleExpand = (expanded, record) => {
expandedRowKeys.value = [];
if (expanded) {
expandedRowKeys.value = [record.id];
mainTableSelectedRowRcord.value = record;
}
};
const hanldeSuccess = (e) => {
reload(e);
reloadCurSubTable();
}
/**
* liaozhiyang
* 2023-12-26
* 刷新子表
*/
const reloadCurSubTable = () => {
if (expandedRowKeys.value.length) {
const tabIndex = innerSubTable.tabIndex;
innerSubTable.tabIndex = '-1';
nextTick(() => {
innerSubTable.tabIndex = tabIndex;
});
}
};
</script>
<style lang="less">
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,395 @@
<template>
<div class="p-2">
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
@loaded="onQueryFormLoaded"
/>
<!-- 列表 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTable"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
>
<template #tableTitle>
<a-button
v-if="buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAdd"
>
<span>{{cgBIBtnMap['add'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.import && cgBIBtnMap['import'].enabled"
type="primary"
:preIcon="cgBIBtnMap['import'].buttonIcon"
@click="onImportExcel"
>
<span>{{cgBIBtnMap['import'].buttonName}}</span>
</a-button>
<a-button
v-if="buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
:loading="exportLoading"
@click="onExportExcelOverride"
>
<span>{{cgBIBtnMap['export'].buttonName}}</span>
</a-button>
<!-- 自定义按钮 -->
<template v-if="cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
<online-super-query
v-if="buttonSwitch.super_query && cgBIBtnMap['super_query'].enabled"
ref="superQueryButtonRef"
online
:status="superQueryStatus"
:queryBtnCfg="cgBIBtnMap['super_query']"
@search="handleSuperQuery"
/>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownActions(record)" />
</template>
</BasicTable>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="reload"
@formConfig="handleFormConfig"
/>
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal" />
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 弹窗给href到另外一张表单用-详情表单 -->
<online-detail-modal :id="hrefMainTableId" @register="registerOnlineHrefModal" :defaultFullscreen="false" />
<!-- 弹窗到另外一张表单用-可编辑表单-关联记录的字段可在列表上打开modal编辑数据 -->
<online-pop-modal ref="onlinePopModalRef" :id="popTableId" @register="registerPopModal" @success="reload" request topTip></online-pop-modal>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
<!-- 页面loading[主要为了js增强使用] -->
<Loading :loading="pageLoading" :absolute="true" />
</div>
</template>
<script lang="ts" setup name="OnlCgformTabList">
import { ref, watch } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import OnlineAutoModal from './modal/OnlineTabAutoModal.vue';
import OnlineDetailModal from './modal/OnlineTabDetailModal.vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import OnlineSuperQuery from '../comp/superquery/SuperQuery.vue';
import { useOnlineListPopEvent } from '../../hooks/auto/useOnlinePopEvent';
import OnlinePopModal from '../comp/OnlinePopModal.vue';
import { TAB } from '../../util/constant';
import { Loading } from '/@/components/Loading';
const { createMessage: $message } = useMessage();
// --
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
onlineExtConfigJson,
handleFormConfig,
registerCustomModal,
tableReloading,
pageLoading,
} = useOnlineTableContext();
// ID
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
// update-begin--author:liaozhiyang---date:20230825---forQQYUN-6326
throw new Error('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
}
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleBatchDelete,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson);
const exportLoading = ref(false);
//
async function onExportExcelOverride() {
try {
exportLoading.value = true;
await onExportExcel();
} finally {
// 1.5sloading
setTimeout(() => (exportLoading.value = false), 1500);
}
}
// BasicTable
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
hrefMainTableId,
registerOnlineHrefModal,
registerPopModal,
openPopModal,
onlinePopModalRef,
popTableId,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
// ID
watch(
ID,
() => {
console.log('watched id is change...');
initAutoList();
},
{ immediate: true }
);
/**重新加载online配置*/
async function initAutoList() {
loading.value = true;
// 1.
let columnResult = await getColumnList(TAB);
handleTableConfig(columnResult);
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data) {
onlineTableContext['queryParam'] = data;
reload({mode:'search'});
}
/**
* 查询组件加载完成事件获取高级查询需要的字段信息
*/
async function onQueryFormLoaded(json) {
console.log('onQueryFormLoaded', json);
await getRefPromise(superQueryButtonRef);
superQueryButtonRef.value.init(json);
}
/**
* list页面打开 其他表单弹窗
* @param params
*/
function openOnlinePopModal(params) {
console.log('openOnlinePopModal', params);
popTableId.value = params.id;
let data = {
title: params.describe,
};
if (params.record && params.record.id) {
data['record'] = params.record;
data['isUpdate'] = true;
}
openPopModal(true, data);
}
//
useOnlineListPopEvent(openOnlinePopModal);
</script>
<style lang="less">
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<BasicModal
:title="title"
@cancel="
() => {
onCloseEvent();
restTabIndex();
}
"
:enableComment="enableComment"
:width="modalWidth"
v-bind="$attrs"
:maxHeight="600"
@register="registerModal"
wrapClassName="jeecg-online-modal"
@ok="handleSubmit"
@commentOpen="handleCommentOpen"
>
<!--update-begin--author:liaozhiyang---date:20230821---forQQYUN-6305tab主题一对多-->
<template v-if="themeTemplate === TAB" #title>
<div class="titleArea">
<div class="title">{{ title }}</div>
<div class="right">
<a-dropdown-button v-if="showDropdownBtn" trigger="click">
{{ tabNav[+tabIndex + 1].tableTxt }}
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-for="(item, index) in tabNav" :key="tabValue(index)">
{{ item.tableTxt }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown-button>
<a-radio-group v-else v-model:value="tabIndex">
<a-radio-button v-for="(item, index) in tabNav" :value="tabValue(index)" :key="item.tableName">{{ item.tableTxt }}</a-radio-button>
</a-radio-group>
</div>
</div>
</template>
<!--update-end--author:liaozhiyang---date:20230821---forQQYUN-6305tab主题一对多-->
<template #footer>
<a-row>
<a-col :span='24 - commentSpan'>
<a-button
v-for="btn in cgButtonList"
:key="btn.id"
type="primary"
@click="handleCgButtonClick(btn.optType, btn.buttonCode)"
:preIcon="btn.buttonIcon ? 'ant-design:' + btn.buttonIcon : ''"
>
{{ btn.buttonName }}
</a-button>
<a-button
v-if="!disableSubmit && confirmBtnCfg.enabled"
key="submit"
type="primary"
:preIcon="confirmBtnCfg.buttonIcon"
:loading="submitLoading"
@click="handleSubmit"
>
<span>{{ confirmBtnCfg.buttonName }}</span>
</a-button>
<a-button
key="back"
@click="
() => {
handleCancel();
restTabIndex();
}
"
>关闭</a-button
>
</a-col>
</a-row>
</template>
<online-form
ref="onlineFormCompRef"
:id="id"
:disabled="disableSubmit"
:form-template="formTemplate"
:isTree="isTreeForm"
:pidField="pidFieldName"
:themeTemplate="themeTemplate"
:tabIndex="tabIndex"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
@rendered="renderSuccess"
@success="handleSuccess"
@toggleTab="handleToggleTab"
>
</online-form>
<template #comment>
<comment-panel ref="commentPanelRef" :tableName="tableName" :dataId="formDataId" />
</template>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, ref, computed } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlineForm from './OnlineTabForm.vue';
import { useAutoModal } from '../../../hooks/auto/useAutoModal';
import CommentPanel from '/@/components/jeecg/comment/CommentPanel.vue';
import { TAB } from '../../../util/constant';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({
name: 'OnlineTabAutoModal',
props: {
id: {
type: String,
default: '',
},
cgBIBtnMap: Object,
buttonSwitch: Object,
confirmBtnCfg: {
type: Object,
default: () => {
return {
enabled: true,
buttonName: '确定',
buttonIcon: '',
}
}
},
},
components: {
BasicModal,
OnlineForm,
CommentPanel,
},
emits: ['success', 'register', 'formConfig'],
setup(props, { emit }) {
console.log('进入表单弹框》》》》modal');
const commentPanelRef = ref();
const tabNav = ref<any>([]);
const tabIndex = ref<any>('-1');
const commentSpan = ref(0);
const { getIsMobile } = useAppInject();
function reloadComment() {
if (commentPanelRef.value) commentPanelRef.value.reload();
}
const {
title,
modalWidth,
registerModal,
closeModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
tableName,
formDataId,
enableComment,
onCloseEvent,
themeTemplate,
} = useAutoModal(false, { emit }, reloadComment);
function handleSuccess(formData) {
emit('success', formData);
closeModal();
//
onCloseEvent();
restTabIndex();
}
// id
watch(() => props.id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!props.id) {
return;
}
console.log('重新渲染表单》》》》modal');
await handleFormConfig(props.id, {}, (result) => {
const nav: any = [];
const sub: any = [];
const { head, schema } = result;
const { properties } = schema;
nav.push({ tableName: head.tableName, tableTxt: head.tableTxt });
Object.entries(properties).forEach(([key, value]: any) => {
if (value.view == 'tab') {
sub.push({ tableName: key, tableTxt: value.describe, order: value.order });
}
});
sub.sort((a, b) => a.order - b.order);
tabNav.value = [...nav, ...sub];
});
}
const tabValue = (index) => {
return String(index - 1);
};
const restTabIndex = () => {
// tab
setTimeout(() => {
tabIndex.value = '-1';
}, 500);
};
// 2
const showDropdownBtn = computed(() => {
return getIsMobile.value && tabNav.value.length > 2;
});
const handleMenuClick = ({ key }) => {
tabIndex.value = key;
};
const handleToggleTab = (key) => {
tabIndex.value = key;
};
// update-begin--author:liaozhiyang---date:20240528---forTV360X-485
const handleCommentOpen = (visible, span) => {
console.log('评论是否展开:', visible);
commentSpan.value = span;
}
// update-end--author:liaozhiyang---date:20240528---forTV360X-485
const that = {
title,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleSuccess,
handleCancel,
modalWidth,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
tableName,
formDataId,
enableComment,
commentPanelRef,
onCloseEvent,
themeTemplate,
tabNav,
tabValue,
tabIndex,
TAB,
restTabIndex,
handleMenuClick,
showDropdownBtn,
handleToggleTab,
handleCommentOpen,
commentSpan,
};
return that;
},
});
</script>
<style lang="less" scoped>
.titleArea {
display: flex;
align-content: center;
padding-right: 70px;
.title {
margin-right: 16px;
line-height: 32px;
}
.right {
overflow-x: auto;
overflow-y: hidden;
flex: 1;
white-space: nowrap;
.ant-radio-group {
font-weight: normal;
}
}
}
.footerWrap {
&.center {
display: flex;
align-items: center;
justify-content: center;
}
}
html[data-theme='light'] {
.right {
.ant-radio-group {
:deep(.ant-radio-button-wrapper:not(.ant-radio-button-wrapper-checked)) {
color: #555;
}
}
}
}
</style>

View File

@ -0,0 +1,196 @@
<template>
<BasicModal
:title="title"
@cancel="
() => {
restTabIndex();
}
"
:width="modalWidth"
:maxHeight="600"
:enableComment="enableComment"
:defaultFullscreen="false"
v-bind="$attrs"
@register="registerModal"
wrapClassName="jeecg-online-detail-modal"
>
<template #title>
<div class="titleArea">
<div class="title">{{ title }}</div>
<div class="right">
<a-radio-group v-model:value="tabIndex">
<a-radio-button v-for="(item, index) in tabNav" :value="tabValue(index)" :key="item.tableName">{{ item.tableTxt }}</a-radio-button>
</a-radio-group>
</div>
</div>
</template>
<template #footer>
<a-button
key="back"
@click="
() => {
handleCancel();
restTabIndex();
}
"
>关闭</a-button
>
</template>
<online-form-detail
ref="onlineFormCompRef"
:id="id"
:form-template="formTemplate"
:show-sub="showSub"
:themeTemplate="themeTemplate"
:tabIndex="tabIndex"
@rendered="renderSuccess"
/>
<template #comment>
<comment-panel ref="commentPanelRef" :tableName="tableName" :dataId="formDataId"></comment-panel>
</template>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue';
import { BasicModal } from '/@/components/Modal';
import OnlineFormDetail from './OnlineTabFormDetail.vue';
import { useAutoModal } from '../../../hooks/auto/useAutoModal';
import CommentPanel from '/@/components/jeecg/comment/CommentPanel.vue';
import { TAB } from '../../../util/constant';
export default defineComponent({
name: 'OnlineTabDetailModal',
props: {
id: {
type: String,
required: false,
default: '',
},
},
components: {
BasicModal,
OnlineFormDetail,
CommentPanel,
},
emits: ['success', 'register', 'formConfig'],
setup(props, { emit }) {
console.log('进入表单弹框》》》》modal');
const commentPanelRef = ref();
const tabNav = ref<any>([]);
const tabIndex = ref<string>('-1');
function reloadComment() {
if (commentPanelRef.value) commentPanelRef.value.reload();
}
const {
title,
modalWidth,
registerModal,
cgButtonList,
handleCgButtonClick,
disableSubmit,
handleSubmit,
submitLoading,
handleCancel,
handleFormConfig,
onlineFormCompRef,
formTemplate,
isTreeForm,
pidFieldName,
renderSuccess,
formRendered,
showSub,
tableName,
formDataId,
enableComment,
themeTemplate,
} = useAutoModal(false, { emit }, reloadComment);
// id
watch(() => props.id, renderFormItems, { immediate: true });
async function renderFormItems() {
formRendered.value = false;
if (!props.id) {
return;
}
console.log('重新渲染表单》》》》modal');
await handleFormConfig(props.id, {}, (result) => {
const nav: any = [];
const sub: any = [];
const { head, schema } = result;
const { properties } = schema;
nav.push({ tableName: head.tableName, tableTxt: head.tableTxt });
Object.entries(properties).forEach(([key, value]: any) => {
if (value.view == 'tab') {
//update-begin---author:chenrui ---date:2025/8/27 for[issues/8760]onlineTABTAB #8760------------
sub.push({ tableName: key, tableTxt: value.describe,order: value.order });
//update-end---author:chenrui ---date:2025/8/27 for[issues/8760]onlineTABTAB #8760------------
}
});
sub.sort((a, b) => a.order - b.order);
tabNav.value = [...nav, ...sub];
});
}
const tabValue = (index) => {
return String(index - 1);
};
const restTabIndex = () => {
// tab
setTimeout(() => {
tabIndex.value = '-1';
}, 500);
};
const that = {
title,
onlineFormCompRef,
renderSuccess,
registerModal,
handleSubmit,
handleCancel,
modalWidth,
formTemplate,
disableSubmit,
cgButtonList,
handleCgButtonClick,
isTreeForm,
pidFieldName,
submitLoading,
showSub,
tableName,
formDataId,
enableComment,
commentPanelRef,
themeTemplate,
tabNav,
tabValue,
tabIndex,
TAB,
restTabIndex,
};
return that;
},
});
</script>
<style lang="less" scoped>
.titleArea {
display: flex;
align-content: center;
padding-right: 70px;
.title {
margin-right: 16px;
line-height: 32px;
}
.right {
overflow-x: auto;
overflow-y: hidden;
flex: 1;
white-space: nowrap;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,343 @@
<template>
<div :id="tableName + '_form'">
<!-- 积木报表的打印按钮只有配置了 reportUrl 才显示 -->
<div
v-if="!!formData.id && !!onlineExtConfigJson.reportPrintShow"
style="text-align: right; position: absolute; top: 15px; right: 20px; z-index: 999"
>
<PrinterOutlined title="打印" @click="onOpenReportPrint" style="font-size: 16px" />
</div>
<a-tabs
class="tabTheme"
@change="onTabChange"
v-model:activeKey="subActiveKey"
>
<a-tab-pane tab="主表" :key="'-1'">
<detail-form :schemas="detailFormSchemas" :data="formData" :span="formSpan" />
</a-tab-pane>
<template v-if="hasSubTable && showSub">
<a-tab-pane v-for="(sub, index) in subTabInfo" :tab="sub.describe" :key="index + ''" :forceRender="true">
<div :style="{ 'overflow-y': 'auto', 'overflow-x': 'hidden', 'max-height': subFormHeight + 'px' }" v-if="sub.relationType == 1">
<!-- 子表-一对一 -->
<online-sub-form-detail
:key="subReloadKey"
:table="sub.key"
:form-template="formTemplate"
:main-id="getSubTableForeignKeyValue(sub.foreignKey)"
:properties="sub.properties"
>
</online-sub-form-detail>
</div>
<div v-else>
<!-- 子表-一对多 -->
<JVxeTable
v-if="showStatus[sub.key]"
:ref="refMap[sub.key]"
toolbar
keep-source
:row-number="rowNumber"
row-selection
:height="subTableHeight"
:disabled="true"
:columns="sub.columns"
:dataSource="subDataSource[sub.key]"
:authPre="getSubTableAuthPre(sub.key)"
/>
<a-spin v-else :spinning="true" />
</div>
</a-tab-pane>
</template>
</a-tabs>
<Loading :loading="loading" :absolute="false" />
<slot name="bottom"></slot>
</div>
</template>
<script lang="ts">
import { useMessage } from '/@/hooks/web/useMessage';
import { ref, reactive, watch } from 'vue';
import { Loading } from '/@/components/Loading';
import { getToken } from '/@/utils/auth';
import { goJmReportViewPage } from '/@/utils';
import { PrinterOutlined } from '@ant-design/icons-vue';
import DetailForm from '../../../extend/form/DetailForm.vue';
import OnlineSubFormDetail from '../../comp/OnlineSubFormDetail.vue';
import { getDetailFormSchemas } from '../../../hooks/auto/useAutoForm';
import { defHttp } from '/@/utils/http/axios';
import { ERP, TAB } from '../../../util/constant';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default {
name: 'OnlineTabFormDetail',
components: {
DetailForm,
Loading,
PrinterOutlined,
OnlineSubFormDetail,
},
props: {
id: {
type: String,
default: '',
},
formTemplate: {
type: Number,
default: 1,
},
disabled: {
type: Boolean,
default: false,
},
isTree: {
type: Boolean,
default: false,
},
pidField: {
type: String,
default: '',
},
submitTip: {
type: Boolean,
default: true,
},
showSub: {
type: Boolean,
default: true,
},
themeTemplate: {
type: String,
default: '',
},
tabIndex: {
type: String,
default: '',
},
},
emits: ['success', 'rendered'],
setup(props, { emit }) {
console.log('onlineForm-setup》》');
const { createMessage: $message } = useMessage();
const { getIsMobile } = useAppInject();
const tableName = ref('');
const single = ref(true);
//
const loading = ref(false);
const tableType = ref(1);
const formData = ref<any>({});
// update-begin-author:liaozhiyang---date:20240313---forQQYUN-9034online
const subFormHeight = ref('auto');
// update-end-author:liaozhiyang---date:20240313---forQQYUN-9034online
const subReloadKey = ref(0);
//
// VUEN-803340
const subTableHeight = ref(340);
const subActiveKey = ref('0');
const rowNumber = ref(getIsMobile.value ? false : true);
watch(
() => props.tabIndex,
(value, oldValue) => {
subActiveKey.value = value;
if (oldValue) {
//()
onTabChange();
}
},
{
immediate: true,
}
);
/**
* online表单扩展配置
*/
const onlineExtConfigJson = reactive({
reportPrintShow: 0,
reportPrintUrl: '',
joinQuery: 0,
modelFullscreen: 0,
modalMinWidth: '',
});
const { detailFormSchemas, hasSubTable, subTabInfo, refMap, showStatus, subDataSource, createFormSchemas, formSpan } =
getDetailFormSchemas(props);
/**
* 处理扩展配置
*/
function handleExtConfigJson(jsonStr) {
let extConfigJson = { reportPrintShow: 0, reportPrintUrl: '', joinQuery: 0, modelFullscreen: 1, modalMinWidth: '' };
if (jsonStr) {
extConfigJson = JSON.parse(jsonStr);
}
Object.keys(extConfigJson).map((k) => {
onlineExtConfigJson[k] = extConfigJson[k];
});
}
//
async function createRootProperties(data) {
tableType.value = data.head.tableType;
tableName.value = data.head.tableName;
single.value = data.head.tableType == 1;
handleExtConfigJson(data.head.extConfigJson);
createFormSchemas(data.schema.properties);
emit('rendered', onlineExtConfigJson);
}
/**
* status: 是否是修改页面
* record: 列表页面的行数据
* param 树形列表添加子节点 传入的父级节点id
* */
async function show(_status, record) {
console.log('进入表单详情》》form', record);
// -update-begin--author:liaozhiyang---date:20251209---forQQYUN-13970
subReloadKey.value++;
// -update-end--author:liaozhiyang---date:20251209---forQQYUN-13970
await edit(record);
changeShowStatus(true);
}
function getFormData(dataId) {
let url = `/online/cgform/api/detail/${props.id}/${dataId}`;
return new Promise((resolve, reject) => {
defHttp
.get({ url }, { isTransformResponse: false })
.then((res) => {
if (res.success) {
resolve(res.result);
} else {
reject();
$message.warning(res.message);
}
})
.catch(() => {
reject();
});
});
}
//update-begin-author:taoyan date:2023-2-13 for: QQYUN-4226vue3online
function changeShowStatus(flag) {
Object.keys(showStatus).map((k) => {
showStatus[k] = flag;
});
}
function onTabChange() {
changeShowStatus(false);
setTimeout(() => {
changeShowStatus(true);
}, 300);
}
//update-end-author:taoyan date:2023-2-13 for: QQYUN-4226vue3online
async function edit(record) {
let temp: any = await getFormData(record.id);
//
formData.value = { ...temp };
editSubVxeTableData(temp);
}
function editSubVxeTableData(record) {
if (!record) {
//
record = {};
}
let keys = Object.keys(subDataSource.value);
if (keys && keys.length > 0) {
let obj = {};
for (let key of keys) {
obj[key] = record[key] || [];
}
subDataSource.value = obj;
}
}
function getSubTableAuthPre(table) {
return 'online_' + table + ':';
}
//
function onOpenReportPrint() {
let url = onlineExtConfigJson.reportPrintUrl;
let temp: any = formData.value;
if (temp) {
let id = temp.id;
let token = getToken();
goJmReportViewPage(url, id, token);
}
}
function getSubTableForeignKeyValue(key) {
let temp = formData.value;
console.log('getValueIgnoreCase(temp, key)', temp, key, getValueIgnoreCase(temp, key));
return getValueIgnoreCase(temp, key);
}
/**
* VUEN-1056 30生成的一对多编辑的时候子表数据挂不上
*/
function getValueIgnoreCase(data, key) {
if (data) {
let temp = data[key];
if (!temp && temp !== 0) {
temp = data[key.toLowerCase()];
if (!temp && temp !== 0) {
temp = data[key.toUpperCase()];
}
}
return temp;
}
return '';
}
return {
detailFormSchemas,
formData,
formSpan,
//
tableName,
loading,
//
hasSubTable,
subTabInfo,
subFormHeight,
subTableHeight,
refMap,
onTabChange,
subReloadKey,
//
subDataSource,
getSubTableAuthPre,
//
show,
createRootProperties,
//
onOpenReportPrint,
onlineExtConfigJson,
getSubTableForeignKeyValue,
showStatus,
ERP,
TAB,
subActiveKey,
rowNumber,
};
},
};
</script>
<style lang="less" scoped>
.tabTheme {
:deep(.ant-tabs-nav) {
display: none;
}
}
</style>

View File

@ -0,0 +1,588 @@
<template>
<div class="p-2">
<!-- 加载中骨架屏 -->
<a-skeleton v-if="tableReloading" active />
<!-- 查询条件 -->
<online-query-form
v-show="!tableReloading"
ref="onlineQueryFormOuter"
:id="ID"
:queryBtnCfg="getQueryButtonCfg"
:resetBtnCfg="getResetButtonCfg"
@search="queryWithCondition"
@loaded="onQueryFormLoaded"
/>
<!-- 列表-树形列表前五个配置不一样 -->
<BasicTable
v-if="!tableReloading"
ref="onlineTreeTableRef"
:isTreeTable="true"
:expandedRowKeys="expandedRowKeys"
@expandedRowsChange="handleExpandedRowsChange"
@expand="handleExpand"
rowKey="jeecg_row_key"
:canResize="true"
:bordered="true"
:showIndexColumn="false"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:rowSelection="rowSelection"
:actionColumn="actionColumn"
:showTableSetting="true"
:clickToRowSelect="false"
:scroll="tableScroll"
@table-redo="reload"
:class="{ 'j-table-force-nowrap': enableScrollBar }"
@change="handleChangeInTable"
>
<template #tableTitle>
<a-button
v-if="buttonSwitch.add && cgBIBtnMap['add'].enabled"
type="primary"
:preIcon="cgBIBtnMap['add'].buttonIcon"
@click="handleAdd"
>
<span>{{cgBIBtnMap['add'].buttonName}}</span>
</a-button>
<!-- <a-button-->
<!-- v-if="buttonSwitch.import && cgBIBtnMap['import'].enabled"-->
<!-- type="primary"-->
<!-- :preIcon="cgBIBtnMap['import'].buttonIcon"-->
<!-- @click="onImportExcel"-->
<!-- >-->
<!-- <span>{{cgBIBtnMap['import'].buttonName}}</span>-->
<!-- </a-button>-->
<a-button
v-if="buttonSwitch.export && cgBIBtnMap['export'].enabled"
type="primary"
:preIcon="cgBIBtnMap['export'].buttonIcon"
@click="onExportExcel"
>
<span>{{cgBIBtnMap['export'].buttonName}}</span>
</a-button>
<!-- 自定义按钮 -->
<template v-if="cgTopButtonList && cgTopButtonList.length > 0" v-for="(item, index) in cgTopButtonList">
<a-button
v-if="item.optType == 'js'"
:key="'cgbtn' + index"
@click="cgButtonJsHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
<a-button
v-else-if="item.optType == 'action'"
:key="'cgbtn' + index"
@click="cgButtonActionHandler(item.buttonCode)"
type="primary"
:preIcon="item.buttonIcon ? 'ant-design:' + item.buttonIcon : ''"
>
{{ item.buttonName }}
</a-button>
</template>
<a-button
v-show="selectedKeys.length > 0"
v-if="buttonSwitch.batch_delete && cgBIBtnMap['batch_delete'].enabled"
:preIcon="cgBIBtnMap['batch_delete'].buttonIcon"
@click="handleBatchDelete"
>
<span>{{cgBIBtnMap['batch_delete'].buttonName}}</span>
</a-button>
<online-super-query
v-if="buttonSwitch.super_query && cgBIBtnMap['super_query'].enabled"
ref="superQueryButtonRef"
online
:status="superQueryStatus"
:queryBtnCfg="cgBIBtnMap['super_query']"
@search="handleSuperQuery"
/>
</template>
<template #fileSlot="{ text, record, column }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text, record, column, ID)">
下载
</a-button>
</template>
<template #imgSlot="{ text }">
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
</template>
<template #htmlSlot="{ text, column, record }">
<!-- update-begin--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<a v-html="text" @click="handleClickFieldHref(column.fieldHref, record)"></a>
</template>
<div v-else v-html="text"></div>
<!-- update-end--author:liaozhiyang---date:20240517---forTV360X-129增加富文本控件配置href跳转 -->
</template>
<template #pcaSlot="{ text, column }">
<div :title="getPcaText(text, column)">{{ getPcaText(text, column) }}</div>
</template>
<template #dateSlot="{ text, column }">
<span>{{ getFormatDate(text, column) }}</span>
</template>
<template #action="{ record }">
<TableAction :actions="getActions(record)" :dropDownActions="getTreeDropDownActions(record)" />
</template>
</BasicTable>
<!-- 表单新增修改弹框 -->
<OnlineAutoModal
@register="registerModal"
:id="ID"
:cgBIBtnMap="cgBIBtnMap"
:buttonSwitch="buttonSwitch"
:confirmBtnCfg="getFormConfirmButtonCfg"
@success="handlerFormSuccess"
@formConfig="handleFormConfig"
/>
<!-- 导入 -->
<JImportModal @register="registerImportModal" :url="importUrl()" @ok="reload" online></JImportModal>
<!-- 跳转Href的动态组件方式 -->
<a-modal v-bind="hrefComponent.model" v-on="hrefComponent.on">
<component :is="hrefComponent.is" v-bind="hrefComponent.params" />
</a-modal>
<!-- 自定义弹窗 -->
<online-custom-modal @register="registerCustomModal" @success="reload" />
<!-- 详情弹框 -->
<online-detail-modal :id="ID" @register="registerDetailModal"/>
<!-- 流程图查看modal -->
<BpmGraphicModal @register="registerBpmModal"></BpmGraphicModal>
<!-- 页面loading[主要为了js增强使用] -->
<Loading :loading="pageLoading" :absolute="true" />
</div>
</template>
<script lang="ts">
import { BasicTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import OnlineAutoModal from '../default/OnlineAutoModal.vue';
import OnlineDetailModal from '../default/OnlineDetailModal.vue';
import OnlineCustomModal from '../default/OnlineCustomModal.vue';
import { ref, watch } from 'vue';
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
import { useOnlineTableContext } from '../../hooks/auto/useOnlineTableContext';
import { useListButton } from '../../hooks/auto/useListButton';
import { useTableColumns } from '../../hooks/auto/useTableColumns';
import { useEnhance } from '../../hooks/auto/useEnhance';
import { defHttp } from '/@/utils/http/axios';
import { getRefPromise } from '../../hooks/auto/useAutoForm';
import OnlineQueryForm from '../comp/OnlineQueryForm.vue';
import OnlineSuperQuery from '../comp/superquery/SuperQuery.vue';
import { Tree } from "../../util/constant";
import { Loading } from '/@/components/Loading';
export default {
name: 'DefaultOnlineList',
components: {
BasicTable,
TableAction,
OnlineAutoModal,
JImportModal,
OnlineQueryForm,
OnlineSuperQuery,
OnlineCustomModal,
OnlineDetailModal,
Loading,
},
setup() {
const { createMessage: $message } = useMessage();
const onlineTreeTableRef = ref();
// 3
const {
ID,
onlineTableContext,
onlineQueryFormOuter,
loading,
reload,
dataSource,
pagination,
handleSpecialConfig,
getColumnList,
handleChangeInTable,
loadData,
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
registerCustomModal,
getTreeDataByResult,
expandedRowKeys,
handleExpandedRowsChange,
tableReloading,
onlineExtConfigJson,
handleFormConfig,
pageLoading,
} = useOnlineTableContext();
if (!ID.value) {
$message.warning('地址错误, 配置ID不存在!');
// update-begin--author:liaozhiyang---date:20230825---forQQYUN-6326
throw new Error('地址错误, 配置ID不存在!');
// update-end--author:liaozhiyang---date:20230825---forQQYUN-6326
}
//
onlineTableContext.isTree(true);
//
let { initCgEnhanceJs } = useEnhance(onlineTableContext);
// button
const {
buttonSwitch,
cgLinkButtonList,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
importUrl,
registerModal,
handleAdd,
handleEdit,
handleBatchDelete,
registerImportModal,
onImportExcel,
onExportExcel,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleSubmitFlow,
getDropDownActions,
getActions,
initButtonList,
initButtonSwitch,
registerDetailModal,
registerBpmModal,
} = useListButton(onlineTableContext, onlineExtConfigJson);
// table
const {
columns,
actionColumn,
selectedKeys,
rowSelection,
enableScrollBar,
tableScroll,
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
handleColumnResult,
hrefComponent,
viewOnlineCellImage,
handleClickFieldHref,
} = useTableColumns(onlineTableContext, onlineExtConfigJson);
watch(
ID,
() => {
initAutoList();
},
{ immediate: true }
);
async function initAutoList() {
loading.value = true;
// 1.
let columnResult = await getColumnList(Tree);
handleTableConfig(columnResult);
// 2.
await loadData();
loading.value = false;
// 3.js setup
onlineTableContext.execButtonEnhance('setup');
}
// table
function handleTableConfig(result) {
// js
let EnhanceJS = initCgEnhanceJs(result.enhanceJs);
onlineTableContext['EnhanceJS'] = EnhanceJS;
//
initButtonList(result.cgButtonList);
//
initButtonSwitch(result.hideColumns);
//
handleColumnResult(result);
//
handleSpecialConfig(result);
//-
onlineTableContext['hasChildrenField'] = result.hasChildrenField;
onlineTableContext['pidField'] = result.pidField;
}
/**
* 查询控件 事件-执行查询
* @param data
*/
function queryWithCondition(data, status) {
onlineTableContext['queryParam'] = data;
if (status === true) {
//
reload({mode:'search'});
} else {
//
searchReset();
}
}
/**
* 查询组件加载完成事件获取高级查询需要的字段信息
*/
async function onQueryFormLoaded(json) {
await getRefPromise(superQueryButtonRef)
superQueryButtonRef.value.init(json);
}
/*-------------------------------以下方法是树列表专有的,或复写或新增-【树】----------------------------*/
//
function handleExpand(expanded, record) {
//
let expandedRowKeysValue = expandedRowKeys.value;
if (expanded) {
addExpandedRowKey(record.id);
if (record.children.length > 0 && record.children[0].isLoading === true) {
let hasChildrenField = onlineTableContext.hasChildrenField;
const { sortField, sortType } = onlineTableContext;
let params = Object.assign({}, { column: sortField, order: sortType });
params[onlineTableContext['pidField']] = record.id;
params[hasChildrenField] = record[hasChildrenField];
let url = `${onlineTableContext.onlineUrl.getTreeData}${onlineTableContext.ID}`;
defHttp
.get({ url, params }, { isTransformResponse: false })
.then((res) => {
console.log('handleExpand', res.result);
if (res.success) {
if (Number(res.result.total) > 0) {
record.children = getTreeDataByResult(res.result.records);
// dataSource.value =
} else {
record.children = '';
record.hasChildrenField = '0';
}
} else {
$message.warning(res.message);
}
})
.catch(() => {
$message.warning('加载子节点失败!');
});
}
} else {
let keyIndex = expandedRowKeysValue.indexOf(record.id);
if (keyIndex >= 0) {
expandedRowKeys.value = expandedRowKeysValue.splice(keyIndex, 1);
}
}
}
/**
* 添加展开项的id
* */
function addExpandedRowKey(key) {
let arr = expandedRowKeys.value;
if (arr && arr.indexOf(key) < 0) {
arr.push(key);
}
expandedRowKeys.value = arr;
}
//
async function searchReset() {
if (onlineTableContext.isTree() === true) {
expandedRowKeys.value = [];
onlineTreeTableRef.value.collapseAll();
}
reload();
}
/**
* 树表单新增修改完成后 列表页面的回调事件
*/
function handlerFormSuccess(formData) {
console.log('expandedRowKeys.value', expandedRowKeys.value);
if (loadParent.value === true) {
let pid = formData[onlineTableContext.pidField];
if (pid) {
let arr = expandedRowKeys.value;
if (arr.indexOf(pid) < 0) {
arr.push(pid);
}
expandedRowKeys.value = arr;
}
}
reload();
}
/**
*
* [更多]下拉项中的 [添加子节点]
*/
const addChildButton = (record) => {
return {
label: '添加下级',
onClick: handleAddChild.bind(null, record),
};
};
const loadParent = ref(false);
function handleAddChild(record) {
loadParent.value = true;
let param = {
[onlineTableContext.pidField]: record['id'],
};
handleAdd(param);
}
/**
* 操作列[更多下拉项]
*/
function getTreeDropDownActions(record) {
let arr = getDropDownActions(record, {'themeTemplate': Tree});
arr.unshift(addChildButton(record));
return arr;
}
const that = {
ID,
//
onlineQueryFormOuter,
queryWithCondition,
onQueryFormLoaded,
reload,
//
superQueryButtonRef,
superQueryStatus,
handleSuperQuery,
// table
loading,
columns,
actionColumn,
dataSource,
pagination,
rowSelection,
selectedKeys,
tableScroll,
enableScrollBar,
handleChangeInTable,
//
buttonSwitch,
handleAdd,
handleEdit,
onImportExcel,
onExportExcel,
cgBIBtnMap,
getQueryButtonCfg,
getResetButtonCfg,
getFormConfirmButtonCfg,
cgTopButtonList,
cgLinkButtonList,
cgButtonJsHandler,
cgButtonActionHandler,
cgButtonLinkHandler,
handleBatchDelete,
// table-slot
downloadRowFile,
getImgView,
getPcaText,
getFormatDate,
//
getActions,
getTreeDropDownActions,
//
registerModal,
registerCustomModal,
registerImportModal,
importUrl,
handleFormConfig,
//
tableReloading,
handleSubmitFlow,
hrefComponent,
viewOnlineCellImage,
//
onlineTreeTableRef,
handlerFormSuccess,
searchReset,
handleExpand,
expandedRowKeys,
handleExpandedRowsChange,
registerDetailModal,
handleClickFieldHref,
registerBpmModal,
pageLoading,
};
//useCompatibleOldVersion(that);
return that;
},
// 1loadsh console.log(that.simpleDateFormat(new Date().getTime(),'yyyy-MM-dd'));
// 2. value
// 3. api
// 1 js
// 2css js document headcss
// TODO
// TODO
// const reportPrintUrl = ref('')
};
</script>
<style lang="less">
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td,
th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
.online-cell-image {
height: 25px !important;
margin: 0 auto;
max-width: 80px;
font-size: 12px;
font-style: italic;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,83 @@
import { defHttp } from '/@/utils/http/axios';
export enum Api {
list = '/online/cgform/head/list',
delete = '/online/cgform/head/delete',
deleteBatch = '/online/cgform/head/deleteBatch',
databaseSync = '/online/cgform/api/doDbSynch',
removeRecord = '/online/cgform/head/removeRecord',
copyOnline = '/online/cgform/head/copyOnline',
copyTable = '/online/cgform/head/copyOnlineTable',
// CgformModalAPI
addAll = '/online/cgform/api/addAll',
editAll = '/online/cgform/api/editAll',
queryField = '/online/cgform/field/listByHeadId',
queryIndex = '/online/cgform/index/listByHeadId',
checkOnlyTable = '/online/cgform/api/checkOnlyTable',
//
editHead = '/online/cgform/head/edit'
}
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
//
export const doBatchRemove = (idList: string[]) => doRemove(idList, 0);
export const doSingleRemove = (pid) => defHttp.delete({ url: Api.removeRecord, params: { id: pid } },
{ joinParamsToUrl: true });
//
export const doBatchDelete = (idList: string[]) => doRemove(idList, 1);
export const doSingleDelete = (pid) => defHttp.delete({ url: Api.delete, params: { id: pid } },
{ joinParamsToUrl: true });
//
function doRemove(idList: string[], flag: number) {
return defHttp.delete(
{
url: Api.deleteBatch,
params: {
ids: idList.join(','),
flag: flag,
},
},
{ joinParamsToUrl: true }
);
}
//
export const doDatabaseSync = (id, method) =>
defHttp.post({ url: `${Api.databaseSync}/${id}/${method}`, timeout: 12000, timeoutErrorMessage: '同步数据库超时,已自动刷新' });
export const doCopyOnlineView = (id) => defHttp.post({ url: `${Api.copyOnline}?code=${id}` });
/**
* 复制表
* @param id 要复制的表的id
* @param tableName 新的表名
* @param params 其他参数
*/
export const doCopyTable = (id, tableName, params?) => defHttp.get({ url: `${Api.copyTable}/${id}`, params: { tableName, ...params } });
// formApi
export const formApi = {
// e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg==
doQueryField: (headId: string, params?) => defHttp.get({ url: Api.queryField, params: { headId, ...params } }),
// index
doQueryIndexes: (headId: string, params?) => defHttp.get({ url: Api.queryIndex, params: { headId, ...params } }),
//
doSaveOrUpdate: (params, isUpdate) => {
if (isUpdate) {
return defHttp.put({ url: Api.editAll, params });
} else {
return defHttp.post({ url: Api.addAll, params });
}
},
//
editHead: (params)=>{
return defHttp.put({ url: Api.editHead, params });
}
};

View File

@ -0,0 +1,308 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { getDictItemsByCode } from '/@/utils/dict';
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
import { buildUUID } from '/@/utils/uuid';
// flag
export const VALIDATE_FAILED = 'validate-failed';
export const columns: BasicColumn[] = [
{
title: '表类型',
align: 'center',
sorter: true,
dataIndex: 'tableType',
width: 140,
customRender({ text, record }) {
let tableTypeDictOptions = getDictItemsByCode('cgform_table_type');
let tbTypeText = filterDictText(tableTypeDictOptions, text);
if (record.isTree === 'Y') {
tbTypeText += '(树)';
}
if (record.themeTemplate === 'innerTable') {
tbTypeText += '(内嵌)';
} else if (record.themeTemplate === 'erp') {
tbTypeText += '(ERP)';
} else if (record.themeTemplate === 'tab') {
tbTypeText += '(TAB)';
}
return tbTypeText;
},
},
{
title: '表名',
sorter: true,
align: 'center',
dataIndex: 'tableName',
width: 240,
},
{
title: '表描述',
align: 'center',
dataIndex: 'tableTxt',
width: 220,
},
{
title: '版本',
align: 'center',
dataIndex: 'tableVersion',
width: 120,
},
{
title: '同步状态',
align: 'center',
sorter: true,
dataIndex: 'isDbSynch',
slots: { customRender: 'dbSync' },
width: 120,
},
{
title: '创建时间',
align: 'center',
sorter: true,
dataIndex: 'createTime',
width: 240,
},
];
export const searchFormSchema: FormSchema[] = [
{
label: '表名',
field: 'tableName',
component: 'JInput',
},
{
label: '表类型',
field: 'tableType_MultiString',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'cgform_table_type',
mode: 'multiple',
},
},
{
label: '表描述',
field: 'tableTxt',
component: 'JInput',
},
];
/** 扩展JSON默认值 */
export const ExtConfigDefaultJson = {
//
reportPrintShow: 0,
reportPrintUrl: '',
joinQuery: 0,
modelFullscreen: 0,
modalMinWidth: '',
commentStatus: 0,
tableFixedAction: 1,
tableFixedActionType: 'right',
// update-begin--author:liaozhiyang---date:20240329---forQQYUN-7872onlinelabel
formLabelLengthShow: 0,
formLabelLength: null,
// update-begin--author:liaozhiyang---date:20240329---forQQYUN-7872onlinelabel
//
enableExternalLink: 0,
externalLinkActions: 'add,edit,detail',
};
/** 获取主表的初始化数据 */
export function useInitialData() {
let initialData = [
{
dbFieldName: 'id',
dbFieldTxt: '主键',
dbLength: 36,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '1',
dbIsNull: '0',
// table2
isShowForm: '0',
isShowList: '0',
isReadOnly: '1',
fieldShowType: 'text',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'create_by',
dbFieldTxt: '创建人',
dbLength: 50,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'create_time',
dbFieldTxt: '创建日期',
dbLength: 0,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'Datetime',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'datetime',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'update_by',
dbFieldTxt: '更新人',
dbLength: 50,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'update_time',
dbFieldTxt: '更新日期',
dbLength: 0,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'Datetime',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'datetime',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'sys_org_code',
dbFieldTxt: '所属部门',
dbLength: 64,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
// {
// dbFieldName: 'sys_org_code',
// dbFieldTxt: '',
// dbLength: 50,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// },
// {
// dbFieldName: 'sys_company_code',
// dbFieldTxt: '',
// dbLength: 50,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// },
// {
// dbFieldName: 'bpm_status',
// dbFieldTxt: '',
// dbLength: 32,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// }
];
// id
let tempIds: string[] = [];
initialData.forEach((record) => {
record['id'] = buildUUID();
tempIds.push(record['id']);
});
return { initialData, tempIds };
}
/** 获取树的初始化数据 */
export function useTreeNeedFields() {
return [
{
dbFieldName: 'pid',
dbFieldTxt: '父级节点',
dbLength: 32,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '1',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '200',
queryMode: 'single',
dbIsSync: '1'
},
{
dbFieldName: 'has_child',
dbFieldTxt: '是否有子节点',
dbLength: 3,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'list',
fieldLength: '200',
queryMode: 'single',
// table3
dictField: 'yn',
dbIsSync: '1'
},
];
}
/**
* online 默认按钮
*/
export const onlineDefaultButton = [
{ code: 'add', title: '新增', status: 0 },
{ code: 'edit', title: '编辑', status: 0 },
{ code: 'delete', title: '删除', status: 0 },
{ code: 'export', title: '导出', status: 0 },
{ code: 'import', title: '导入', status: 0 },
{ code: 'query', title: '查询', status: 0 },
];

View File

@ -0,0 +1,116 @@
<template>
<BasicModal
:height="180"
:title="title"
:width="600"
:maskClosable="false"
v-bind="$attrs"
@register="registerModal"
:footer="null"
@cancel="handleCancel"
>
<div class="aiWrap">
<div class="titleArea">
<svg
t="1707100353985"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4235"
width="26"
height="26"
>
<path
d="M512 64C264.8 64 64 264.8 64 512s200.8 448 448 448 448-200.8 448-448S759.2 64 512 64z m32 704h-64v-64h64v64z m11.2-203.2l-5.6 4.8c-3.2 2.4-5.6 8-5.6 12.8v58.4h-64v-58.4c0-24.8 11.2-48 29.6-63.2l5.6-4.8c56-44.8 83.2-68 83.2-108C598.4 358.4 560 320 512 320c-49.6 0-86.4 36.8-86.4 86.4h-64C361.6 322.4 428 256 512 256c83.2 0 150.4 67.2 150.4 150.4 0 72.8-49.6 112.8-107.2 158.4z"
p-id="4236"
fill="currentColor"
></path>
</svg>
<h3>创建工作表字段需要专业建议?试试AI智能推荐吧</h3>
</div>
<p class="tip">可尝试添加修饰词:智能家居行业的生产计划表</p>
<div class="content">
<a-input v-model:value.trim="inputValue" placeholder="请输入修饰词" /><a-button :loading="loading" type="primary" @click="handleCreate"
>生成表</a-button
>
</div>
</div>
</BasicModal>
</template>
<script setup>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { ref } from 'vue';
import { defHttp } from '/@/utils/http/axios';
const configUrl = {
aigc: '/online/cgform/api/aigc',
};
const emit = defineEmits(['register', 'success']);
const [registerModal, { closeModal }] = useModalInner();
const { createMessage } = useMessage();
const inputValue = ref('');
const loading = ref(false);
const title = ref('ai创建表');
const handleCancel = () => {
closeModal();
};
const handleCreate = () => {
if (inputValue.value.trim() === '') {
createMessage.warning('请输入修饰词~');
} else {
loading.value = true;
defHttp
// timeout 5
.post({ url: `${configUrl.aigc}?prompt=${inputValue.value}`, timeout: 1e3 * 60 * 5 })
.then((res) => {
loading.value = false;
handleCancel();
emit('success');
inputValue.value = '';
})
.catch((err) => {
loading.value = false;
});
}
};
</script>
<style lang="less" scoped>
html[data-theme="light"] {
.aiWrap {
h3 {color: #333;}
.tip { color: #666;}
}
}
.aiWrap {
padding: 30px 20px 20px;
.titleArea {
position: relative;
color: #ffb308;
width: fit-content;
margin:0 auto;
svg {
position: absolute;
left: -30px;
margin-right: 8px;
}
}
h3 {
text-align: center;
margin-bottom: 0;
}
.tip {
text-align: center;
margin-bottom: 26px;
}
.content {
display: flex;
}
.ant-btn {
margin-left: 16px;
}
}
</style>

View File

@ -0,0 +1,232 @@
<!-- Online表单配置地址弹窗 -->
<template>
<BasicModal
@register="registerModal"
title="配置地址"
:width="750"
:canFullscreen="false"
:showOkBtn="false"
cancelText="关闭"
>
<div class="content">
<a-collapse v-model:activeKey="collapseKey" class="j-collapse" :bordered="false" ghost>
<a-collapse-panel key="def" header="配置地址" class="j-collapse-panel no-header">
<a-row style="margin-bottom: 8px">
<a-col :span="24">
<a-input :readOnly="true" addonBefore="数据列表地址" :value="getAddress.list">
<template #addonAfter>
<a :href="getAddress.list" target="_blank">打开</a>
</template>
</a-input>
</a-col>
</a-row>
<a-button class="copy-sql" type="primary" size="small" @click="copySqlClick">复制菜单SQL</a-button>
</a-collapse-panel>
<a-collapse-panel v-if="enableExternalLink" key="external" header="外部链接">
<a-row style="margin-bottom: 8px">
<a-col :span="24">
<a-input :readOnly="true" addonBefore="外部新增地址" :value="getAddress.extLink.add">
<template #addonAfter>
<a :href="getAddress.extLink.add + '?token=' + accessToken" target="_blank">打开</a>
</template>
</a-input>
</a-col>
</a-row>
<a-row style="margin-bottom: 8px">
<a-col :span="24">
<a-input :readOnly="true" addonBefore="外部修改地址" :value="getAddress.extLink.edit">
<template #addonAfter>
<a @click="openPage(getAddress.extLink.edit)">打开</a>
</template>
</a-input>
</a-col>
</a-row>
<a-row style="margin-bottom: 8px">
<a-col :span="24">
<a-input :readOnly="true" addonBefore="外部详情地址" :value="getAddress.extLink.detail">
<template #addonAfter>
<a @click="openPage(getAddress.extLink.detail)">打开</a>
</template>
</a-input>
</a-col>
</a-row>
<div style="text-align: right; color: red">注意<span style="font-weight: bold">{dataId}</span> 为数据id</div>
</a-collapse-panel>
</a-collapse>
</div>
</BasicModal>
<!-- 让用户输入 dataId 的弹窗 -->
<BasicModal v-model:visible="dataIdProps.visible" v-bind="dataIdBind">
<a-input placeholder="请输入dataId" v-model:value="dataIdProps.value"></a-input>
<template #footer>
<a :href="dataIdHref" target="_blank">
<a-button type="primary" @click="onClickDataId">确定</a-button>
</a>
</template>
</BasicModal>
</template>
<script setup lang="ts">
import {computed, reactive, ref} from "vue";
import {BasicModal, useModalInner} from "@/components/Modal";
import {parseExtConfigJson} from "../util/utils";
import {copyTextToClipboard} from "@/hooks/web/useCopyToClipboard";
import {getToken} from "@/utils/auth";
import {useMessage} from "@/hooks/web/useMessage";
import {buildUUID} from "@/utils/uuid";
const emit = defineEmits(['register'])
const {createMessage: $message} = useMessage();
const accessToken = computed(() => getToken());
const collapseKey = ref(['def'])
const enableExternalLink = ref(false);
const themeTemplate = ref('normal');
const isTree = ref(false);
const model = reactive({
title: '',
content: '',
copyText: '',
copyTitle: '',
formId: '',
})
const [registerModal] = useModalInner((data: Recordable) => {
Object.assign(model, data, {
formId: data.record.id,
})
// JSON
const extConfigJson = parseExtConfigJson(data.record);
enableExternalLink.value = extConfigJson.enableExternalLink === 1;
themeTemplate.value = data.record.themeTemplate;
isTree.value = data.record.isTree === 'Y';
if (enableExternalLink.value) {
collapseKey.value = ['def', 'external'];
} else {
collapseKey.value = ['def'];
}
});
const getAddress = computed(() => {
const extLink: Recordable = {}
if (enableExternalLink.value) {
const before = `/online/cgform/share/${model.formId}`;
extLink.add = `${before}/add`;
extLink.edit = `${before}/u/{dataId}`;
extLink.detail = `${before}/d/{dataId}`;
}
return {
list: model.content,
extLink,
};
});
/**
* 复制sql
*/
function copySqlClick() {
// update-begin--author:liaozhiyang---date:20240308---forQQYUN-12348onlinesql
let component_name = 'OnlineAutoList';
if (themeTemplate.value === 'normal') {
component_name = 'OnlineAutoList';
} else if (themeTemplate.value === 'erp') {
component_name = 'CgformErpList';
} else if (themeTemplate.value === 'innerTable') {
component_name = 'OnlCgformInnerTableList';
} else if (themeTemplate.value === 'tab') {
component_name = 'OnlCgformTabList';
}
if (isTree.value) {
component_name = 'DefaultOnlineList';
}
// update-end--author:liaozhiyang---date:20240308---forQQYUN-12348onlinesql
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, '${model.copyTitle}', '${model.copyText}', '1', '${component_name}', NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', null, NULL, NULL, 0)
`;
copyText(insertMenuSql);
}
//
function copyText(text: string) {
const success = copyTextToClipboard(text)
if (success) {
$message.success('复制成功!')
} else {
$message.error('复制失败!')
}
return success
}
const dataIdProps = reactive({
base: '',
value: '',
visible: false,
});
const dataIdHref = computed(() => {
return dataIdProps.value ? dataIdProps.base.replace(/{dataId}/, dataIdProps.value) + '?token=' + accessToken.value : undefined;
});
const dataIdBind = computed(() => {
return {
title: '请输入dataId',
minHeight: 120,
centered: true,
canFullscreen: false,
onOk: () => (dataIdProps.visible = false),
onCancel: () => (dataIdProps.visible = false),
};
});
function openPage(url: string) {
dataIdProps.base = url;
dataIdProps.value = '';
dataIdProps.visible = true;
}
function onClickDataId() {
if (!dataIdProps.value) {
$message.warn('请输入dataId');
return
}
dataIdProps.visible = false;
}
</script>
<style scoped lang="less">
.j-collapse {
//background: red;
:deep(.ant-collapse-header) {
padding-left: 0;
}
:deep(.ant-collapse-content-box) {
padding: 0;
}
.j-collapse-panel {
&.no-header {
:deep(.ant-collapse-header) {
display: none;
}
}
}
}
.content {
padding: 20px;
}
.copy-sql {
float: right;
margin-top: 6px;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="cgf-item">
<label class="cgf-label">
<span v-if="required" class="cgf-required">*</span>{{ label }}
</label>
<a-input
v-if="type === 'input'"
:value="modelValue"
:disabled="disabled"
:allow-clear="allowClear"
:status="status"
style="width: 100%"
@update:value="emit('update:modelValue', $event)"
@change="emit('change', $event)"
/>
<a-input-number
v-else-if="type === 'input-number'"
:value="modelValue"
:disabled="disabled"
style="width: 100%"
@update:value="emit('update:modelValue', $event)"
@change="emit('change', $event)"
/>
<a-select
v-else-if="type === 'select'"
:value="modelValue"
:options="options"
:allow-clear="false"
:disabled="disabled"
:status="status"
style="width: 100%"
@update:value="emit('update:modelValue', $event)"
@change="emit('change', $event)"
/>
<a-radio-group
v-else-if="type === 'radio-group'"
:value="modelValue"
:options="options"
:disabled="disabled"
@update:value="emit('update:modelValue', $event)"
@change="emit('change', $event)"
/>
</div>
</template>
<script setup lang="ts">
defineProps<{
label: string;
type: 'input' | 'select' | 'input-number' | 'radio-group';
modelValue?: any;
required?: boolean;
disabled?: boolean;
allowClear?: boolean;
options?: { label: string; value: any }[];
status?: '' | 'error';
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: any): void;
(e: 'change', value: any): void;
}>();
</script>

View File

@ -0,0 +1,533 @@
<template>
<div class="cgform-fields" :class="{ 'cgform-fields--inline': isInlineLayout }">
<CgformFieldItem
v-for="item in baseFields"
:key="item.field"
:label="item.label"
:type="item.type"
:model-value="(formModel as Recordable)[item.field]"
:required="item.required"
:disabled="item.disabled"
:allow-clear="item.allowClear"
:options="item.options"
:status="item.status"
@update:model-value="(formModel as Recordable)[item.field] = $event"
@change="item.onChange?.($event)"
/>
<template v-if="[1, 2].includes(formModel.tableType)">
<!-- 非附表条件字段单表/主表 -->
<CgformFieldItem
v-for="item in conditionalFields"
v-show="item.show !== false"
:key="item.field"
:label="item.label"
:type="item.type"
:model-value="(formModel as Recordable)[item.field]"
:required="item.required"
:disabled="item.disabled"
:allow-clear="item.allowClear"
:options="item.options"
:status="item.status"
@update:model-value="(formModel as Recordable)[item.field] = $event"
@change="item.onChange?.($event)"
/>
<!-- 非附表类型按钮 -->
<div v-if="formModel.tableType !== 3" class="cgf-item cgf-item--buttons">
<a-button preIcon="ant-design:setting" @click="onOpenExtConfig">扩展配置</a-button>
<a-button type="link" :preIcon="expandStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" @click="expandStatus = !expandStatus">{{
expandStatus ? '收起' : '展开'
}}</a-button>
</div>
</template>
<template v-if="formModel.tableType === 3">
<!-- 附表类型展开row2=显示复选框+表单风格+关联类型+序号 -->
<template v-if="expandStatus">
<CgformFieldItem
v-for="item in preGroupFields"
:key="item.field"
:label="item.label"
:type="item.type"
:model-value="(formModel as Recordable)[item.field]"
:options="item.options"
@update:model-value="(formModel as Recordable)[item.field] = $event"
/>
<div class="cgf-item cgf-item--group">
<div class="cgf-item--group-left">
<a-radio-group v-model:value="formModel.relationType" :options="relationTypeOptions" @change="onRelationTypeChange" />
<label class="cgf-label cgf-label--inline">序号</label>
<a-input-number v-model:value="formModel.tabOrderNum" />
</div>
<div class="cgf-item--group-right">
<a-button preIcon="ant-design:setting" :disabled="formModel.relationType === 1" @click="onOpenExtConfig">扩展配置</a-button>
<a-button type="link" preIcon="ant-design:up-outlined" @click="expandStatus = !expandStatus">收起</a-button>
</div>
</div>
<CgformFieldItem
v-for="item in postGroupFields"
:key="item.field"
:label="item.label"
:type="item.type"
:model-value="(formModel as Recordable)[item.field]"
:options="item.options"
@update:model-value="(formModel as Recordable)[item.field] = $event"
/>
</template>
<!-- 附表类型收起col1+col2空格 + col3全部内容-->
<template v-else>
<div class="cgf-spacer"></div>
<div class="cgf-spacer"></div>
<div class="cgf-item cgf-item--group">
<div class="cgf-item--group-left">
<a-radio-group v-model:value="formModel.relationType" :options="relationTypeOptions" @change="onRelationTypeChange" />
<label class="cgf-label cgf-label--inline">序号</label>
<a-input-number v-model:value="formModel.tabOrderNum" style="width: 50px" />
</div>
<div class="cgf-item--group-right">
<a-button preIcon="ant-design:setting" :disabled="formModel.relationType === 1" @click="onOpenExtConfig">扩展配置</a-button>
<a-button type="link" preIcon="ant-design:down-outlined" @click="expandStatus = !expandStatus">展开</a-button>
</div>
</div>
</template>
</template>
</div>
<ExtendConfigModal @register="registerExtendConfigModal" :parentForm="formActionForModal" @ok="onExtConfigOk" />
</template>
<script setup lang="ts">
import { computed, reactive, ref, toRaw } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { duplicateValidate } from '/@/utils/helper/validator';
import ExtendConfigModal from './ExtendConfigModal.vue';
import CgformFieldItem from './CgformFieldItem.vue';
import { ExtConfigDefaultJson } from '../cgform.data';
import { parseExtConfigJson } from '../util/utils';
interface FieldItem {
label: string;
field: string;
type: 'input' | 'select' | 'input-number' | 'radio-group';
required?: boolean;
disabled?: boolean;
allowClear?: boolean;
options?: { label: string; value: any }[];
onChange?: (value: any) => void;
show?: boolean;
status?: '' | 'error';
}
const fieldErrors = reactive<Record<string, '' | 'error'>>({
tableName: '',
tableTxt: '',
treeFieldname: '',
});
const emit = defineEmits<{
(e: 'tableTypeChange', value: number): void;
(e: 'relationTypeChange', value: number): void;
(e: 'isTreeChange', value: string): void;
(e: 'extConfigSaved', values: Recordable): void;
}>();
const { createMessage: $message } = useMessage();
const expandStatus = ref(false);
const tableTypeOptions = [
{ label: '单表', value: 1 },
{ label: '主表', value: 2 },
{ label: '附表', value: 3 },
];
const relationTypeOptions = [
{ label: '一对多', value: 0 },
{ label: '一对一', value: 1 },
];
const isCheckboxOptions = [
{ label: '显示', value: 'Y' },
{ label: '不显示', value: 'N' },
];
const themeTemplateOptions = [
{ label: '默认主题', value: 'normal' },
{ label: 'ERP主题(一对多)', value: 'erp' },
{ label: '内嵌子表主题(一对多)', value: 'innerTable' },
{ label: 'TAB主题(一对多)', value: 'tab' },
];
const formTemplateOptions = [
{ label: '一列', value: '1' },
{ label: '两列', value: '2' },
{ label: '三列', value: '3' },
{ label: '四列', value: '4' },
];
const scrollOptions = [
{ label: '有', value: 1 },
{ label: '无', value: 0 },
];
const isPageOptions = [
{ label: '显示', value: 'Y' },
{ label: '不显示', value: 'N' },
];
const isTreeOptions = [
{ label: '是', value: 'Y' },
{ label: '否', value: 'N' },
];
const DEFAULT_FORM_MODEL = {
id: '',
tableVersion: null as any,
tableName: '',
tableTxt: '',
tableType: 1,
relationType: 0,
tabOrderNum: null as any,
idSequence: '',
isCheckbox: 'Y',
themeTemplate: 'normal',
formTemplate: '1',
scroll: 1,
isPage: 'Y',
isTree: 'N',
treeParentIdField: '',
treeIdField: '',
treeFieldname: '',
subTableStr: '',
extConfigJson: '',
};
const formModel = reactive({ ...DEFAULT_FORM_MODEL });
function onTableTypeChange(value: number) {
if (value === 1) {
formModel.themeTemplate = 'normal';
} else if (value === 3) {
formModel.isTree = 'N';
emit('isTreeChange', 'N');
}
emit('tableTypeChange', value);
}
function onRelationTypeChange(e: any) {
const value = e?.target?.value ?? e;
emit('relationTypeChange', value);
}
function onIsTreeChange(value: string) {
emit('isTreeChange', value);
}
let _tableNameLastMsg = '';
function validateTableNameSync(val: string) {
let msg = '';
if (!val) {
fieldErrors.tableName = 'error';
} else if (/[\u4E00-\u9FA5]/g.test(val)) {
fieldErrors.tableName = 'error';
msg = '表名不允许输入中文';
} else if (val.length > 50) {
fieldErrors.tableName = 'error';
msg = '表名最长50个字符';
} else {
fieldErrors.tableName = '';
}
if (msg && msg !== _tableNameLastMsg) {
$message.warning(msg);
}
_tableNameLastMsg = msg;
}
function validateTableTxtSync(val: string) {
fieldErrors.tableTxt = !val || val.length > 200 ? 'error' : '';
}
//
const baseFields = computed<FieldItem[]>(() => [
{
label: '表名',
field: 'tableName',
type: 'input',
required: true,
disabled: !!(formModel.tableVersion && formModel.tableVersion != 1),
allowClear: true,
status: fieldErrors.tableName,
onChange: (e: any) => validateTableNameSync(e?.target?.value ?? e ?? ''),
},
{
label: '表描述',
field: 'tableTxt',
type: 'input',
required: true,
allowClear: true,
status: fieldErrors.tableTxt,
onChange: (e: any) => validateTableTxtSync(e?.target?.value ?? e ?? ''),
},
{
label: '表类型',
field: 'tableType',
type: 'select',
options: tableTypeOptions,
onChange: onTableTypeChange,
},
]);
//
const subTableField: FieldItem = {
label: '附表',
field: 'subTableStr',
type: 'input',
disabled: true,
};
//
// const relationTypeField: FieldItem = {
// label: '',
// field: 'relationType',
// type: 'radio-group',
// options: relationTypeOptions,
// onChange: onRelationTypeChange,
// };
//
// const tabOrderNumField: FieldItem = {
// label: '',
// field: 'tabOrderNum',
// type: 'input-number',
// };
//
const expandCommonFields = computed<FieldItem[]>(() => [
{ label: '复选框', field: 'isCheckbox', type: 'select', options: isCheckboxOptions },
{ label: '表单风格', field: 'formTemplate', type: 'select', options: formTemplateOptions },
{ label: '滚动条', field: 'scroll', type: 'select', options: scrollOptions },
{ label: '分页', field: 'isPage', type: 'select', options: isPageOptions },
]);
// tableType expandStatus
const conditionalFields = computed<FieldItem[]>(() => {
const { tableType, isTree } = formModel;
const expanded = expandStatus.value;
// (tableType === 3) preGroupFields/postGroupFields
if (tableType === 3) return [];
// (tableType === 2)
if (tableType === 2) {
const fields: FieldItem[] = [{ ...subTableField, show: !!formModel.subTableStr }];
if (expanded) {
fields.push(...expandCommonFields.value, { label: '主题模板', field: 'themeTemplate', type: 'select', options: themeTemplateOptions });
}
return fields;
}
// (tableType === 1)
if (expanded) {
const fields: FieldItem[] = [
...expandCommonFields.value,
{ label: '是否树', field: 'isTree', type: 'select', options: isTreeOptions, onChange: onIsTreeChange },
];
if (isTree === 'Y') {
fields.push(
{ label: '树父ID', field: 'treeParentIdField', type: 'input' },
{ label: '树表单列', field: 'treeFieldname', type: 'input', required: true, status: fieldErrors.treeFieldname }
);
}
return fields;
}
return [];
});
// + group isCheckbox, formTemplate row2 col1-2
const preGroupFields = computed<FieldItem[]>(() => {
if (formModel.tableType !== 3 || !expandStatus.value) return [];
return expandCommonFields.value.slice(0, 2);
});
// + group scroll, isPage row3 col1-2
const postGroupFields = computed<FieldItem[]>(() => {
if (formModel.tableType !== 3 || !expandStatus.value) return [];
return expandCommonFields.value.slice(2);
});
//
const isInlineLayout = computed(() => {
if (expandStatus.value) return false;
if (formModel.tableType === 1) return true;
if (formModel.tableType === 2 && !formModel.subTableStr) return true;
return false;
});
function resetFields() {
Object.assign(formModel, DEFAULT_FORM_MODEL);
fieldErrors.tableName = '';
fieldErrors.tableTxt = '';
}
function setFieldsValue(values: Recordable) {
Object.assign(formModel, values);
}
function getFieldsValue(fields?: string[]) {
if (fields) {
return Object.fromEntries(fields.map((f) => [f, (formModel as Recordable)[f]]));
}
return { ...toRaw(formModel) };
}
async function validate() {
fieldErrors.tableName = '';
fieldErrors.tableTxt = '';
fieldErrors.treeFieldname = '';
if (!formModel.tableName) {
fieldErrors.tableName = 'error';
return Promise.reject('请输入表名');
}
if (/[\u4E00-\u9FA5]/g.test(formModel.tableName)) {
fieldErrors.tableName = 'error';
return Promise.reject('表名不允许输入中文');
}
if (formModel.tableName.length > 50) {
fieldErrors.tableName = 'error';
return Promise.reject('表名最长50个字符');
}
try {
await duplicateValidate('onl_cgform_head', 'table_name', formModel.tableName, formModel.id);
} catch (e: any) {
fieldErrors.tableName = 'error';
return Promise.reject('表名已存在');
}
if (!formModel.tableTxt) {
fieldErrors.tableTxt = 'error';
return Promise.reject('请输入表描述');
}
if (formModel.tableTxt.length > 200) {
fieldErrors.tableTxt = 'error';
return Promise.reject('表描述最长200个字');
}
if (formModel.isTree === 'Y' && formModel.tableType === 1 && !formModel.treeFieldname) {
fieldErrors.treeFieldname = 'error';
expandStatus.value = true;
return Promise.reject('请填写树表单列');
}
if (extConfigJson.joinQuery && ['erp'].includes(formModel.themeTemplate)) {
return Promise.reject('ERP不支持联合查询功能');
}
if (extConfigJson.joinQuery && ['innerTable'].includes(formModel.themeTemplate)) {
return Promise.reject('内嵌子表不支持联合查询功能');
}
return getFieldsValue();
}
// JSON
let extConfigJson: Recordable = {};
const [registerExtendConfigModal, extendConfigModal] = useModal();
// ExtendConfigModal parentForm
const formActionForModal = {
getFieldsValue,
setFieldsValue,
validateFields: (_fields?: string[]) => Promise.resolve(),
};
function initialExtConfigJson(record: Recordable) {
const parseJSON = parseExtConfigJson(record);
extConfigJson = Object.assign({}, ExtConfigDefaultJson, parseJSON, {
isDesForm: record.isDesForm || 'N',
desFormCode: record.desFormCode || '',
});
}
function getExtConfigJson() {
return extConfigJson;
}
function onOpenExtConfig() {
extendConfigModal.openModal(true, { extConfigJson });
}
async function onExtConfigOk(values: Recordable) {
extConfigJson = values;
emit('extConfigSaved', values);
}
defineExpose({ formModel, resetFields, setFieldsValue, getFieldsValue, validate, initialExtConfigJson, getExtConfigJson });
</script>
<style lang="less" scoped>
.cgform-fields {
display: flex;
flex-wrap: wrap;
padding-top: 5px;
:deep(.cgf-item) {
width: 33.333%;
display: flex;
align-items: center;
padding: 4px 8px;
.cgf-label {
flex-shrink: 0;
width: 75px;
text-align: right;
padding-right: 8px;
font-size: 14px;
.cgf-required {
color: #ff4d4f;
margin-right: 2px;
}
}
}
.cgf-item--buttons {
flex: 1;
min-width: 33.333%;
justify-content: flex-end;
display: flex;
align-items: center;
padding: 4px 8px;
// flex-grow
&-fixed {
flex: 0 0 33.333%;
min-width: unset;
}
}
.cgf-spacer {
width: 33.333%;
}
.cgf-item--group {
width: 33.333%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 8px;
padding-left: 34px;
.cgf-label {
width: 50px;
}
.cgf-item--group-left {
display: flex;
align-items: center;
}
.cgf-item--group-right {
display: flex;
align-items: center;
}
}
// /
&--inline {
flex-wrap: nowrap;
:deep(.cgf-item) {
flex: 1 0 0;
width: auto;
min-width: 0;
}
.cgf-item--buttons {
flex: 0 0 auto;
min-width: unset;
}
}
}
:deep(.ant-btn) {
&.ant-btn-link {
margin-right: -5px;
padding-right: 5px;
}
& > .anticon + span {
margin-left: 4px;
}
}
</style>

View File

@ -0,0 +1,879 @@
<template>
<BasicModal
ref="modalRef"
:title="title"
:width="1200"
:maskClosable="false"
:defaultFullscreen="true"
:confirmLoading="confirmLoading"
v-bind="$attrs"
wrapClassName="onlForm-config-modal"
@cancel="onCancel"
@register="registerModal"
>
<div ref="onlFormContentRef" class="onlForm-content">
<a-spin ref="spinRef" wrapperClassName="pl-2 pr-2" :spinning="confirmLoading">
<div ref="onlFormContentFormRef" class="onlForm-content-form">
<CgformHeadForm
ref="cgformHeadFormRef"
@tableTypeChange="onTableTypeChange"
@relationTypeChange="onRelationTypeChange"
@isTreeChange="onIsTreeChange"
@extConfigSaved="onExtConfigSaved"
/>
</div>
<a-spin :spinning="tableLoading || hideTabs">
<a-tabs v-show="!hideTabs" v-model:activeKey="activeKey" @change="onTabsChange">
<a-tab-pane tab="数据库属性" key="dbTable" forceRender>
<DBAttributeTable
ref="dbTable"
:isUpdate="isUpdate"
:tableName="oldTableName"
:actionButton="actionButton"
@added="onTableAdded"
@removed="onTableRemoved"
@dragged="onTableDragged"
@inserted="onTableInserted"
@syncDbType="onTableSyncDbType"
@syncDbIsPersist="onTableSyncDbIsPersist"
@syncDbIsNull="onTableSyncDbIsNull"
/>
</a-tab-pane>
<a-tab-pane tab="页面属性" key="pageTable" forceRender>
<PageAttributeTable ref="pageTable" />
</a-tab-pane>
<a-tab-pane tab="校验字段" key="checkTable" forceRender>
<CheckDictTable ref="checkTable" />
</a-tab-pane>
<a-tab-pane tab="外键" key="fkTable" forceRender>
<ForeignKeyTable ref="fkTable" :actionButton="actionButton" />
</a-tab-pane>
<a-tab-pane tab="索引" key="idxTable" forceRender>
<IndexTable ref="idxTable" :actionButton="actionButton" />
</a-tab-pane>
<a-tab-pane key="queryTable" forceRender>
<template #tab>
<span>
个性查询配置
<a-tooltip>
<template #title>允许自定义查询表单字段控件类型</template>
<Icon icon="bx:help-circle"></Icon>
</a-tooltip>
</span>
</template>
<QueryTable ref="queryTable" @query="onTableQuery" />
</a-tab-pane>
</a-tabs>
</a-spin>
</a-spin>
</div>
<template #footer>
<div class="footer-area">
<div class="rightArea">
<a-button @click="onCancel">关闭</a-button>
<a-button type="primary" :loading="confirmLoading" preIcon="ant-design:save" @click="onSubmit">保存</a-button>
</div>
<div class="leftArea">
<div v-if="aiTestMode && !isUpdate">
<a-select
v-model:value="aiTestTable"
placeholder="请选择测试的数据模型"
:getPopupContainer="(n) => n?.parentElement"
style="width: 300px; margin: 0 10px 0 0;text-align: left;"
>
<template v-for="(item, index) in aiTableList" :key="index">
<a-select-option :value="item.name">{{ item.title + '' + item.name + '' }}</a-select-option>
</template>
</a-select>
<a-button type="primary" ghost @click="initVirtualData">生成数据&gt;&gt;</a-button>
</div>
<div v-if="isUpdate" class="positioning-area">
<a-input v-model:value="positioning" placeholder="请输入字段名称或字段备注" allowClear @pressEnter="handlePositioning"></a-input>
<a-button type="primary" ghost @click="handlePositioning">定位</a-button>
</div>
</div>
</div>
</template>
</BasicModal>
</template>
<script lang="ts">
import { ref, computed, nextTick, provide, defineComponent, toRaw } from 'vue';
import { useResizeObserver } from '@vueuse/core';
import { Icon } from '/@/components/Icon';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import DBAttributeTable from './tables/DBAttributeTable.vue';
import CgformHeadForm from './CgformHeadForm.vue';
import PageAttributeTable from './tables/PageAttributeTable.vue';
import CheckDictTable from './tables/CheckDictTable.vue';
import ForeignKeyTable from './tables/ForeignKeyTable.vue';
import IndexTable from './tables/IndexTable.vue';
import QueryTable from './tables/QueryTable.vue';
import { CgformModal } from '../types';
import { useInitialData, VALIDATE_FAILED, useTreeNeedFields } from '../cgform.data';
import { formApi } from '../cgform.api';
import { simpleDebounce } from '/@/utils/common/compUtils';
import { useOnlineTest } from '../hooks/aitest/useOnlineTest';
import { buildUUID } from '/@/utils/uuid';
import { sleep } from '/@/utils';
export default defineComponent({
name: 'CgformModal',
components: {
BasicModal,
CgformHeadForm,
DBAttributeTable,
PageAttributeTable,
CheckDictTable,
ForeignKeyTable,
IndexTable,
QueryTable,
Icon,
},
emits: ['success', 'register'],
props: {
actionButton: {
type: Boolean,
default: true,
required: false,
},
},
setup(props, { emit }) {
const { createMessage: $message } = useMessage();
const modalRef = ref();
const spinRef = ref();
//
const isUpdate = ref(false);
//
let model: Recordable = {};
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
//
const confirmLoading = ref(true);
//
const tableLoading = ref(false);
// tabs
const activeKey = ref('dbTable');
//
const hideTabs = ref(true);
// v-show
let tabsInitialized = false;
// table refs
const tables: CgformModal.TablesRef = {
dbTable: ref<CgformModal.DBAttributeTableType>(),
pageTable: ref<CgformModal.PageAttributeTableType>(),
checkTable: ref<CgformModal.CheckDictTableType>(),
fkTable: ref<CgformModal.ForeignKeyTableType>(),
idxTable: ref<CgformModal.IndexTableType>(),
queryTable: ref<CgformModal.QueryTableType>(),
};
//
const fullScreenRef = computed(() => modalRef.value?.fullScreenRef ?? false);
provide('tables', tables);
provide('fullScreenRef', fullScreenRef);
const positioning = ref('');
const onlFormContentRef = ref<HTMLElement>();
const onlFormContentFormRef = ref<HTMLElement>();
/** `.onlForm-content-form` 当前高度(随布局/展开收起等变化) */
const onlFormContentFormHeight = ref(0);
const vxetableHeight = ref(0);
provide('vxetableHeight', vxetableHeight);
useResizeObserver(onlFormContentFormRef, (entries) => {
const entry = entries[0];
if (entry) {
onlFormContentFormHeight.value = entry.contentRect.height;
vxetableHeight.value = onlFormContentRef.value!.offsetHeight - onlFormContentFormHeight.value - 128;
}
});
// ref
const cgformHeadFormRef = ref<InstanceType<typeof CgformHeadForm>>();
// formAction ExtendConfigModal useOnlineTest 使
const formAction = {
getFieldsValue: (fields?: string[]) => cgformHeadFormRef.value?.getFieldsValue(fields) ?? {},
setFieldsValue: (values: Recordable) => cgformHeadFormRef.value?.setFieldsValue(values),
validateFields: (_fields?: string[]) => Promise.resolve(),
resetFields: () => cgformHeadFormRef.value?.resetFields(),
};
//
const [registerModal, { closeModal }] = useModalInner((data) => {
isUpdate.value = data?.isUpdate ?? false;
if (isUpdate.value) {
edit(data?.record);
} else {
add();
}
});
//
const oldTableName = ref('');
// table
const syncAllTableNowDebounce = simpleDebounce(() => syncAllTableNowPromise(), 150);
// ID
let fieldTempIds: string[] = [];
//
let showSubTableStr = false;
//
let treeFieldAdded = false;
// ID
let treeFieldIds: string[] = [];
let interval: any = null;
// hook OnlineTest
const { aiTestMode, aiTestTable, aiTableList, initVirtualData, tableJsonGetHelper, refreshCacheTableName } = useOnlineTest(
{
oldTableName,
tables,
},
{
initialAllShowItem,
setAllTableData,
},
formAction
);
function add() {
edit({});
}
async function edit(record) {
confirmLoading.value = false;
activeKey.value = 'dbTable';
//
cgformHeadFormRef.value?.resetFields();
model = Object.assign({}, record);
initialAllShowItem(model);
//
positioning.value = '';
// 便 ai test data
tableJsonGetHelper(model);
cgformHeadFormRef.value?.initialExtConfigJson(model);
cgformHeadFormRef.value?.setFieldsValue(model);
oldTableName.value = model.tableName;
// update-begin--author:liaozhiyang---date:20260330---for:QQYUN-13610online
//
if (tabsInitialized) {
hideTabs.value = false;
// v-show
clearAllTableData();
} else {
sleep(1, () => {
hideTabs.value = false;
tabsInitialized = true;
});
}
// update-end--author:liaozhiyang---date:20260330---for:QQYUN-13610online
// update
if (isUpdate.value) {
// update-begin--author:liaozhiyang---date:20260210---for:QQYUN-13658Jvxetablevxetable
await getRefPromise(tables.dbTable);
// update-end--author:liaozhiyang---date:20260209---for:QQYUN-13658Jvxetablevxetable
tables.dbTable.value?.setDataSource([]);
await loadFields(model.id);
// index
await loadIndexes(model.id);
//
getRefPromise(tables.pageTable).then(() => {
tables.pageTable.value!.changePageType(model.tableType == 3, model.relationType);
});
} else {
//
let { initialData, tempIds } = useInitialData();
await setAllTableData(initialData, true);
fieldTempIds = tempIds;
}
}
//
async function loadFields(headId) {
tableLoading.value = true;
try {
let fields = await formApi.doQueryField(headId);
// 便 ai test data
console.log("online fields:", fields)
tableLoading.value = false;
await setAllTableData(fields);
} finally {
tableLoading.value = false;
}
}
//
async function loadIndexes(headId) {
let indexes = await formApi.doQueryIndexes(headId);
// 便 ai test data
console.log("online indexs:", indexes)
// update-begin--author:liaozhiyang---date:20260210---for:QQYUN-13658Jvxetablevxetable
await getRefPromise(tables.idxTable);
// update-end--author:liaozhiyang---date:20260209---for:QQYUN-13658Jvxetablevxetable
tables.idxTable.value!.setDataSource(indexes);
}
function initialAllShowItem(model) {
treeFieldAdded = model.isTree == 'Y';
showSubTableStr = model.tableType === 2;
}
// update-begin--author:liaozhiyang---date:20260330---for:QQYUN-13610online
// v-showID
function clearAllTableData() {
const { dbTable, pageTable, checkTable, fkTable, idxTable, queryTable } = tables;
dbTable.value?.setDataSource([]);
pageTable.value?.setDataSource([]);
checkTable.value?.setDataSource([]);
fkTable.value?.setDataSource([]);
idxTable.value?.setDataSource([]);
queryTable.value?.setDataSource([]);
}
// update-end--author:liaozhiyang---date:20260330---for:QQYUN-13610online
// JVxeTable
async function setAllTableData(data: Recordable[], insert?) {
const { dbTable, pageTable, checkTable, fkTable, queryTable } = tables;
// update-begin--author:liaozhiyang---date:20260210---for:QQYUN-13658Jvxetablevxetable
await getRefPromise(dbTable);
// update-end--author:liaozhiyang---date:20260209---for:QQYUN-13658Jvxetablevxetable
dbTable.value!.setDataSource(data, insert);
// tabtab使
// update-begin--author:liaozhiyang---date:20260210---for:QQYUN-13658Jvxetablevxetable
setTimeout(async () => {
await Promise.all([
getRefPromise(pageTable),
getRefPromise(checkTable),
getRefPromise(fkTable),
getRefPromise(queryTable),
]);
// update-end--author:liaozhiyang---date:20260209---for:QQYUN-13658Jvxetablevxetable
pageTable.value!.setDataSource(data, insert);
checkTable.value!.setDataSource(data, insert);
fkTable.value!.setDataSource(data, insert);
queryTable.value!.setDataSource(data, insert);
}, 10);
}
/** ATab切换事件 */
function onTabsChange(activeKey) {
// table
if (['pageTable', 'checkTable', 'fkTable', 'idxTable', 'queryTable'].indexOf(activeKey) !== -1) {
const dbTable = tables.dbTable;
const table = tables[activeKey];
dbTable.value!.tableRef!.resetScrollTop();
// update-begin--author:liaozhiyang---date:20260316---for:QQYUN-13751jVxetable
clearInterval(interval);
interval = setTimeout(() => {
table.value.syncTable(dbTable);
}, 200);
// update-end--author:liaozhiyang---date:20260316---for:QQYUN-13751jVxetable
}
}
// tableTypechange emit formModel
function onTableTypeChange(value) {
const relationType = cgformHeadFormRef.value?.formModel.relationType ?? 0;
tables.pageTable.value!.changePageType(value == 3, relationType);
}
// update-begin--author:liaozhiyang---date:20260317---for:QQYUN-9441online
// relationTypechange
function onRelationTypeChange(value) {
const tableType = cgformHeadFormRef.value?.formModel.tableType ?? 1;
tables.pageTable.value!.changePageType(tableType == 3, value);
}
// update-end--author:liaozhiyang---date:20260317---for:QQYUN-9441online
// isTreechange
function onIsTreeChange(value) {
value === 'Y' ? addTreeNeedField() : deleteTreeNeedField();
}
/** 立即主动同步所有table */
function syncAllTableNow() {
syncAllTableNowDebounce();
}
// table
async function syncAllTableNowPromise() {
let { dbTable, pageTable, checkTable, fkTable, queryTable } = tables;
await pageTable.value!.syncTable(dbTable);
await checkTable.value!.syncTable(dbTable);
await fkTable.value!.syncTable(dbTable);
await queryTable.value!.syncTable(dbTable);
}
// update-begin--author:liaozhiyang---date:20260414---forQQYUN-15128
const SYS_BUILT_IN_FIELDS = ['create_by', 'create_time', 'update_by', 'update_time', 'sys_org_code'];
// update-end--author:liaozhiyang---date:20260414---forQQYUN-15128
/** 当新增了的时候应立即同步 */
async function onTableAdded() {
// update-begin--author:liaozhiyang---date:20260414---forQQYUN-15128退
const dbJVxeRef = tables.dbTable.value?.tableRef;
if (dbJVxeRef) {
const fullData = dbJVxeRef.getXTable().internalData.tableFullData;
const sysIndex = fullData.findIndex((row) => SYS_BUILT_IN_FIELDS.includes(row.dbFieldName));
const lastIndex = fullData.length - 1;
//
if (sysIndex !== -1 && lastIndex > sysIndex && !SYS_BUILT_IN_FIELDS.includes(fullData[lastIndex]?.dbFieldName)) {
const newRowData = { ...fullData[lastIndex] };
// dbTable
await dbJVxeRef.rowResort(lastIndex, sysIndex);
// tables syncAllTableNow
const { pageTable, checkTable, fkTable, queryTable } = tables;
for (const t of [pageTable, checkTable, fkTable, queryTable]) {
const jvxeRef = t.value?.tableRef;
if (!jvxeRef) continue;
const tFullData = jvxeRef.getXTable().internalData.tableFullData;
const tSysIndex = tFullData.findIndex((row) => SYS_BUILT_IN_FIELDS.includes(row.dbFieldName));
if (tSysIndex !== -1) {
jvxeRef.insertRows(newRowData, tSysIndex);
} else {
jvxeRef.addRows(newRowData);
}
}
return;
}
}
// update-end--author:liaozhiyang---date:20260414---forQQYUN-15128退
syncAllTableNow();
}
/** 当删除的时候也应立即同步 */
function onTableRemoved() {
syncAllTableNow();
}
/** 当拖动后立即同步 */
function onTableDragged(event) {
let { oldIndex, newIndex } = event;
syncAllOrderNumNow(oldIndex, newIndex);
}
/** 当插入后立即同步 */
async function onTableInserted(event) {
let { insertIndex, row } = event;
let { pageTable, checkTable, fkTable, queryTable } = tables;
pageTable.value!.tableRef!.insertRows(row, insertIndex);
checkTable.value!.tableRef!.insertRows(row, insertIndex);
fkTable.value!.tableRef!.insertRows(row, insertIndex);
queryTable.value!.tableRef!.insertRows(row, insertIndex);
}
/** 立即同步所有的表的排序顺序 */
function syncAllOrderNumNow(oldIndex: number, newIndex: number) {
let { pageTable, checkTable, fkTable, queryTable } = tables;
pageTable.value!.tableRef!.rowResort(oldIndex, newIndex);
checkTable.value!.tableRef!.rowResort(oldIndex, newIndex);
fkTable.value!.tableRef!.rowResort(oldIndex, newIndex);
queryTable.value!.tableRef!.rowResort(oldIndex, newIndex);
}
/** 当value变化时同步 date */
function onTableSyncDbType(event) {
tables.pageTable.value!.syncFieldShowType(event.row);
}
// update-begin--author:liaozhiyang---date:20240313---forQQYUN-8485
/** 当dbIsPersist同步数据库 value变化时同步 查询去掉勾选 */
function onTableSyncDbIsPersist(event) {
tables.pageTable.value!.syncIsQuery(event.row);
}
// update-end--author:liaozhiyang---date:20240313---forQQYUN-8485
// update-begin--author:liaozhiyang---date:20240313---forQQYUN-8485
/** 当dbIsNull(不允许空值) value变化时同步 校验必填 */
function onTableSyncDbIsNull(event) {
tables.checkTable.value!.syncFieldMustInput(event.row);
}
// update-end--author:liaozhiyang---date:20240313---forQQYUN-8485
function onTableQuery(id) {
tables.pageTable.value!.enableQuery(id);
}
/** 添加树字段 */
function addTreeNeedField() {
if (!treeFieldAdded) {
let { dbTable, pageTable, checkTable } = tables;
let treeFields = useTreeNeedFields();
treeFields = treeFields.filter((item: any) => {
let nameList = dbTable.value!.tableRef!.getTableData().map((o) => o.dbFieldName);
return !nameList.includes(item.dbFieldName);
});
treeFieldIds = [];
treeFields.forEach((newData: any) => {
let uuidTemp = buildUUID() + '__tempId';
treeFieldIds.push(uuidTemp);
newData.id = uuidTemp;
});
dbTable.value!.tableRef!.addRows(treeFields, { setActive: false });
pageTable.value!.tableRef!.addRows(treeFields, { setActive: false });
checkTable.value!.tableRef!.addRows(treeFields, { setActive: false });
nextTick(() => syncAllTableNow());
treeFieldAdded = true;
}
nextTick(() => {
if (cgformHeadFormRef.value) {
cgformHeadFormRef.value.formModel.treeIdField = 'has_child';
cgformHeadFormRef.value.formModel.treeParentIdField = 'pid';
}
});
}
/** 删除树字段 */
function deleteTreeNeedField() {
if (treeFieldIds && treeFieldIds.length > 0) {
let { dbTable } = tables;
dbTable.value!.tableDeleteLines(treeFieldIds);
treeFieldIds = [];
treeFieldAdded = false;
}
}
//
function validateAll() {
let options = {};
return new Promise((resolve, reject) => {
//
cgformHeadFormRef.value!.validate().then(
(values) => resolve({ values }),
(errMsg) => {
if (errMsg) $message.warning(errMsg);
reject(VALIDATE_FAILED);
}
);
})
.then((result) => {
Object.assign(options, result);
return validateTableFields();
})
.then((allTableData) => {
Object.assign(options, allTableData);
let formData = classifyIntoFormData(options);
return validateForeignKey(formData);
})
.catch((e) => {
if (e === VALIDATE_FAILED || e?.code === VALIDATE_FAILED) {
// validate()
} else {
// update-begin--author:liaozhiyang---date:20231226---forQQYUN-7503
e?.msg ? $message.warning(e.msg) : console.error(e);
// update-end--author:liaozhiyang---date:20231226---forQQYUN-7503
}
return Promise.reject(null);
});
}
/** 验证并获取所有表的数据 */
function validateTableFields() {
return new Promise(async (resolve, reject) => {
let tableKeys = Object.keys(tables);
let allTableData: any = {};
for (let i = 0; i < tableKeys.length; i++) {
let key = tableKeys[i];
let table = tables[key];
try {
allTableData[key] = await table.value!.validateData(key);
} catch (e: any) {
if (e.code === VALIDATE_FAILED) {
// tab
activeKey.value = e.activeKey;
} else {
console.error(e);
}
reject(e);
return;
}
}
resolve(allTableData);
});
}
/** 将所有表的数据整理整合成后台识别的formData */
function classifyIntoFormData(options) {
//
let formData = {
head: {} as Recordable,
fields: [] as any[],
indexs: [] as any[],
deleteFieldIds: [] as any[],
deleteIndexIds: [] as any[],
};
formData.head = Object.assign(model, options.values);
// update-begin--author:liaozhiyang---date:20260401---forQQYUN-14949onlinevxetable
formData.head.formCategory = 'temp';
formData.head.idType = 'UUID';
// update-end--author:liaozhiyang---date:20260401---forQQYUN-14949onlinevxetable
// onlineJSON
const extConfigJson = { ...cgformHeadFormRef.value?.getExtConfigJson() };
formData.head.isDesForm = extConfigJson.isDesForm;
formData.head.desFormCode = extConfigJson.desFormCode;
delete extConfigJson.isDesForm;
delete extConfigJson.desFormCode;
formData.head.extConfigJson = JSON.stringify(extConfigJson);
// fields
options.dbTable.tableData.forEach((item, index) => {
// ID dbTable ID
let rowId = item.id;
let fields = Object.assign({}, item);
let pageTable = options.pageTable.tableData[index];
fields = Object.assign(pageTable, fields);
let checkTable = options.checkTable.tableData[index];
fields = Object.assign(checkTable, fields);
let fkTable = options.fkTable.tableData[index];
fields = Object.assign(fkTable, fields);
let queryTable = options.queryTable.tableData[index];
fields = Object.assign(queryTable, fields);
// dbTable id
if (rowId == null || rowId === '') {
delete fields.id;
} else {
fields.id = rowId;
}
// ID
let tempIds = ([] as string[]).concat(fieldTempIds, treeFieldIds);
if (tempIds.includes(fields.id)) {
delete fields.id;
}
formData.fields.push(fields);
});
formData.deleteFieldIds = options.dbTable.deleteIds;
// index
formData.indexs = options.idxTable.tableData;
formData.deleteIndexIds = options.idxTable.deleteIds;
return formData;
}
/** 外键配置校验 只能配置一个 */
function validateForeignKey(formData) {
// 1.
// 2.
// 3.,
return new Promise((resolve, reject) => {
let fields = formData.fields;
let saved = true;
if (fields && fields.length > 0) {
let hasForeignKey = 0;
for (let i = 0; i < fields.length; i++) {
if (fields[i].mainField || fields[i].mainTable) {
hasForeignKey += 1;
}
if (hasForeignKey > 1) {
saved = false;
break;
}
}
}
if (saved) {
resolve(formData);
} else {
reject({
code: -1,
msg: '外键只允许配置一个!',
error: VALIDATE_FAILED,
});
}
});
}
//
function onSubmit() {
confirmLoading.value = true;
validateAll()
.then(
async (formData: any) => {
//
if (formData.fields && formData.fields.length > 0) {
for (let field of formData.fields) {
field.dbFieldName = field.dbFieldName.toLowerCase().trim();
}
}
if (formData.head?.tableName) {
formData.head.tableName = formData.head.tableName.toLowerCase().trim();
}
//
await formApi.doSaveOrUpdate(formData, isUpdate.value);
refreshCacheTableName(oldTableName.value, formData.head['tableName']);
emit('success');
// JVxeTable
sleep(1, () => onCancel());
},
(e) => {
console.error(e);
}
)
.finally(() => {
confirmLoading.value = false;
});
}
//update-begin-author:taoyan date:2022-8-15 for: VUEN-1891 online
async function onExtConfigSaved(values) {
if (isUpdate.value) {
const params = {
id: model.id,
extConfigJson: JSON.stringify(toRaw(values)),
};
await formApi.editHead(params);
emit('success');
}
}
//update-end-author:taoyan date:2022-8-15 for: VUEN-1891 online
function onCancel() {
hideTabs.value = true;
// JVxeTable
sleep(1, () => closeModal());
}
/**
* 2024-07-17
* liaozhiyang
* TV360X-829根据字典名称和字段备注快速定位到行
* */
const handlePositioning = () => {
const val = positioning.value.trim();
if (val.length) {
const jVxe_instance = tables[activeKey.value].value.tableRef;
const vxe_instance = jVxe_instance.getXTable();
const fullData = vxe_instance.getTableData().fullData;
//
const preciseIndex = fullData.findIndex((item) => val === item.dbFieldName || val === item.dbFieldTxt);
let index = -1;
if (preciseIndex == -1) {
//
const dimIndex = fullData.findIndex((item) => item.dbFieldName.includes(positioning.value) || item.dbFieldTxt.includes(positioning.value));
index = dimIndex;
} else {
index = preciseIndex;
}
if(index != -1) {
const row = fullData[index];
vxe_instance.scrollToRow(row).then(() => {
const { refTableBody } = vxe_instance.getRefMaps();
const tableBody = refTableBody.value;
const bodyElem = tableBody ? tableBody.$el : null;
if (bodyElem) {
const trElem = bodyElem.querySelector(`[rowid="${vxe_instance.getRowid(row)}"]`);
if (trElem) {
trElem.classList.add('customHighlight');
setTimeout(() => {
trElem?.classList.remove('customHighlight');
}, 1e3);
}
}
// update-begin--author:liaozhiyang---date:20260330---forQQYUN-15058
//
const spinEl = spinRef.value?.$el || spinRef.value;
const scrollContainer = spinEl?.closest('.scrollbar__wrap');
if (scrollContainer) {
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}
// update-end--author:liaozhiyang---date:20260330---forQQYUN-15058
});
} else {
$message.warning('没搜到相关字段名称或字段备注~');
}
} else {
$message.warning('请输入字段名称或字段备注~');
}
};
function getRefPromise(componentRef) {
return new Promise((resolve) => {
function next() {
let ref = componentRef.value;
if (ref?.tableRef?.getXTable) {
resolve(ref);
} else {
requestAnimationFrame(next);
}
}
next();
});
}
return {
...tables,
modalRef,
onlFormContentRef,
onlFormContentFormRef,
onlFormContentFormHeight,
spinRef,
title,
confirmLoading,
tableLoading,
activeKey,
onCancel,
formAction,
hideTabs,
onSubmit,
onTabsChange,
onTableAdded,
onTableRemoved,
onTableDragged,
onTableInserted,
onTableSyncDbType,
onTableQuery,
onExtConfigSaved,
registerModal,
// hook OnlineTest
aiTestMode,
aiTestTable,
aiTableList,
initVirtualData,
onTableSyncDbIsPersist,
onTableSyncDbIsNull,
isUpdate,
positioning,
handlePositioning,
oldTableName,
onIsTreeChange,
//
cgformHeadFormRef,
onTableTypeChange,
onRelationTypeChange,
};
},
});
</script>
<style lang="less" scoped>
.onlForm-content {
height: 100%;
}
// update-begin--author:liaozhiyang---date:20240717---forTV360X-829
.footer-area {
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
.leftArea {
display: flex;
> * {
&:not(:first-child) {
margin-left: 16px;
}
}
}
}
.positioning-area {
width: 280px;
display: flex;
> :first-child {
margin-right: 8px;
}
}
:deep(.vxe-table) {
.vxe-body--row.customHighlight {
background-color: var(--vxe-ui-table-row-hover-background-color);
}
}
// update-begin--author:liaozhiyang---date:20240717---forTV360X-829
</style>
<style>
.onlForm-config-modal {
.scroll-container .scrollbar__wrap {
margin-bottom: 0 !important;
}
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<BasicModal @register="registerModal" :width="1200" :defaultFullscreen="false" :canFullscreen="false">
<template #title> <info-circle-two-tone /> 代码生成结果 </template>
<div :style="divStyle">
<p>
<template v-for="item in codeList"> {{ item }}<br /> </template>
</p>
</div>
<template #footer>
<a-button @click="handleClose">关闭</a-button>
<a-button type="primary" ghost @click="handleView">在线预览</a-button>
<a-button type="primary" @click="onDownloadGenerateCode" :loading="loading">下载到本地</a-button>
</template>
</BasicModal>
<code-file-view-modal @register="registerCodeViewModal" @download="onDownloadGenerateCode" @close="handleClose"></code-file-view-modal>
</template>
<script>
/**
* online代码生成后弹出的modal 文件列表
*/
import { ref, reactive, computed, nextTick, defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicForm, useForm } from '/@/components/Form';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { InfoCircleTwoTone } from '@ant-design/icons-vue';
import CodeFileViewModal from './CodeFileViewModal.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { downloadByData } from '/@/utils/file/download';
export default {
name: 'CodeFileListModal',
components: {
BasicModal,
InfoCircleTwoTone,
CodeFileViewModal,
},
emits: ['register'],
setup() {
const { createMessage: $message } = useMessage();
const codeList = ref([]);
const height = window.innerHeight - 150;
const divStyle = reactive({
overflowY: 'auto',
maxHeight: height + 'px',
});
const loading = ref(false);
const tableName = ref('')
const pathKey = ref('')
const [registerModal, { closeModal }] = useModalInner(async (data) => {
codeList.value = data.codeList;
tableName.value = data.tableName;
pathKey.value = data.pathKey;
});
function handleClose() {
closeModal();
}
function onDownloadGenerateCode() {
//update-begin-author:taoyan date:2022-6-27 for: VUEN-1433vue3
let codeFileList = codeList.value;
if (!codeFileList || codeFileList.length == 0) {
$message.warning('无代码!');
return;
}
let temp = codeFileList.join(',');
//console.log(temp);
//update-end-author:taoyan date:2022-6-27 for: VUEN-1433vue3
return defHttp
.post(
{
url: '/online/cgform/api/downGenerateCode',
params: {
fileList: encodeURI(temp),
pathKey: pathKey.value
},
responseType: 'blob',
},
{ isTransformResponse: false }
)
.then((data) => {
if (!data || data.size == 0) {
$message.warning('导出代码失败!');
return;
}
let fileName = '导到生成代码_' + tableName.value + '_' + new Date().getTime() + '.zip';
downloadByData(data, fileName, 'application/zip');
});
}
const [registerCodeViewModal, { openModal }] = useModal();
function handleView() {
let temp = codeList.value;
openModal(true, {
codeList: temp,
pathKey: pathKey.value
});
}
return {
registerModal,
registerCodeViewModal,
divStyle,
codeList,
onDownloadGenerateCode,
handleClose,
handleView,
loading,
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,378 @@
<template>
<BasicModal :height="modalHeight" @register="registerModal" okText="" cancelText="关闭" :width="1200" :defaultFullscreen="true" :canFullscreen="false" @ok="onDownloadGenerateCode" :wrapClassName="prefixCls">
<template #title> <info-circle-two-tone /> 代码在线预览 </template>
<div>
<a-row class="code-gen">
<div class="left" style="border-right: 1px solid #eee" :style="{width:`calc(${defaultLeftWidthRate}% - 2px)`}">
<div :style="{ height: height + 'px', overflowY: 'auto' }">
<a-directory-tree v-if="treeData.length" :defaultExpandAll="true" :tree-data="treeData" @select="showCodeContent"> </a-directory-tree>
</div>
</div>
<div class="resize"/>
<div class="right" :style="{width:`calc(100% - ${defaultLeftWidthRate}% - 2px)`}">
<JCodeEditor
v-if="activeCodeContent"
v-model:value="activeCodeContent"
theme="idea"
:language="language"
:fullScreen="false"
:lineNumbers="true"
:height="height + 'px'"
:disabled="true"
:language-change="true">
</JCodeEditor>
<a-empty v-else style="margin-top: 50px" description="请选择左侧文件,显示详细代码" />
</div>
</a-row>
</div>
<template #footer>
<a-button @click="handleClose">关闭</a-button>
<a-button type="primary" @click="onDownloadGenerateCode">下载到本地</a-button>
</template>
</BasicModal>
</template>
<script lang="ts">
/**
* online代码生成后支持在线预览
*/
import {ref, defineComponent, reactive, onMounted} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { InfoCircleTwoTone } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { JCodeEditor } from '/@/components/Form';
import 'codemirror/theme/idea.css';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'CodeFileViewModal',
components: {
BasicModal,
InfoCircleTwoTone,
JCodeEditor,
},
emits: ['download', 'register', 'close'],
setup(_p, { emit }) {
const codeList = ref([]);
const pathKey = ref('');
const treeData = ref<any[]>([]);
const expandStatus = ref(false);
const height = window.innerHeight - 142;
const language = ref('java');
const activeCodeContent = ref('');
let codeMap = reactive({});
const [registerModal, { closeModal }] = useModalInner(async (data) => {
codeMap = reactive({});
activeCodeContent.value = '';
codeList.value = data.codeList;
pathKey.value = data.pathKey;
getTreeData();
dragDiv();
expandStatus.value = true;
});
const { prefixCls } = useDesign('online-codeFileViewModal');
function getTreeData() {
let list = getPlainList();
let root = list[0];
assembleTree(root, list);
let treeList: any[] = [];
const getFinalTreeData = function (root) {
if (root.children) {
let children = root.children;
if (children.length == 1) {
getFinalTreeData(children[0]);
} else if (children.length > 1) {
treeList.push(root);
}
}
};
getFinalTreeData(root);
console.log(123, treeList)
treeData.value = treeList;
setTimeout(()=>{
loadFirstFileContent(root)
}, 300)
}
/**
* 默认加载第一个文件
* @param root
*/
async function loadFirstFileContent(root){
const getFirstFile = function(temp){
if(temp.isLeaf === true){
return temp;
}else{
if (temp.children) {
return getFirstFile(temp.children[0])
}
}
}
let node = getFirstFile(root);
if(node && node.isLeaf === true){
let path = node.path;
if (!codeMap[path]) {
await loadCode(path);
}
language.value = getCodeLanguage(path);
activeCodeContent.value = codeMap[path];
}
}
function assembleTree(root, list) {
for (let item of list) {
if (root.key == item.pid) {
let children = root.children;
if (!children) {
root.children = [];
}
root.children.push(item);
assembleTree(item, list);
}
}
}
function getAbsolutePath(arr, index){
let i=0;
let str = ''
while(i<=index){
str+=arr[i];
i++;
}
return str;
}
function getPlainList() {
// title key pid
let list: any[] = [];
let list2: any[] = [];
let arr: any[] = codeList.value;
for (let item of arr) {
let temp = item.replace(new RegExp('\\\\', 'g'), '/').replace('生成成功:', '').trim();
if (temp) {
let arr2 = temp.split('/');
for (let i = 0; i < arr2.length; i++) {
let a = arr2[i];
let id = getAbsolutePath(arr2, i)
// let str = getAbsolutePath(arr2, i)
if (a) {
let item = {
title: a,
key: id,
};
if (a == 0) {
} else {
let lastKey = getAbsolutePath(arr2, i-1)
//arr2[i - 1] + (i - 1);
if (lastKey) {
item['pid'] = lastKey;
} else {
}
}
if (i == arr2.length - 1) {
//
item['isLeaf'] = true;
item['path'] = temp;
}
if (list2.indexOf(id) < 0 || i == arr2.length - 1) {
list.push(item);
list2.push(id);
}
}
}
}
}
return list;
}
function handleClose() {
closeModal();
emit('close')
}
function onDownloadGenerateCode() {
emit('download');
}
function getCodeLanguage(path) {
if (path.endsWith('xml')) {
return 'application/xml';
}
if (path.endsWith('sql')) {
return 'text/x-sql';
}
if (path.endsWith('vue')) {
return 'text/x-vue';
}
if (path.endsWith('ts')) {
return 'text/typescript';
} else {
return 'text/x-java';
}
}
async function showCodeContent(_selectedKeys, e) {
let node = e.node.dataRef;
if (node.isLeaf) {
let path = node.path;
if (!codeMap[path]) {
await loadCode(path);
}
language.value = getCodeLanguage(path);
activeCodeContent.value = codeMap[path];
}
}
function loadCode(path) {
return new Promise((resolve) => {
let params = {
path: encodeURI(path),
pathKey: pathKey.value
};
defHttp.get({ url: '/online/cgform/api/codeView', params }, { isTransformResponse: false }).then((data) => {
if (!data || data.size === 0) {
message.warning('文件下载失败');
return;
}else{
if (data.message) {
message.warning(data.message);
return;
}
}
let blob = new Blob([data]);
let reader = new FileReader();
reader.readAsText(blob, 'utf8');
reader.onload = function () {
let content = this.result;
codeMap[path] = content;
resolve(1);
};
});
});
}
//update-begin---author:wangshuai---date:2025-08-25---for:QQYUN-13519---
//
const defaultLeftWidthRate = ref(24)
//
const leftMin = ref<number>(14);
//
const rightMin = ref<number>(30);
/**
* div拖拽事件
*
*/
function dragDiv(){
let resize:any = document.getElementsByClassName('resize');
let box:any = document.getElementsByClassName('code-gen');
let left:any = document.getElementsByClassName('left');
for (let i = 0; i < resize.length; i++) {
//
resize[i].onmousedown = function (e: any) {
//div
let boxWidth = box[i].offsetWidth;
//
let leftWidth = left[i].offsetWidth;
//
let startX = e.clientX;
//
document.onmousemove = function (e: any) {
//
let endX = e.clientX;
//
let distX = Math.abs(endX - startX);
//
if (endX > startX) {
let moveLate = parseFloat(((leftWidth + distX) / boxWidth * 100).toFixed(4));
let right = parseFloat('100') - moveLate;
//
if (right > rightMin.value) {
defaultLeftWidthRate.value = moveLate;
}
}
//
if (endX < startX) {
let moveLate = parseFloat(((leftWidth - distX) / boxWidth * 100).toFixed(4));
//
if (moveLate > leftMin.value) {
defaultLeftWidthRate.value = moveLate;
}
}
}
//
document.onmouseup = function (e: any) {
document.onmousemove = null;
document.onmouseup = null;
//ReleaseCapture()
resize[i].releaseCapture && resize[i].releaseCapture();
}
//线
resize[i].setCapture && resize[i].setCapture();
return false;
}
}
}
//update-end---author:wangshuai---date:2025-08-25---for:QQYUN-13519---
return {
registerModal,
codeList,
onDownloadGenerateCode,
handleClose,
treeData,
showCodeContent,
activeCodeContent,
expandStatus,
height,
language,
prefixCls,
modalHeight:1000,
defaultLeftWidthRate
};
},
});
</script>
<style lang="less">
// update-begin--author:liaozhiyang---date:20240617---forTV360X-227线
@prefix-cls: ~'@{namespace}-online-codeFileViewModal';
.@{prefix-cls} {
.scrollbar__wrap {
margin-bottom: 0 !important;
}
.ant-modal-footer {
padding-top: 32px;
}
//update-begin---author:wangshuai---date:2025-08-25---for:QQYUN-13519---
.code-gen .left {
position: relative;
width: calc(24% - 2px);
height: 100%;
overflow-x: auto;
}
.code-gen .resize {
position: relative;
width: 4px;
color: #f7f7f7;
background: #f7f7f7;
cursor: w-resize;
}
.code-gen .right {
position: relative;
width: calc(76% - 2px);
height: 100%;
overflow-x: auto;
}
.ant-tree-node-content-wrapper{
display: flex;
}
//update-end---author:wangshuai---date:2025-08-25---for:QQYUN-13519---
}
// update-end--author:liaozhiyang---date:20240617---forTV360X-227线
</style>

View File

@ -0,0 +1,345 @@
<template>
<BasicModal
@register="registerModal"
:title="title"
:width="modalWidth"
:confirmLoading="confirmLoading"
okText="开始生成"
cancelText="取消"
@ok="onSubmit"
@cancel="onCancel"
:wrapClassName="wrapClassName"
>
<a-spin :spinning="confirmLoading">
<BasicForm @register="registerForm">
<!-- update-begin--author:liaozhiyang---date:20240612---forTV360X-1057代码生成页面代码鼠标移入给说明 -->
<template #pageCode="{ model, field }">
<a-radio-group v-model:value="model[field]">
<a-tooltip placement="top">
<template #title>
<span>深度封装表单用户只需定义字段json即可渲染表单优点简单便捷缺点扩展有难度</span>
</template>
<a-radio value="vue3">封装表单(BasicForm)</a-radio>
</a-tooltip>
<a-tooltip placement="top">
<template #title>
<span>antd的原生表单所有字段都需要硬编码缺点编码繁琐优点扩展容易</span>
</template>
<a-radio value="vue3Native" v-if="!(model.jspMode == 'innerTable' || model.jspMode == 'tab')">原生表单(a-form)</a-radio>
</a-tooltip>
</a-radio-group>
</template>
<!-- update-end--author:liaozhiyang---date:20240612---forTV360X-1057代码生成页面代码鼠标移入给说明 -->
</BasicForm>
<a-card v-if="showSubTable" title="子表信息" size="small">
<JVxeTable ref="subTableRef" rowNumber :maxHeight="580" v-bind="subTable" />
</a-card>
</a-spin>
</BasicModal>
<FileSelectModal @register="registerFileSelectModal" @select="onFileSelect" />
<!-- 生成代码文件后弹窗显示 -->
<code-file-list-modal @register="registerCodeFileListModal"></code-file-list-modal>
</template>
<script lang="ts">
import { ref, reactive, computed, nextTick, defineComponent,h } from 'vue';
import { defHttp } from '/@/utils/http/axios';
// TODO import { DisabledAuthFilterMixin } from '@/mixins/DisabledAuthFilterMixin'
import { BasicForm, useForm } from '/@/components/Form';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { JVxeTypes, JVxeColumn, JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types';
import { useCodeGeneratorFormSchemas } from '../hooks/useSchemas';
import { underLine2CamelCase } from '/@/utils/common/compUtils';
import CodeFileListModal from './CodeFileListModal.vue'
import FileSelectModal from './FileSelectModal.vue';
import {message} from "ant-design-vue";
import { useDesign } from '/@/hooks/web/useDesign';
import { createLocalStorage } from '/@/utils/cache';
import { useMessage } from '@/hooks/web/useMessage';
const $ls = createLocalStorage();
const { notification } = useMessage();
enum Api {
tableInfo = '/online/cgform/head/tableInfo',
codeGenerate = '/online/cgform/api/codeGenerate',
downGenerateCode = '/online/cgform/api/downGenerateCode',
}
export default defineComponent({
name: 'CodeGenerator',
components: { BasicForm, BasicModal, FileSelectModal, CodeFileListModal },
emits: ['register'],
setup(props) {
const JEECG_ONL_PROJECT_PATH = 'JEECG_ONL_PROJECT_PATH';
const JEECG_ONL_PROJECT_NAME = 'JEECG_ONL_PROJECT_NAME';
const single = ref(true);
const subTableRef = ref<JVxeTableInstance>();
const modalWidth = computed(() => (single.value ? 800 : 1200));
const title = ref('代码生成');
const confirmLoading = ref(false);
const { prefixCls } = useDesign('code-generator-modal');
const wrapClassName = prefixCls;
const code = ref('');
const metaModel = reactive({
projectPath: '',
packageStyle: 'service',
jspMode: '',
jformType: '1',
tableName_tmp: '',
ftlDescription: '',
entityName: '',
codeTypes: 'controller,service,dao,mapper,entity,vue',
});
const model = reactive<Recordable>({});
const jspModeOptions = ref<any[]>([]);
//
const subTable = reactive({
dataSource: [] as Recordable[],
columns: [
{
title: '子表名',
key: 'tableName',
type: JVxeTypes.input,
disabled: true,
validateRules: [{ required: true, message: '请输入${title}' }],
},
{
title: '子表实体',
key: 'entityName',
type: JVxeTypes.input,
validateRules: [{ required: true, message: '请输入${title}' }],
},
{
title: '功能说明',
key: 'ftlDescription',
type: JVxeTypes.input,
validateRules: [{ required: true, message: '请输入${title}' }],
},
] as JVxeColumn[],
});
const showSubTable = computed<boolean>(() => subTable.dataSource.length > 0);
const { formSchemas } = useCodeGeneratorFormSchemas(
props,
{
onProjectPathChange,
onProjectPathSearch,
jspModeOptions,
},
single
);
//
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
schemas: formSchemas,
showActionButtonGroup: false,
labelAlign: 'right',
});
const [registerModal, { closeModal }] = useModalInner(async (data) => {
await resetFields();
// ID
code.value = data.code;
confirmLoading.value = false;
subTable.dataSource = [];
jspModeOptions.value = [];
getStoreProjectPath();
Object.assign(model, metaModel);
loadData();
});
async function loadData() {
let { main, sub, jspModeList, projectPath } = await defHttp.get({
url: Api.tableInfo,
params: { code: code.value },
});
//update-begin-author:taoyan date:2022-5-17 for: vue3 jvxe
let jspModeListForVue3: any[] = [];
for (let mode of jspModeList) {
const { code, note } = mode;
if (code == 'many') {
//
} else {
jspModeListForVue3.push({
label: note,
value: code,
});
}
}
jspModeOptions.value = jspModeListForVue3;
if (main.isTree == 'Y') {
model.jspMode = 'tree';
} else {
//
if (jspModeListForVue3.find(item => item.value === main.themeTemplate)) {
model.jspMode = main.themeTemplate
} else {
//
model.jspMode = jspModeListForVue3[0].value;
}
}
//update-end-author:taoyan date:2022-5-17 for: vue3 jvxe
single.value = main.tableType == 1;
title.value = '代码生成【' + main.tableName + '】';
if (!model.projectPath) {
model.projectPath = projectPath;
window.localStorage.setItem(JEECG_ONL_PROJECT_PATH, projectPath);
}
// update-begin--author:liaozhiyang---date:20240611---forTV360X-1054
const projectName = localStorage.getItem(JEECG_ONL_PROJECT_NAME);
if (projectName) {
model.entityPackage = projectName;
}
// update-end--author:liaozhiyang---date:20240611---forTV360X-1054
model.jformType = main.tableType + '';
model.tableName_tmp = main.tableName;
model.ftlDescription = main.tableTxt;
let entityNameTemp = underLine2CamelCase(main.tableName);
model.entityName = entityNameTemp.substring(0, 1).toUpperCase() + entityNameTemp.substring(1);
await nextTick();
setFieldsValue(model);
if (sub && sub.length > 0) {
subTable.dataSource = sub.map((item) => ({
tableName: item.tableName,
entityName: getCamelCase(item.tableName),
ftlDescription: item.tableTxt,
}));
}
}
//
const [registerCodeFileListModal, {openModal: openCodeFileListModal}] = useModal();
async function onSubmit() {
try {
const values = await validate();
let params = Object.assign({}, values, { code: code.value, tableName: values.tableName_tmp });
if (showSubTable.value) {
let errMap = await subTableRef.value!.validateTable();
if (errMap) {
return;
}
params.subList = subTableRef.value!.getTableData();
}
confirmLoading.value = true;
// let codeList = await defHttp.post({ url: Api.codeGenerate, params });
let res:any = await codeGen(params);
//
openCodeFileListModal(true, {
codeList: res.codeList,
pathKey: res.pathKey,
tableName: values.tableName_tmp
});
//-----------------------------------------------------------------------------------------
// vue310
const hasViewPath = res.codeList.some(
s => s.includes('src/views/') || s.includes('src\\views\\')
);
if(hasViewPath){
$ls.set(
'code.genenrate.success.msg',
`表【${values.tableName_tmp}】代码生成成功前端代码已自动刷新Java后台需重启生效`,
15
);
}else{
notification.success({
message: `表【${values.tableName_tmp}】代码生成成功`,
description: h('div', [
'1. 前端代码请迁移到 VUE3项目', h('br'),
'2. 菜单SQL放到jeecg-system-start的flyway目录', h('br'),
'3. Java 后台需重启生效'
]),
duration: 5,
});
}
//-----------------------------------------------------------------------------------------
closeModal();
// update-begin--author:liaozhiyang---date:20240611---forTV360X-1054
localStorage.setItem(JEECG_ONL_PROJECT_NAME, values.entityPackage);
// update-end--author:liaozhiyang---date:20240611---forTV360X-1054
} catch (e) {
console.error(e);
} finally {
confirmLoading.value = false;
}
}
function codeGen(params){
return new Promise((resolve, reject) => {
defHttp.post({ url: Api.codeGenerate, params }, {isTransformResponse: false}).then(res=>{
if(res.success){
let codeList = res.result;
let pathKey = res.message;
resolve({
codeList,
pathKey
})
}else{
message.error(res.message);
reject(res.message)
}
})
})
}
function onCancel() {
closeModal();
}
//
const [registerFileSelectModal, fileSelectModal] = useModal();
function onProjectPathSearch() {
fileSelectModal.openModal(true, {});
}
function onFileSelect(url) {
window.localStorage.setItem(JEECG_ONL_PROJECT_PATH, url);
setFieldsValue({ projectPath: url });
}
function getCamelCase(val) {
let temp = underLine2CamelCase(val);
return temp.substring(0, 1).toUpperCase() + temp.substring(1);
}
function getStoreProjectPath() {
let path = window.localStorage.getItem(JEECG_ONL_PROJECT_PATH);
if (path) {
metaModel.projectPath = path;
}
}
function onProjectPathChange(e) {
if (e.target.value) window.localStorage.setItem(JEECG_ONL_PROJECT_PATH, e.target.value);
}
return {
title,
modalWidth,
confirmLoading,
subTable,
showSubTable,
onSubmit,
onCancel,
onFileSelect,
registerFileSelectModal,
subTableRef,
registerForm,
registerModal,
registerCodeFileListModal,
wrapClassName
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-code-generator-modal';
.@{prefix-cls} {
.jeecg-basic-form .ant-input {font-size: 14px;}
}
</style>

View File

@ -0,0 +1,205 @@
<template>
<BasicModal @register="registerModal" :height="modalHeight" :width="500" title="从数据库导入表单" :confirmLoading="confirmLoading" @cancel="handleCancel" :wrapClassName="wrapClassName">
<a-spin :spinning="confirmLoading">
<BasicTable @register="registerTable" :rowSelection="rowSelection" @tableRedo="queryTables">
<template #tableTitle>
<div>
导入表会排除配置前缀表
<a href="http://doc.jeecg.com/2043924" target="_blank"> 参考文档</a>
</div>
</template>
</BasicTable>
</a-spin>
<template #footer>
<a-button @click="handleCancel">关闭</a-button>
<a-button @click="handleTrans" type="primary" preIcon="ant-design:swap" :loading="confirmLoading || btnLoading"> 生成表单 </a-button>
</template>
</BasicModal>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicTable } from '/@/components/Table';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useListTable } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import { useDesign } from '/@/hooks/web/useDesign';
enum Api {
query = '/online/cgform/head/queryTables',
trans = '/online/cgform/head/transTables/',
}
export default defineComponent({
name: 'TransDb2Online',
components: { BasicModal, BasicTable },
emits: ['success', 'register'],
setup(_, { emit }) {
const { createMessage: $message } = useMessage();
const emptyText = ref('暂无数据');
const confirmLoading = ref(false);
const btnLoading = ref(false);
const metaSource = ref<Recordable[]>([]);
const dataSource = ref<Recordable[]>([]);
const { prefixCls } = useDesign('online-db-import-form-modal');
const wrapClassName = prefixCls;
const clientHeight = document.documentElement.clientHeight;
const modalH = clientHeight - 180;
const modalHeight = modalH > 690 ? 690 : modalH;
const [registerTable, { setPagination, getForm }, { rowSelection, selectedRowKeys }] = useListTable({
bordered: true,
columns: [{ title: '表名', align: 'left', dataIndex: 'id' }],
dataSource: dataSource,
maxHeight: clientHeight - 400,
locale: { emptyText: emptyText },
pagination: {
showQuickJumper: false,
showSizeChanger: false,
},
clickToRowSelect: true,
showIndexColumn: true,
showActionColumn: false,
formConfig: {
schemas: [
{
label: '表名',
field: 'tableName',
component: 'Input',
componentProps: {
style: { width: '100%' },
placeholder: '请输入表名以模糊筛选',
onChange: (e) => searchFilter(e.target.value),
},
disabledLabelWidth: true,
itemProps: {
labelCol: { sm: 0, md: 0 },
wrapperCol: { sm: 24, md: 20 },
},
},
],
baseColProps: { xs: 24, sm: 24, md: 24, lg: 24, xl: 24, xxl: 24 },
showActionButtonGroup: false,
},
});
const [registerModal, { closeModal }] = useModalInner(() => {
// form
getForm()?.resetFields();
btnLoading.value = false;
emptyText.value = '暂无数据';
selectedRowKeys.value = [];
queryTables();
});
function queryTables() {
confirmLoading.value = true;
return defHttp
.get(
{
url: Api.query,
},
{
errorMessageMode: 'none',
}
)
.then(
(result) => {
dataSource.value = result;
metaSource.value = [...result];
return result;
},
(e) => {
if (e.message == 'noadminauth') {
emptyText.value = '非admin用户无权限操作';
$message.warn(emptyText.value);
} else {
console.error(e);
}
dataSource.value = [];
metaSource.value = [];
}
)
.finally(() => {
confirmLoading.value = false;
});
}
function searchFilter(keyword) {
if (metaSource.value.length === 0) return;
if (!keyword) {
dataSource.value = [...metaSource.value];
} else {
dataSource.value = metaSource.value.filter((item) => item.id.toLowerCase().includes(keyword.toLowerCase()));
emptyText.value = dataSource.value.length === 0 ? '无筛选结果' : '暂无数据';
}
setPagination({ current: 1 });
}
function handleCancel() {
closeModal();
}
function handleTrans() {
if (!selectedRowKeys.value || selectedRowKeys.value.length == 0) {
$message.warning('请选择一张表');
return;
} else {
btnLoading.value = true;
let tbNames = selectedRowKeys.value.join(',');
defHttp
.post({url: Api.trans + tbNames}, {errorMessageMode: "modal"})
.then(() => {
closeModal();
})
.finally(() => {
btnLoading.value = false
emit('success')
});
}
}
return {
emptyText,
confirmLoading,
btnLoading,
metaSource,
handleTrans,
handleCancel,
queryTables,
registerModal,
registerTable,
rowSelection,
selectedRowKeys,
wrapClassName,
modalHeight,
};
},
});
</script>
<style lang="less">
// update-begin--author:liaozhiyang---date:20240110---forQQYUN-7838online
@prefix-cls: ~'@{namespace}-online-db-import-form-modal';
.@{prefix-cls} {
.ant-table-wrapper .ant-table-pagination.ant-pagination {
margin-bottom: 0;
}
.jeecg-basic-table-form-container {
padding-bottom: 0;
.ant-form {
padding: 0;
}
}
.jeecg-basic-table .ant-table-wrapper {
padding: 0;
}
.ant-form-item-label {width:40px;}
.ant-modal {top: 20px;}
}
// update-end--author:liaozhiyang---date:20240110---forQQYUN-7838online
</style>

View File

@ -0,0 +1,147 @@
<template>
<ConfigProvider :theme="{ token: { fontSize: 13 } }">
<BasicModal @register="registerModal" title="表单扩展配置项" :width="1000" @ok="handleOk" @cancel="handleCancel">
<BasicForm @register="registerForm" />
</BasicModal>
</ConfigProvider>
</template>
<script lang="ts">
import { ConfigProvider } from 'ant-design-vue';
import { nextTick, defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { useMessage } from '/@/hooks/web/useMessage';
import { useExtendConfigFormSchemas } from '../hooks/useSchemas';
export default defineComponent({
name: 'CgformExtConfigModel',
components: { BasicModal, BasicForm, ConfigProvider },
props: {
parentForm: {
type: Object,
required: true,
},
},
emits: ['register', 'ok'],
setup(props, { emit }) {
const { createMessage: $message } = useMessage();
const { formSchemas } = useExtendConfigFormSchemas(props, {
onIsDesformChange,
onJoinQueryChange,
onReportPrintShowChange,
onFormLabelLengthShow,
});
//
const [registerForm, { resetFields, setFieldsValue, getFieldsValue, clearValidate, validate }] = useForm({
schemas: formSchemas,
showActionButtonGroup: false,
labelAlign: 'right',
});
const [registerModal, { closeModal }] = useModalInner(async (data) => {
await resetFields();
await setFieldsValue(data.extConfigJson);
});
async function handleOk() {
await clearValidate();
await nextTick();
try {
const values = await validate();
emit('ok', values);
closeModal();
} catch (e) {}
}
function handleCancel() {
closeModal();
}
//
function onIsDesformChange(value) {
if (value === 'Y') {
let { themeTemplate } = props.parentForm.getFieldsValue(['themeTemplate']);
if ('erp' === themeTemplate) {
props.parentForm.setFieldsValue({ themeTemplate: 'normal' });
$message.warning('请注意erp风格不支持对接表单设计已自动改为默认风格');
}
} else {
clearValidate('desFormCode');
}
}
//
const defaultReportPrintUrl = `{{ window._CONFIG['domianURL'] }}/jmreport/view/{积木报表ID}`;
//
async function onReportPrintShowChange(value) {
let reportPrintUrl = getFieldsValue()['reportPrintUrl'];
// 0 =
if (value === 0) {
if (reportPrintUrl === defaultReportPrintUrl) {
await setFieldsValue({ reportPrintUrl: '' });
}
} else if (value === 1) {
if (reportPrintUrl === '') {
await setFieldsValue({ reportPrintUrl: defaultReportPrintUrl });
}
}
clearValidate('reportPrintUrl');
}
/**
* 2024-03-29
* liaozhiyang
* 表单统一label长度关闭时,formLabelLength置空
*/
async function onFormLabelLengthShow(value) {
if (value == 0) {
await setFieldsValue({ formLabelLength: null });
}
await clearValidate('formLabelLength');
}
//
function onJoinQueryChange(value) {
if (value === 1) {
let { themeTemplate, isTree, tableType } = props.parentForm.getFieldsValue(['themeTemplate', 'isTree', 'tableType']);
if ('erp' === themeTemplate) {
$message.warning('请注意erp风格不支持联合查询配置无效!');
setFieldsValue({ joinQuery: 0 });
}
if ('innerTable' === themeTemplate) {
$message.warning('请注意:内嵌风格不支持联合查询,配置无效!');
setFieldsValue({ joinQuery: 0 });
}
if (1 === tableType) {
$message.warning('请注意:单表不支持联合查询,配置无效!');
setFieldsValue({ joinQuery: 0 });
} else if (3 === tableType) {
$message.warning('请注意:当前表为附表,请在对应主表配置!');
setFieldsValue({ joinQuery: 0 });
} else if ('Y' === isTree) {
$message.warning('请注意:树形列表不支持联合查询,配置无效!');
setFieldsValue({ joinQuery: 0 });
}
}
}
return {
handleOk,
handleCancel,
registerModal,
registerForm,
};
},
});
</script>
<style scoped lang="less">
.onl-cgform-ext-config-form.ant-form-inline {
:deep(.ant-form-item) {
margin-bottom: 8px;
}
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<BasicModal @register="registerModal" title="选择目录" :width="500" @ok="onSubmit" @cancel="onCancel">
<a-spin :spinning="loading">
<div class="btnArea">
<a-button @click="hanldeRefresh">刷新</a-button>
</div>
<a-directory-tree v-if="directoryTreeShow" :treeData="treeData" :loadData="onLoadData" @select="onSelect" />
</a-spin>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicModal, useModalInner } from '/@/components/Modal';
enum Api {
fileTreeUrl = '/online/cgform/head/fileTree',
rootFileUrl = '/online/cgform/head/rootFile',
}
export default defineComponent({
name: 'FileSelectModal',
components: { BasicModal },
emits: ['select', 'register'],
setup(_, { emit }) {
const loading = ref(true);
const treeData = ref<any[]>([]);
const selectedKey = ref('');
const directoryTreeShow = ref(false);
const [registerModal, { closeModal }] = useModalInner(async () => {
selectedKey.value = '';
if (treeData.value.length === 0) {
loadRoot();
}
});
function onSubmit() {
emit('select', selectedKey.value);
closeModal();
}
function onCancel() {
closeModal();
}
async function loadRoot() {
loading.value = true;
treeData.value = await defHttp.get({ url: Api.rootFileUrl }).finally(() => {
loading.value = false;
directoryTreeShow.value = true;
});
}
async function onLoadData(treeNode) {
if (treeNode.dataRef.children) {
return;
}
let params = {
parentPath: treeNode.dataRef.key,
};
treeNode.dataRef.children = await defHttp.get({ url: Api.fileTreeUrl, params });
treeData.value = [...treeData.value];
}
function onSelect(selectedKeys) {
selectedKey.value = selectedKeys[0];
}
// update-begin--author:liaozhiyang---date:20231017---forQQYUN-6102
const hanldeRefresh = () => {
selectedKey.value = '';
directoryTreeShow.value = false;
loadRoot();
};
// update-begin--author:liaozhiyang---date:20231017---forQQYUN-6102
return { loading, treeData, onLoadData, onSelect, onSubmit, onCancel, registerModal, hanldeRefresh, directoryTreeShow };
},
});
</script>
<style lang="less" scoped>
.btnArea {
margin-bottom: 10px;
text-align: left;
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<BasicDrawer @register="registerDrawer" title="权限管理" :width="800" @close="onClose">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane tab="字段权限" key="field" forceRender>
<AuthFieldConfig :headId="headId" v-model:authFields="authFields" />
</a-tab-pane>
<template v-if="hasDataAuth">
<a-tab-pane tab="按钮权限" key="button" forceRender>
<AuthButtonConfig :headId="headId" :tableType="curTableType" />
</a-tab-pane>
<a-tab-pane tab="数据权限" key="data" forceRender>
<!-- 数据权限不需要实时刷新 故而此处传原表单ID -->
<AuthDataConfig :cgformId="cgformId" :authFields="authFields" />
</a-tab-pane>
</template>
</a-tabs>
</BasicDrawer>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import AuthFieldConfig from './manager/AuthFieldConfig.vue';
import AuthButtonConfig from './manager/AuthButtonConfig.vue';
import AuthDataConfig from './manager/AuthDataConfig.vue';
export default defineComponent({
name: 'AuthManagerDrawer',
components: {
BasicDrawer,
AuthFieldConfig,
AuthButtonConfig,
AuthDataConfig,
},
props: {
// 1 2 3
tableType: {
type: Number,
default: 1,
},
},
emits: ['register'],
setup(props) {
const cgformId = ref('');
const headId = ref('');
const authFields = ref([]);
const activeKey = ref('field');
const curTableType = ref(1);
const hasDataAuth = computed(() => props.tableType == 1 || props.tableType == 2);
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
cgformId.value = data.cgformId;
headId.value = cgformId.value + '?' + new Date().getTime();
activeKey.value = 'field';
// update-begin--author:liaozhiyang---date:20240520---forTV360X-187
curTableType.value = data.tableType;
// update-end--author:liaozhiyang---date:20240520---forTV360X-187
});
function onClose() {
closeDrawer();
}
return {
activeKey,
cgformId,
headId,
authFields,
hasDataAuth,
onClose,
registerDrawer,
curTableType,
};
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,232 @@
<template>
<BasicModal
title="Online权限授权"
:width="900"
:maskClosable="false"
defaultFullscreen
:okButtonProps="{ style: { display: 'none' } }"
cancelText="关闭"
@cancel="closeModal"
@register="registerModal"
@open-change="hanldeOpenChange"
>
<a-spin wrapperClassName="authsetting-container" :spinning="loading">
<a-row v-if="contentShow">
<a-col :span="12">
<a-tabs v-model:activeKey="authMode" @change="onAuthModeChange">
<a-tab-pane tab="角色授权" key="role" forceRender>
<LeftRole ref="roleRef" @select="onSelectRole" />
</a-tab-pane>
<a-tab-pane tab="部门授权" key="depart" forceRender>
<LeftDepart ref="departRef" @select="onSelectDepart" />
</a-tab-pane>
<a-tab-pane tab="人员授权" key="user" forceRender>
<LeftUser ref="userRef" @select="onSelectUser" />
</a-tab-pane>
</a-tabs>
</a-col>
<a-col :span="1"></a-col>
<a-col :span="11">
<a-tabs v-model:activeKey="activeKey" @change="onAuthTypeChange">
<a-tab-pane tab="字段权限" key="field" forceRender>
<AuthFieldTree class="authFieldTree" ref="fieldRef" :cgformId="cgformId" />
</a-tab-pane>
<template v-if="hasDataAuth">
<a-tab-pane tab="按钮权限" key="button" forceRender>
<AuthButtonTree class="authButtonTree" ref="buttonRef" :cgformId="cgformId" />
</a-tab-pane>
<a-tab-pane tab="数据权限" key="data" forceRender>
<AuthDataTree class="authDataTree" ref="dataRef" :cgformId="cgformId" />
</a-tab-pane>
</template>
</a-tabs>
</a-col>
</a-row>
</a-spin>
</BasicModal>
</template>
<script lang="ts">
import LeftRole from './setter/LeftRole.vue';
import LeftDepart from './setter/LeftDepart.vue';
import LeftUser from './setter/LeftUser.vue';
import AuthFieldTree from './setter/AuthFieldTree.vue';
import AuthButtonTree from './setter/AuthButtonTree.vue';
import AuthDataTree from './setter/AuthDataTree.vue';
import { ref, computed, nextTick, defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
export default defineComponent({
name: 'AuthSetterModal',
components: {
BasicModal,
LeftRole,
LeftDepart,
LeftUser,
AuthFieldTree,
AuthButtonTree,
AuthDataTree,
},
props: {
// 1 2 3
tableType: { type: Number, default: 1 },
},
setup(props) {
const cgformId = ref('');
const loading = ref(false);
const activeKey = ref('field');
const authMode = ref('role');
const refs = {
fieldRef: ref(),
buttonRef: ref(),
dataRef: ref(),
roleRef: ref(),
userRef: ref(),
departRef: ref(),
};
const activeRole = ref('');
const hasDataAuth = computed(() => props.tableType == 1 || props.tableType == 2);
const contentShow = ref(true);
//
const [registerModal, { closeModal }] = useModalInner((data) => {
activeKey.value = 'field';
// update-begin--author:liaozhiyang---date:20240520---forTV360X-147tab
authMode.value = 'role';
// update-end--author:liaozhiyang---date:20240520---forTV360X-147tab
// QQYUN-4285online
cgformId.value = data.cgformId;
reset();
});
function getActiveRef<T = any>(key = activeKey.value): T {
return refs[key + 'Ref']?.value;
}
async function reset() {
await nextTick();
clearLeftCurrentTabSelect();
getActiveRef().clear();
}
//
function onSelectRole(roleId) {
activeRole.value = roleId;
onAuthTypeChange(activeKey.value);
clearLeftOtherTabSelect();
}
//
function onSelectDepart(departid) {
activeRole.value = departid;
onAuthTypeChange(activeKey.value);
clearLeftOtherTabSelect();
}
//
function onSelectUser(userId) {
activeRole.value = userId;
onAuthTypeChange(activeKey.value);
clearLeftOtherTabSelect();
}
//
function clearLeftOtherTabSelect() {
if (authMode.value == 'role') {
refs.departRef.value.clearSelected();
refs.userRef.value.clearSelected();
} else if (authMode.value == 'depart') {
refs.roleRef.value.clearSelected();
refs.userRef.value.clearSelected();
} else if (authMode.value == 'user') {
refs.departRef.value.clearSelected();
refs.roleRef.value.clearSelected();
}
}
function clearLeftCurrentTabSelect() {
if (authMode.value == 'role') {
refs.roleRef.value.clearSelected();
} else if (authMode.value == 'depart') {
refs.departRef.value.clearSelected();
} else if (authMode.value == 'user') {
refs.userRef.value.clearSelected();
}
getActiveRef().clearChecked();
// update-begin--author:liaozhiyang---date:20231226---forQQYUN-7543
activeRole.value = '';
// update-end--author:liaozhiyang---date:20231226---forQQYUN-7543
}
//
async function onAuthTypeChange(key) {
// tab tab
await nextTick();
if (activeRole.value) {
getActiveRef(key).loadChecked(activeRole.value, authMode.value);
}
}
//
function onAuthModeChange() {
clearLeftCurrentTabSelect();
}
// update-begin--author:liaozhiyang---date:20240523---forTV360X-239
const hanldeOpenChange = (open: boolean) => {
contentShow.value = open;
};
// update-end--author:liaozhiyang---date:20240523---forTV360X-239
return {
...refs,
cgformId,
loading,
activeKey,
hasDataAuth,
authMode,
onAuthModeChange,
onAuthTypeChange,
closeModal,
onSelectRole,
onSelectDepart,
onSelectUser,
registerModal,
hanldeOpenChange,
contentShow,
};
},
});
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20231226---forQQYUN-7540
.authsetting-container {
height: 100%;
:deep(.ant-spin-container) {
height: 100%;
}
.ant-row,
.ant-col {
height: 100%;
}
.ant-tabs {
height: 100%;
:deep(.ant-tabs-content) {
height: 100%;
}
.ant-tabs-tabpane {
height: 100%;
.authFieldTree, .authButtonTree, .authDataTree {
height: 100%;
display: flex;
flex-direction: column;
:deep(.ant-tree) {
flex: 1;
min-height: 0;
overflow: auto;
}
}
}
}
}
// update-end--author:liaozhiyang---date:20231226---forQQYUN-7540
</style>

View File

@ -0,0 +1,81 @@
import { defHttp } from '/@/utils/http/axios';
export enum Api {
authField = '/online/cgform/api/authColumn',
authButton = '/online/cgform/api/authButton',
authData = '/online/cgform/api/authData',
authPage = '/online/cgform/api/authPage',
roleAuth = '/online/cgform/api/roleAuth',
saveButton = '/online/cgform/api/roleButtonAuth',
saveData = '/online/cgform/api/roleDataAuth',
validData = '/online/cgform/api/validAuthData',
saveField = '/online/cgform/api/roleColumnAuth',
batchAuthField = '/online/cgform/api/authColumn/batch',
}
//
export const authFieldLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authField}/${cgformId}`, params });
//
export const authFieldUpdateStatus = (params) => defHttp.put({ url: Api.authField, params });
//
export const authFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.authField, params });
//
export const batchAuthFieldUpdateStatus = (params) => defHttp.put({ url: Api.batchAuthField, params });
//
export const batchAuthFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.batchAuthField, params });
//
export const authButtonLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authButton}/${cgformId}`, params });
//
export const authButtonEnable = (params) => defHttp.post({ url: Api.authButton, params });
//
export const authButtonDisable = (id: string, params?) => defHttp.put({ url: `${Api.authButton}/${id}`, params });
//
export const authDataLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authData}/${cgformId}`, params });
//
export const authDataUpdateStatus = (params) => defHttp.put({ url: Api.authData, params });
//
export const authDataSaveOrUpdate = (params, isUpdate: boolean) => {
if (isUpdate) {
return defHttp.put({ url: Api.authData, params });
} else {
return defHttp.post({ url: Api.authData, params });
}
};
//
export const authDataDelete = (id: string, params?) => defHttp.delete({ url: `${Api.authData}/${id}`, params });
export const authFieldLoadTree = (cgformId: string, authType: number, params?) => {
let url = `${Api.authPage}/${cgformId}/${authType}`;
return defHttp.get({ url, params });
};
export const authDataLoadTree = (cgformId: string, params?) => {
let url = `${Api.validData}/${cgformId}`;
return defHttp.get({ url, params });
};
export const authButtonLoadTree = (cgformId: string, authType: number, params?) => {
let url = `${Api.authPage}/${cgformId}/${authType}`;
return defHttp.get({ url, params });
};
export const loadRoleAuthChecked = (params) => defHttp.get({ url: Api.roleAuth, params });
export const saveAuthField = (roleId: string, cgformId: string, params?) => {
let url = `${Api.saveField}/${roleId}/${cgformId}`;
return defHttp.post({ url, params });
};
export const saveAuthData = (roleId: string, cgformId: string, params?) => {
let url = `${Api.saveData}/${roleId}/${cgformId}`;
return defHttp.post({ url, params });
};
export const saveAuthButton = (roleId: string, cgformId: string, params?) => {
let url = `${Api.saveButton}/${roleId}/${cgformId}`;
return defHttp.post({url, params}, {successMessageMode: 'none', isTransformResponse: false});
};

View File

@ -0,0 +1,235 @@
import { computed } from 'vue';
import { BasicColumn, FormSchema } from '/@/components/Table';
import { useConditionFilter } from '/@/utils/index';
//
export const authFieldColumns: BasicColumn[] = [
{
title: '启用',
dataIndex: 'switch',
width: 100,
align: 'center',
slots: { customRender: 'switch' },
},
{
title: '字段名称',
width: 200,
dataIndex: 'code',
},
{
title: '字段描述',
// width: 200,
dataIndex: 'title',
},
{
title: '列表控制',
dataIndex: 'list',
width: 120,
slots: { customRender: 'list' },
},
{
title: '表单控制',
dataIndex: 'form',
width: 180,
slots: { customRender: 'form' },
},
];
//
export const authButtonColumns: BasicColumn[] = [
{
title: '启用',
dataIndex: 'switch',
width: 80,
slots: { customRender: 'switch' },
},
{
title: '名称',
dataIndex: 'title',
},
{
title: '编码',
dataIndex: 'code',
},
{
title: '权限控制',
dataIndex: 'control',
width: 180,
slots: { customRender: 'control' },
},
];
export const authButtonFixedList = [
{ code: 'add', title: '新增', status: 0 },
{ code: 'edit', title: '编辑', status: 0 },
{ code: 'detail', title: '详情', status: 0 },
{ code: 'delete', title: '删除', status: 0 },
{ code: 'batch_delete', title: '批量删除', status: 0 },
{ code: 'export', title: '导出', status: 0 },
{ code: 'import', title: '导入', status: 0 },
{ code: 'query', title: '查询', status: 0 },
{ code: 'reset', title: '重置', status: 0 },
{ code: 'aigc_mock_data', title: '生成测试数据', status: 0 },
{ code: 'bpm', title: '提交流程', status: 0 },
{ code: 'super_query', title: '高级查询', status: 0 },
{ code: 'form_confirm', title: '确定', status: 0 },
];
export const USE_SQL_RULES = 'USE_SQL_RULES';
//
export const authDataColumns: BasicColumn[] = [
{
title: '启用',
dataIndex: 'switch',
width: 80,
slots: { customRender: 'switch' },
},
{
title: '规则名称',
dataIndex: 'ruleName',
width: 130,
},
{
title: '规则描述',
dataIndex: 'description',
customRender({ record: { ruleOperator, ruleValue, ruleColumn } }) {
if (ruleOperator == USE_SQL_RULES) {
return `自定义SQL: ${ruleValue}`;
} else {
return `${ruleColumn} ${ruleOperator} ${ruleValue}`;
}
},
},
];
export function useAuthDataFormSchemas(props, methods) {
const formSchemas = computed<FormSchema[]>(() => [
{
label: '规则名称',
field: 'ruleName',
required: true,
component: 'Input',
componentProps: {
onChange: methods.onRuleNameChange,
},
},
{
label: '规则字段',
field: 'ruleColumn',
component: 'JSearchSelect',
componentProps: {
dictOptions: props.authFields,
getPopupContainer: () => document.body,
onChange: methods.onRuleColumnChange,
},
dynamicRules({ model }) {
return [{ required: model.ruleOperator != USE_SQL_RULES, message: '请输入规则字段' }];
},
show: ({ model }) => model.ruleOperator != USE_SQL_RULES,
},
// -update-begin--author:liaozhiyang---date:20240617---forTV360X-201
{
label: '条件规则',
field: 'ruleOperator',
required: true,
component: 'JDictSelectTag',
componentProps: {
options: [],
onChange: methods.onRuleOperatorChange,
getPopupContainer: () => document.body,
},
dynamicPropskey: 'options',
dynamicPropsVal: ({ model, field }) => {
const getFieldType = (type) => {
if (['BigDecimal', 'double', 'int'].includes(type)) {
return 'number';
} else {
return;
}
};
const { filterCondition } = useConditionFilter();
if (model.ruleColumn) {
const findItem = props.authFields.find((item) => item.value === model.ruleColumn) ?? {};
const result = filterCondition({ view: findItem.view, fieldType: getFieldType(findItem.dbType) }).map((item) => ({
label: item.title ?? item.label,
value: item.val ?? item.value,
}));
result.push({ value: 'USE_SQL_RULES', label: '自定义SQL' });
return result;
} else {
return [{ value: 'USE_SQL_RULES', label: '自定义SQL' }];
}
},
},
// {
// label: '',
// field: 'ruleOperator',
// required: true,
// component: 'JDictSelectTag',
// componentProps: {
// dictCode: 'rule_conditions',
// onChange: methods.onRuleOperatorChange,
// getPopupContainer: () => document.body,
// },
// },
// -update-end--author:liaozhiyang---date:20240617---forTV360X-201
{
label: '规则值',
field: 'ruleValue',
required: true,
// -update-begin--author:liaozhiyang---date:20240607---forTV360X-536JInputSelect
component: 'JInputSelect',
componentProps: {
selectPlaceholder: '可选择系统变量',
inputPlaceholder: '请输入',
getPopupContainer: () => document.body,
selectWidth: '200px',
options: [
{
label: '登录用户账号',
value: '#{sys_user_code}',
},
{
label: '登录用户名称',
value: '#{sys_user_name}',
},
{
label: '当前日期',
value: '#{sys_date}',
},
{
label: '当前时间',
value: '#{sys_time}',
},
{
label: '登录用户部门',
value: '#{sys_org_code}',
},
{
label: '用户拥有的部门',
value: '#{sys_multi_org_code}',
},
{
label: '登录用户租户',
value: '#{tenant_id}',
},
],
},
// -update-end--author:liaozhiyang---date:20240607---forTV360X-536JInputSelect
},
{
label: '状态',
field: 'status',
required: true,
component: 'RadioButtonGroup',
componentProps: {
options: [
{ label: '有效', value: 1 },
{ label: '无效', value: 0 },
],
},
defaultValue: 1,
},
]);
return { formSchemas };
}

View File

@ -0,0 +1,190 @@
<template>
<div class="auth-field-config">
<BasicTable @register="registerTable">
<template #switch="{ text, record }">
<a-switch size="small" :checked="record.status === 1" @change="(flag) => onUpdateStatus(flag, record)" />
</template>
<template #control> 可见 </template>
</BasicTable>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { cloneDeep } from 'lodash-es';
import { BasicTable, useTable } from '/@/components/Table';
import { authButtonLoadData, authButtonEnable, authButtonDisable } from '../auth.api';
import { authButtonColumns, authButtonFixedList } from '../auth.data';
export default defineComponent({
name: 'AuthButtonConfig',
components: { BasicTable },
props: {
headId: {
type: String,
default: '',
required: true,
},
// 1 2 3
tableType: {
type: Number,
default: 1,
},
},
setup(props) {
const cgformId = ref('');
const pageType = ref(2);
const pageControlList = ref(3);
const pageControlForm = ref(5);
const [registerTable, { reload, getTableRef, setPagination }] = useTable({
api: loadData,
rowKey: 'code',
bordered: true,
columns: authButtonColumns,
showIndexColumn: false,
});
watch(
() => props.headId,
(headId) => {
cgformId.value = headId.split('?')[0];
// update-begin--author:liaozhiyang---date:20240520---forTV360X-149
getTableRef().value && setPagination({ current: 1, pageSize: 10 });
// update-end--author:liaozhiyang---date:20240520---forTV360X-149
reload().catch(() => null);
},
{ immediate: true }
);
/**
* 获取内置按钮
* liaozhiyang
* 2024-06-14
* TV360X-1017子表权限管理按钮权限根据主表主题模版只显示所需按钮
* */
const getButtonList = (res) => {
const buttons = cloneDeep(authButtonFixedList);
if (res.mainRelationType != null && res.mainThemeTemplate != null && props.tableType == 3) {
//
let result: any = [];
switch (res.mainThemeTemplate) {
case 'normal':
case 'innerTable':
case 'tab':
if (res.mainRelationType == 1) {
//
result = [];
} else {
//
result = buttons.filter((item) => ['add', 'update', 'batch_delete'].includes(item.code));
}
break;
case 'erp':
result = buttons.filter((item) => !['super_query'].includes(item.code));
break;
}
return result;
} else {
// \
return buttons;
}
};
//
async function loadData(params) {
let result = await authButtonLoadData(cgformId.value, params);
let { authList, buttonList } = result;
let dataSource: Recordable[] = [];
// concat
// -update-begin--author:liaozhiyang---date:20240614---forTV360X-1017
//
const buttons = getButtonList(result);
// -update-end--author:liaozhiyang---date:20240614---forTV360X-1017
for (let btn of buttons) {
//
const findBtnIdx = buttonList.findIndex((item) => item.buttonCode === btn.code);
const findBtn: Recordable = {}
if (findBtnIdx !== -1) {
findBtn.title = buttonList[findBtnIdx].buttonName;
buttonList.splice(findBtnIdx, 1);
}
let item = {
status: 0,
page: pageControlList.value,
};
let auth = authList.find((auth) => auth.code == btn.code);
Object.assign(btn, item, auth, findBtn);
dataSource.push(btn);
}
// update-begin--author:liaozhiyang---date:20240520---forTV360X-187
if (props.tableType == 3) {
const findIndex = dataSource.findIndex((item) => item.code === 'super_query');
if (findIndex != -1) {
dataSource.splice(findIndex, 1);
}
}
// update-end--author:liaozhiyang---date:20240520---forTV360X-187
// update-begin--author:liaozhiyang---date:20250403---forQQYUN-11801
if ([2, 3].includes(+props.tableType)) {
const findIndex = dataSource.findIndex((item) => item.code == 'aigc_mock_data');
if (findIndex != -1) {
dataSource.splice(findIndex, 1);
}
}
// update-end--author:liaozhiyang---date:20250403---forQQYUN-11801
// concat
return concatCustomButton(authList, buttonList, dataSource);
}
function concatCustomButton(authList: any[], buttonList: any[], dataSource: any[]) {
for (let btn of buttonList) {
//update-begin-author:taoyan date:2022-5-25 for: VUEN-1103
let auth = authList.find((auth) => auth.code == btn.buttonCode);
//update-end-author:taoyan date:2022-5-25 for: VUEN-1103
let item = {
code: btn.buttonCode,
title: btn.buttonName,
status: 0,
page: btn.buttonStyle == 'form' ? pageControlForm.value : pageControlList.value,
};
dataSource.push(Object.assign(item, auth));
}
return dataSource;
}
async function onUpdateStatus(flag, record) {
flag ? doEnableAuthButton(record) : doDisableAuthButton(record);
}
//
async function doEnableAuthButton(record: Recordable) {
let result = await authButtonEnable({
id: record.id,
code: record.code,
page: record.page,
cgformId: cgformId.value,
type: pageType.value,
control: 5,
status: 1,
});
record.id = result.id;
record.status = 1;
}
//
async function doDisableAuthButton(record: Recordable) {
await authButtonDisable(record.id);
record.status = 0;
}
return { registerTable, onUpdateStatus };
},
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,229 @@
<template>
<div>
<BasicTable @register="registerTable" :loading="loading">
<template #tableTitle>
<a-button @click="onAdd" type="primary" preIcon="ant-design:plus">新增</a-button>
</template>
<template #switch="{ text, record }">
<a-switch size="small" :checked="record.status === 1" @click="() => onUpdateStatus(record)" />
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 子表单 -->
<BasicModal v-bind="formModalProps" @openChange="handleOpenChange">
<a-spin :spinning="formModalProps.confirmLoading">
<BasicForm @register="registerForm" />
</a-spin>
</BasicModal>
</div>
</template>
<script lang="ts">
import { watch, defineComponent, ref, reactive, nextTick } from 'vue';
import { BasicTable, TableAction, ActionItem, useTable } from '/@/components/Table';
import { BasicModal, useModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { authDataDelete, authDataLoadData, authDataSaveOrUpdate, authDataUpdateStatus } from '../auth.api';
import { authDataColumns, useAuthDataFormSchemas, USE_SQL_RULES } from '../auth.data';
export default defineComponent({
name: 'AuthDataConfig',
components: { BasicTable, TableAction, BasicModal, BasicForm },
props: {
cgformId: { type: String, required: true },
authFields: { type: Array, required: true },
},
setup(props) {
const loading = ref(false);
const [registerTable, { reload, setLoading }] = useTable({
api: (params) => authDataLoadData(props.cgformId, params),
rowKey: 'id',
bordered: true,
columns: authDataColumns,
showIndexColumn: false,
//
actionColumn: {
width: 120,
title: '操作',
fixed: false,
dataIndex: 'action',
slots: { customRender: 'action' },
},
});
watch(loading, (l) => setLoading(l));
const [registerModal, { openModal, closeModal }] = useModal();
const formModalProps = reactive({
title: '',
width: 800,
confirmLoading: false,
onOk: onSubmit,
onCancel: closeModal,
onRegister: registerModal,
});
let isUpdate = false;
let formRecord = {};
let isManualEnter = false;
const { formSchemas } = useAuthDataFormSchemas(props, {
onRuleOperatorChange,
onRuleColumnChange,
onRuleNameChange,
});
//
const [registerForm, { validate, resetFields, setFieldsValue, getFieldsValue, clearValidate, updateSchema }] = useForm({
schemas: formSchemas,
showActionButtonGroup: false,
labelAlign: 'right',
});
watch(
() => props.cgformId,
() => {
reload().catch(() => null);
},
{ immediate: true }
);
async function openFormModal(data) {
isUpdate = data.isUpdate ?? false;
formModalProps.title = data.title;
openModal();
await nextTick();
await resetFields();
formRecord = Object.assign({}, data.record);
await setFieldsValue(formRecord);
}
function onAdd() {
openFormModal({ title: '新增' });
}
function onEdit(record) {
openFormModal({ title: '编辑', record, isUpdate: true });
}
function onDelete(id) {
loading.value = true;
authDataDelete(id)
.then(reload)
.finally(() => (loading.value = false));
}
async function onSubmit() {
try {
formModalProps.confirmLoading = true;
let formData = await validate();
formData = Object.assign({}, formRecord, formData);
if (formData.ruleOperator == USE_SQL_RULES) {
formData.ruleColumn = '';
}
formData.cgformId = props.cgformId;
await authDataSaveOrUpdate(formData, isUpdate);
reload();
closeModal();
} finally {
formModalProps.confirmLoading = false;
}
}
function onUpdateStatus(record) {
loading.value = true;
let status = Math.abs(record.status - 1);
authDataUpdateStatus({ ...record, status })
.then(() => {
record.status = status;
})
.finally(() => {
loading.value = false;
});
}
function onRuleOperatorChange(val) {
if (val == USE_SQL_RULES) {
setFieldsValue({
ruleColumn: '',
ruleValue: '',
});
updateSchema({
field: 'ruleValue',
component: 'InputTextArea',
});
clearValidate(['ruleValue']);
} else {
updateSchema({
field: 'ruleValue',
component: 'JInputSelect',
});
}
}
// -update-begin--author:liaozhiyang---date:20240607---forTV360X-536JInputSelect
function onRuleColumnChange(val) {
const values = getFieldsValue();
if (!values.ruleName || (values.ruleName && !isManualEnter)) {
const findItem: any = props.authFields.find((item: any) => item.value === val);
const text = findItem ? findItem.text : val;
setFieldsValue({
ruleName: text,
});
}
}
function onRuleNameChange(e) {
if (e.target.value.length) {
isManualEnter = true;
} else {
isManualEnter = false;
}
}
function handleOpenChange(visible) {
if (visible) {
isManualEnter = false;
}
}
// -update-end--author:liaozhiyang---date:20240607---forTV360X-536JInputSelect
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: () => onEdit(record),
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record): ActionItem[] {
return [
{
label: '删除',
popConfirm: {
title: '确定删除吗?',
placement: 'left',
confirm: () => onDelete(record.id),
},
},
];
}
return {
loading,
formModalProps,
onAdd,
onUpdateStatus,
getTableAction,
getDropDownAction,
registerTable,
registerModal,
registerForm,
handleOpenChange,
};
},
});
</script>

View File

@ -0,0 +1,364 @@
<template>
<div class="auth-field-config">
<BasicTable @register="registerTable" @change="handleTableChange" :loading="tableLoading">
<!-- update-begin--author:liaozhiyang---date:20240612---forTV360X-148权限配置加全选 -->
<template #headerCell="{ column }">
<template v-if="column.dataIndex === 'switch'">
<a-switch :loading="allSloading" v-model:checked="allSwitch" size="small" @change="handleChangeSwitch"></a-switch>启用
</template>
<template v-else-if="column.dataIndex === 'list'">
<a-checkbox :indeterminate="listIndeterminate" v-model:checked="allListControl" :disabled="!allSwitch" @change="handleChangeList">{{ column.customTitle }}</a-checkbox>
</template>
<template v-else-if="column.dataIndex === 'form'">
<a-checkbox :indeterminate="formIndeterminate" v-model:checked="allFormControl" :disabled="!allSwitch" @change="handleChangeForm">{{ column.customTitle }}</a-checkbox>
</template>
<template v-else>
{{ column.customTitle }}
</template>
</template>
<!-- update-end--author:liaozhiyang---date:20240612---forTV360X-148权限配置加全选 -->
<template #switch="{ text, record }">
<a-switch size="small" :checked="record.status === 1" @change="(flag) => onUpdateStatus(flag, record)" />
</template>
<template #list="{ text, record }">
<a-checkbox :checked="record.listShow" :disabled="record.status === 0" @change="(e) => onCheckboxChange(e, record, 1)"> 可见 </a-checkbox>
</template>
<template #form="{ text, record }">
<a-checkbox :checked="record.formShow" :disabled="record.status === 0" @change="(e) => onCheckboxChange(e, record, 2)"> 可见 </a-checkbox>
<a-checkbox :checked="record.formEditable" :disabled="record.status === 0" @change="(e) => onCheckboxChange(e, record, 3)">
可编辑
</a-checkbox>
</template>
</BasicTable>
</div>
</template>
<script lang="ts">
import { watch, defineComponent, ref } from 'vue';
import { BasicTable, useTable } from '/@/components/Table';
import { authFieldLoadData, authFieldUpdateCheckbox, authFieldUpdateStatus, batchAuthFieldUpdateStatus, batchAuthFieldUpdateCheckbox } from '../auth.api';
import { authFieldColumns } from '../auth.data';
export default defineComponent({
name: 'AuthFieldConfig',
components: { BasicTable },
props: {
headId: {
type: String,
default: '',
required: true,
},
},
emits: ['update:authFields'],
setup(props, { emit }) {
const cgformId = ref('');
const [registerTable, { reload, getTableRef, setPagination }] = useTable({
api: loadData,
rowKey: 'code',
bordered: true,
columns: authFieldColumns,
showIndexColumn: false,
});
const allSwitch = ref(false);
const allListControl = ref(false);
const allFormControl = ref(false);
const allSloading = ref(false);
const tableLoading = ref(false);
const formIndeterminate = ref(false);
const listIndeterminate = ref(false);
watch(
() => props.headId,
(headId) => {
cgformId.value = headId.split('?')[0];
// update-begin--author:liaozhiyang---date:20240520---forTV360X-149
getTableRef().value && setPagination({ current: 1, pageSize: 10 });
// update-end--author:liaozhiyang---date:20240520---forTV360X-149
reload().catch(() => null);
},
{ immediate: true }
);
//
async function loadData(params) {
const exclude = ['id'];
let data = await authFieldLoadData(cgformId.value, params);
let fields: any[] = [];
let filterData: any[] = [];
data.forEach((item) => {
if (exclude.indexOf(item.code) < 0) {
if (item.isShowForm == 1 || item.isShowList == 1) {
filterData.push(item);
}
//update-begin-author:taoyan date:2022-8-9 for: VUEN-1957 online
if(item.dbIsPersist==1){
fields.push({
text: item.title,
value: item.code,
// -update-begin--author:liaozhiyang---date:20240617---forTV360X-201
view: item.fieldShowType,
dbType: item.dbType,
// -update-end--author:liaozhiyang---date:20240617---forTV360X-201
});
}
//update-end-author:taoyan date:2022-8-9 for: VUEN-1957 online
}
});
emit('update:authFields', fields);
// update-begin--author:liaozhiyang---date:20240612---forTV360X-148
setCurDataStatus(params.pageNo, params.pageSize, filterData);
// update-end--author:liaozhiyang---date:20240612---forTV360X-148
return filterData;
}
async function onUpdateStatus(flag, record) {
await authFieldUpdateStatus({
cgformId: cgformId.value,
code: record.code,
status: flag ? 1 : 0,
});
if (!(record.formEditable || record.formShow || record.listShow)) {
record.formEditable = true;
record.formShow = true;
record.listShow = true;
}
record.status = Math.abs(record.status - 1);
// update-begin--author:liaozhiyang---date:20240612---forTV360X-148
itemChange();
// update-end--author:liaozhiyang---date:20240612---forTV360X-148
}
async function onCheckboxChange(event, record, switchFlag) {
let checked = event.target.checked;
await authFieldUpdateCheckbox({
cgformId: cgformId.value,
code: record.code,
switchFlag: switchFlag,
listShow: checked,
formShow: checked,
formEditable: checked,
});
if (switchFlag == 1) {
record.listShow = checked;
} else if (switchFlag == 2) {
record.formShow = checked;
} else if (switchFlag == 3) {
record.formEditable = checked;
}
// update-begin--author:liaozhiyang---date:20240807---forTV360X-2087
if (record.listShow === false && record.formShow === false && record.formEditable === false) {
record.status = 0;
}
// update-end--author:liaozhiyang---date:20240807---forTV360X-2087
// update-begin--author:liaozhiyang---date:20240612---forTV360X-148
itemChange();
// update-end--author:liaozhiyang---date:20240612---forTV360X-148
}
// update-begin--author:liaozhiyang---date:20240612---forTV360X-148
//
function setCurDataStatus(current, pageSize, data) {
const result: any = [];
if (data?.length) {
const max = current * pageSize > data.length ? data.length : current * pageSize;
for (let i = current * pageSize - pageSize; i < max; i++) {
const item = data[i];
result.push(item);
}
}
if (result.length) {
// truefalsefalse
allSwitch.value = true;
allListControl.value = true;
allFormControl.value = true;
result.forEach((item) => {
if (allSwitch.value && item.status == 0) {
allSwitch.value = false;
}
if (allListControl.value && item.listShow == false) {
allListControl.value = false;
}
if (allFormControl.value && (item.formEditable == false || item.formShow == false)) {
allFormControl.value = false;
}
});
if (allListControl.value == true) {
listIndeterminate.value = false;
} else {
const findItem = result.find((item) => item.listShow);
if (findItem) {
listIndeterminate.value = true;
} else {
listIndeterminate.value = false;
}
}
if (allFormControl.value == true) {
formIndeterminate.value = false;
} else {
const findItem = result.find((item) => item.formEditable || item.formShow);
if (findItem) {
formIndeterminate.value = true;
} else {
formIndeterminate.value = false;
}
}
} else {
allSwitch.value = false;
allListControl.value = false;
allFormControl.value = false;
}
}
//
const itemChange = () => {
const { current, pageSize } = getTableRef().value!.getPaginationRef();
const allDataSource = getTableRef().value!.getDataSource();
setCurDataStatus(current, pageSize, allDataSource);
};
// dataSource
const getCurrentDataSource = (current, pageSize) => {
const result: any = [];
const allDataSource = getTableRef().value!.getDataSource();
if (allDataSource?.length) {
const max = current * pageSize > allDataSource.length ? allDataSource.length :current * pageSize;
for (let i = current * pageSize - pageSize; i < max; i++) {
const item = allDataSource[i];
result.push(item);
}
}
return result;
};
// |
const handleChangeSwitch = async (checked) => {
tableLoading.value = true;
allSwitch.value = checked;
const { current, pageSize } = getTableRef().value!.getPaginationRef();
const dataSource = getCurrentDataSource(current, pageSize);
let params = dataSource.map((item) => ({ cgformId: item.cgformId, code: item.code, status: checked ? 1 : 0 }));
allSloading.value = true;
await batchAuthFieldUpdateStatus(params);
dataSource.forEach((item) => {
if (checked) {
item.status = 1;
} else {
item.status = 0;
}
// update-begin--author:liaozhiyang---date:20240807---forTV360X-1992-
if (!(item.formEditable || item.formShow || item.listShow)) {
item.formEditable = true;
item.formShow = true;
item.listShow = true;
}
});
// update-end--author:liaozhiyang---date:20240807---forTV360X-1992-
allSloading.value = false;
tableLoading.value = false;
const allDataSource = getTableRef().value!.getDataSource();
setCurDataStatus(current, pageSize, allDataSource);
};
const handleChangeList = async (e) => {
tableLoading.value = true;
const checked = e.target.checked;
allListControl.value = checked;
const { current, pageSize } = getTableRef().value!.getPaginationRef();
const dataSource = getCurrentDataSource(current, pageSize);
let params = dataSource.map((item) => ({ cgformId: item.cgformId, code: item.code, switchFlag: 1, listShow: !!checked }));
await batchAuthFieldUpdateCheckbox(params);
dataSource.forEach((item) => {
item.listShow = !!checked;
// update-begin--author:liaozhiyang---date:20240807---forTV360X-2087
if (item.listShow === false && item.formShow === false && item.formEditable === false) {
item.status = 0;
allSwitch.value = false;
}
// update-end--author:liaozhiyang---date:20240807---forTV360X-2087
});
if (checked) {
listIndeterminate.value = false;
}
tableLoading.value = false;
};
const handleChangeForm = async (e) => {
tableLoading.value = true;
const checked = e.target.checked;
allFormControl.value = checked;
const { current, pageSize } = getTableRef().value!.getPaginationRef();
const dataSource = getCurrentDataSource(current, pageSize);
const params = [
...dataSource.map((item) => ({ cgformId: item.cgformId, code: item.code, switchFlag: 4, formShow: !!checked, formEditable: !!checked })),
];
await batchAuthFieldUpdateCheckbox(params);
dataSource.forEach((item) => {
item.formEditable = !!checked;
item.formShow = !!checked;
// update-begin--author:liaozhiyang---date:20240807---forTV360X-2087
if (item.listShow === false && item.formShow === false && item.formEditable === false) {
item.status = 0;
allSwitch.value = false;
}
// update-end--author:liaozhiyang---date:20240807---forTV360X-2087
});
if (checked) {
formIndeterminate.value = false;
}
tableLoading.value = false;
};
//
const handleTableChange = (pagination) => {
// const { current, pageSize } = pagination;
// const dataSource = getCurrentDataSource(current, pageSize);
// //
// allSwitch.value = false;
// if(dataSource.length) {
// }
}
// update-end--author:liaozhiyang---date:20240612---forTV360X-148
return { registerTable, onUpdateStatus, onCheckboxChange, handleChangeSwitch, allSwitch, allFormControl, allListControl, allSloading, handleTableChange, handleChangeList, handleChangeForm, tableLoading, formIndeterminate, listIndeterminate, };
},
});
</script>
<style lang="less" scoped>
.auth-field-config :deep(.ant-checkbox + span) {
padding-left: 2px;
}
:deep(.ant-table-thead) {
.ant-switch {
margin-right: 5px;
}
.ant-checkbox-wrapper {
.ant-checkbox {
margin-right: 4px;
}
.ant-checkbox-disabled + span {
color: #000;
}
}
}
html[data-theme='light'] {
.auth-field-config {
:deep(.ant-table-thead) {
.ant-checkbox-wrapper {
.ant-checkbox-disabled + span {
color: #000;
}
}
}
}
}
html[data-theme='dark'] {
.auth-field-config {
:deep(.ant-table-thead) {
.ant-checkbox-wrapper {
color: rgba(255, 255, 255, 0.65);
.ant-checkbox-disabled + span {
color: rgba(255, 255, 255, 0.65);
}
}
}
}
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<div>
<a-empty v-if="disabled" description="请先选中左侧角色/部门/用户" />
<a-empty v-else-if="treeData.length === 0" description="无权限信息" />
<template v-else>
<div class="onl-auth-tree-btns">
<a-button @click="onRefresh" size="small" type="primary" preIcon="ant-design:redo" ghost>刷新</a-button>
<a-button @click="onSave" size="small" type="primary" preIcon="ant-design:save" ghost>保存</a-button>
</div>
<a-tree
checkable
v-model:checkedKeys="checkedKeys"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
:treeData="treeData"
@expand="onExpand"
/>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { authButtonFixedList } from '../auth.data';
import { authButtonLoadTree, loadRoleAuthChecked, saveAuthButton } from '../auth.api';
export default defineComponent({
name: 'AuthButtonTree',
props: {
cgformId: { type: String, required: true },
},
setup(props) {
const { createMessage: $message, createSuccessModal } = useMessage();
const roleId = ref('');
const authType = ref(2);
const autoExpandParent = ref(true);
const expandedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const treeData = ref<Recordable[]>([]);
const authMode = ref('');
const disabled = computed(() => !roleId.value);
watch(() => props.cgformId, loadTree, { immediate: true });
async function loadTree() {
if (!props.cgformId) return;
let result = (await authButtonLoadTree(props.cgformId, authType.value)) as Recordable[];
//1. code
result.forEach((item) => {
for (const btn of authButtonFixedList) {
if (item.code == btn.code) {
if (!item['title']) {
item['title'] = btn.title;
}
break;
}
}
});
//2.
let trees: Recordable[] = [];
for (let item of result) {
let title = getTreeNodeTitle(item);
trees.push({ key: item.id, title });
}
treeData.value = trees;
}
function onRefresh() {
loadTree();
loadChecked(roleId.value, authMode.value);
}
//
async function loadChecked($roleId, $authMode) {
roleId.value = $roleId;
authMode.value = $authMode;
checkedKeys.value = [];
await loadTree();
let result = (await loadRoleAuthChecked({
roleId: $roleId,
cgformId: props.cgformId,
type: authType.value,
authMode: $authMode,
})) as Recordable[];
checkedKeys.value = result.map((item) => item.authId);
}
function clearChecked() {
roleId.value = '';
// QQYUN-4284 online
loadTree();
}
async function onSave() {
try {
const {success, message, result} = await saveAuthButton(roleId.value, props.cgformId, {
authId: JSON.stringify(checkedKeys.value),
authMode: authMode.value,
});
if (success) {
if (Array.isArray(result?.disabledNames)) {
createSuccessModal({
title: '保存成功',
content: `由于以下按钮未激活,所以权限未生效。<br>${result.disabledNames.join('<br>')}`,
});
} else {
$message.success('保存成功');
}
} else {
$message.error(message);
}
} catch (e) {
$message.error('保存出现异常');
console.error(e);
}
}
function getTreeNodeTitle(item) {
let str = item.title + '-';
if (item.code && item.code.includes('form_sub')) {
// TV360X-2711 code form_sub
str += '表单可见(附表)'
} else if (item.page == 3) {
str += '列表可见';
} else if (item.page == 5) {
str += '表单可见';
}
return str;
}
function onExpand($expandedKeys) {
expandedKeys.value = $expandedKeys;
autoExpandParent.value = false;
}
function clear() {
roleId.value = '';
checkedKeys.value = [];
}
return {
loadChecked,
clear,
expandedKeys,
autoExpandParent,
checkedKeys,
treeData,
disabled,
onSave,
onExpand,
onRefresh,
clearChecked,
};
},
});
</script>
<style scoped lang="less">
.onl-auth-tree-btns {
margin-left: 10px;
}
.onl-auth-tree-btns button {
margin: 0 5px 0 2px;
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<div>
<a-empty v-if="disabled" description="请先选中左侧角色/部门/用户" />
<a-empty v-else-if="treeData.length === 0" description="无权限信息" />
<template v-else>
<div class="onl-auth-tree-btns">
<a-button @click="onRefresh" size="small" type="primary" preIcon="ant-design:redo" ghost>刷新</a-button>
<a-button @click="onSave" size="small" type="primary" preIcon="ant-design:save" ghost>保存</a-button>
</div>
<a-tree
checkable
v-model:checkedKeys="checkedKeys"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
:treeData="treeData"
@expand="onExpand"
>
</a-tree>
</template>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { authDataLoadTree, loadRoleAuthChecked, saveAuthData } from '../auth.api';
export default defineComponent({
name: 'AuthDataTree',
props: {
cgformId: { type: String, required: true },
},
setup(props) {
const { createMessage: $message } = useMessage();
const roleId = ref('');
const authType = ref(3);
const autoExpandParent = ref(true);
const expandedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const treeData = ref<Recordable[]>([]);
const authMode = ref('');
const disabled = computed(() => !roleId.value);
watch(() => props.cgformId, loadTree, { immediate: true });
async function loadTree() {
if (!props.cgformId) return;
let result = (await authDataLoadTree(props.cgformId)) as Recordable[];
treeData.value = result.map((item) => ({ key: item.id, title: item.ruleName }));
}
async function loadChecked($roleId, $authMode) {
roleId.value = $roleId;
authMode.value = $authMode;
checkedKeys.value = [];
await loadTree();
let result = (await loadRoleAuthChecked({
roleId: $roleId,
cgformId: props.cgformId,
type: authType.value,
authMode: $authMode,
})) as Recordable[];
checkedKeys.value = result.map((item) => item.authId);
}
function clearChecked() {
roleId.value = '';
// QQYUN-4284 online
loadTree();
}
function onRefresh() {
loadTree();
loadChecked(roleId.value, authMode.value);
}
async function onSave() {
await saveAuthData(roleId.value, props.cgformId, {
authId: JSON.stringify(checkedKeys.value),
authMode: authMode.value,
});
$message.success('保存成功');
}
function onExpand($expandedKeys) {
expandedKeys.value = $expandedKeys;
autoExpandParent.value = false;
}
function clear() {
roleId.value = '';
checkedKeys.value = [];
}
return {
loadChecked,
clear,
expandedKeys,
autoExpandParent,
checkedKeys,
treeData,
disabled,
onSave,
onExpand,
onRefresh,
clearChecked,
};
},
});
</script>
<style scoped lang="less">
.onl-auth-tree-btns {
margin-left: 10px;
}
.onl-auth-tree-btns button {
margin: 0 5px 0 2px;
}
</style>

View File

@ -0,0 +1,205 @@
<template>
<div>
<a-empty v-if="disabled" description="请先选中左侧角色/部门/用户" />
<a-empty v-else-if="treeData.length === 0" description="无权限信息" />
<template v-else>
<div class="onl-auth-tree-btns">
<a-button @click="onRefresh" size="small" type="primary" preIcon="ant-design:redo" ghost>刷新</a-button>
<a-button @click="onExpandAll" size="small" type="primary" ghost><DownCircleOutlined />展开</a-button>
<a-button @click="onCloseAll" size="small" type="primary" ghost><UpCircleOutlined />折叠</a-button>
<a-button @click="onSave" size="small" type="primary" preIcon="ant-design:save" ghost>保存</a-button>
<!-- update-begin-author:taoyan date:2022-5-25 for: VUEN-1102 字段授权 全选没有 -->
<a-button @click="onSelectAll" size="small" type="primary" ghost><CheckOutlined />全选</a-button>
<a-button @click="onClearSelected" size="small" type="primary" ghost><UndoOutlined />重置</a-button>
<!-- update-end-author:taoyan date:2022-5-25 for: VUEN-1102 字段授权 全选没有 -->
</div>
<a-tree
checkable
v-model:checkedKeys="checkedKeys"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
:treeData="treeData"
@expand="onExpand"
/>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed, unref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { authFieldLoadTree, loadRoleAuthChecked, saveAuthField } from '../auth.api';
import { DownCircleOutlined, HomeOutlined, UpCircleOutlined, CheckOutlined, UndoOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'AuthFieldTree',
components: {
DownCircleOutlined,
HomeOutlined,
UpCircleOutlined,
UndoOutlined,
CheckOutlined,
},
props: {
cgformId: { type: String, required: true },
},
setup(props) {
const { createMessage: $message } = useMessage();
const roleId = ref('');
const authType = ref(1);
const autoExpandParent = ref(true);
const expandedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const allCode = ref<string[]>([]);
const treeData = ref<Recordable[]>([]);
const authMode = ref('');
const disabled = computed(() => !roleId.value);
watch(() => props.cgformId, loadTree, { immediate: true });
async function loadTree() {
if (!props.cgformId) return;
let result = (await authFieldLoadTree(props.cgformId, authType.value)) as Recordable[];
// 1. code
let trees: Recordable[] = [];
let codes: string[] = [];
result.forEach((item) => {
if (!codes.includes(item.code)) {
codes.push(item.code);
trees.push({ key: item.code, title: item.title });
}
});
// 2.
for (let node of trees) {
let children: Recordable[] = [];
for (let item of result) {
if (node.key === item.code) {
let temp = getTreeNodeTitle(item);
children.push({ key: item.id, title: temp });
}
}
node.children = children;
}
treeData.value = trees;
expandedKeys.value = [...codes];
allCode.value = codes;
}
function getTreeNodeTitle(item) {
let str = '';
if (item.page == 3) {
str += '列表';
} else if (item.page == 5) {
str += '表单';
}
if (item.control == 3) {
str += '可编辑';
} else if (item.control == 5) {
str += '可见';
}
return str;
}
//
async function loadChecked($roleId, $authMode) {
roleId.value = $roleId;
authMode.value = $authMode;
checkedKeys.value = [];
await loadTree();
let result = (await loadRoleAuthChecked({
roleId: $roleId,
cgformId: props.cgformId,
type: authType.value,
authMode: $authMode,
})) as Recordable[];
checkedKeys.value = result.map((item) => item.authId);
}
function clearChecked() {
roleId.value = '';
// QQYUN-4284 online
loadTree();
}
function onRefresh() {
loadTree();
loadChecked(roleId.value, authMode.value);
}
async function onSave() {
let ids = checkedKeys.value.filter((i) => allCode.value.indexOf(i) < 0);
await saveAuthField(roleId.value, props.cgformId, {
authId: JSON.stringify(ids),
authMode: authMode.value,
});
$message.success('保存成功');
}
function onExpandAll() {
expandedKeys.value = [...allCode.value];
}
function onCloseAll() {
expandedKeys.value = [];
}
function onExpand($expandedKeys) {
expandedKeys.value = $expandedKeys;
autoExpandParent.value = false;
}
function clear() {
roleId.value = '';
checkedKeys.value = [];
}
// update-begin-author:taoyan date:2022-5-25 for: VUEN-1102
// --
function onClearSelected() {
checkedKeys.value = [];
}
//
function onSelectAll() {
const selectFun = function (arr) {
for (let node of arr) {
checkedKeys.value.push(node.key);
if (node.children && node.children.length > 0) {
selectFun.call(null, node.children);
}
}
};
checkedKeys.value = [];
selectFun.call(null, unref(treeData));
}
// update-end-author:taoyan date:2022-5-25 for: VUEN-1102
return {
loadChecked,
clear,
expandedKeys,
autoExpandParent,
checkedKeys,
treeData,
disabled,
onSave,
onExpand,
clearChecked,
onCloseAll,
onExpandAll,
onRefresh,
onClearSelected,
onSelectAll,
};
},
});
</script>
<style scoped lang="less">
.onl-auth-tree-btns {
margin-left: 10px;
}
.onl-auth-tree-btns button {
margin: 0 5px 0 2px;
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<div>
<a-tree
v-if="treeData.length > 0"
showIcon
autoExpandParent
:treeData="treeData"
:selectedKeys="selectedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
<template #icon="{ selected }">
<a-icon :style="{ color: selected ? 'blue' : '' }" type="apartment" />
</template>
</a-tree>
</div>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue';
import { queryTreeList } from '/@/api/common/api';
export default defineComponent({
name: 'LeftDepart',
emits: ['select'],
setup(_, { emit }) {
const treeData = ref<Recordable[]>([]);
const selectedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>([]);
function onSelect(_, e) {
let record = e.node.dataRef;
selectedKeys.value = [record.key];
emit('select', record.id);
}
loadTree();
async function loadTree() {
let result = await queryTreeList();
treeData.value = [];
result.forEach((node) => initialNode(node));
}
function initialNode(node, level = 1) {
if (level === 1) {
treeData.value.push(node);
expandedKeys.value.push(node.id);
}
if (node.children && node.children.length > 0) {
for (const childNode of node.children) {
initialNode(childNode, level + 1);
}
}
}
function clearSelected() {
selectedKeys.value = [];
}
return {
treeData,
expandedKeys,
selectedKeys,
clearSelected,
onSelect,
};
},
});
</script>

View File

@ -0,0 +1,72 @@
<template>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTop><span></span></template>
</BasicTable>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicTable } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
export default defineComponent({
name: 'LeftRole',
components: { BasicTable },
emits: ['select'],
setup(_, { emit }) {
const { tableContext, createMessage: $message } = useListPage({
tableProps: {
api: loadData,
rowKey: 'id',
size: 'small',
bordered: true,
columns: [
{ title: '角色编码', align: 'center', dataIndex: 'roleCode' },
{ title: '角色名称', align: 'center', dataIndex: 'roleName' },
],
rowSelection: {
type: 'radio',
onChange(selectedRowKeys) {
if (selectedRowKeys.length > 0) {
emit('select', selectedRowKeys[0]);
}
},
},
canResize: false,
clickToRowSelect: true,
useSearchForm: false,
showActionColumn: false,
showTableSetting: false,
},
});
const [registerTable, { clearSelectedRowKeys }, { rowSelection }] = tableContext;
async function loadData(params) {
let { code, success, result, message } = await defHttp.get(
{
url: '/sys/role/list',
params,
},
{ isTransformResponse: false }
);
if (success) {
return result;
}
if (code === 510) {
$message.warning(message);
}
return {};
}
function clearSelected() {
clearSelectedRowKeys();
}
return { rowSelection, registerTable, clearSelected };
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,90 @@
<template>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTop><span></span></template>
</BasicTable>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { BasicTable } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
export default defineComponent({
name: 'LeftUser',
components: { BasicTable },
emits: ['select'],
setup(_, { emit }) {
const { tableContext, createMessage: $message } = useListPage({
tableProps: {
api: loadData,
rowKey: 'id',
size: 'small',
bordered: true,
columns: [
{ title: '账号', dataIndex: 'username', width: 200 },
{ title: '姓名', dataIndex: 'realname', width: 200 },
],
rowSelection: {
type: 'radio',
onChange(selectedRowKeys) {
if (selectedRowKeys.length > 0) {
emit('select', selectedRowKeys[0]);
}
},
},
formConfig: {
schemas: [
{
label: '账号',
field: 'username',
component: 'JInput',
componentProps: {
placeholder: '输入账号',
},
},
{
label: '姓名',
field: 'realname',
component: 'JInput',
componentProps: {
placeholder: '输入姓名',
},
},
],
},
canResize: false,
clickToRowSelect: true,
showActionColumn: false,
showTableSetting: false,
},
});
const [registerTable, { clearSelectedRowKeys }, { rowSelection }] = tableContext;
async function loadData(params) {
let { code, success, result, message } = await defHttp.get(
{
url: '/sys/user/list',
params,
},
{ isTransformResponse: false }
);
if (success) {
return result;
}
if (code === 510) {
$message.warning(message);
}
return {};
}
function clearSelected() {
clearSelectedRowKeys();
}
return { rowSelection, registerTable, clearSelected };
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,164 @@
<template>
<BasicModal @register="registerModal" title="内置按钮" :width="1200" @cancel="onCancel">
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"/>
</template>
</BasicTable>
<template #footer>
<a-button @click="onCancel">关闭</a-button>
</template>
<!-- 表单区域 -->
<BasicModal v-bind="formModalProps">
<a-spin :spinning="formModalProps.confirmLoading">
<div style="margin-top: 20px;">
<BasicForm @register="registerForm"/>
</div>
</a-spin>
</BasicModal>
</BasicModal>
</template>
<script setup lang="ts">
import {computed, nextTick, reactive, ref} from 'vue';
import {useListPage} from '/@/hooks/system/useListPage';
import {BasicTable, TableAction} from '/@/components/Table';
import {BasicModal, useModal, useModalInner} from '/@/components/Modal';
import {BasicForm, useForm} from '/@/components/Form';
import {builtInList, saveOrUpdate} from './button.api';
import {columns, formSchemas} from './button.data';
const props = defineProps({
record: {
type: Object,
required: true,
},
})
const emit = defineEmits(['register']);
const code = computed(() => props.record?.id);
// 123
const tableType = computed(() => props.record?.tableType);
const isSingleTable = computed(() => tableType.value === 1);
//
const {tableContext} = useListPage({
tableProps: {
api: async (params) => {
const list = await builtInList(code.value, params)
if (isSingleTable.value) {
// sub_
return list.filter(item => !item.buttonCode.includes('sub_'));
}
return list;
},
columns: columns.filter(item => !['buttonStyle', 'optType', 'orderNum', 'exp'].includes(item.dataIndex as string)),
canResize: false,
useSearchForm: false,
pagination: false,
},
});
// table
const [registerTable, {reload}, {rowSelection}] = tableContext;
//
const [registerModal, {closeModal}] = useModalInner(() => reload());
// form
const [registerFormModal, formModal] = useModal();
const isUpdate = ref(false);
const formModalProps = reactive({
onRegister: registerFormModal,
title: computed(() => (isUpdate?.value ? '修改' : '新增')),
width: 600,
centered: true,
confirmLoading: false,
onOk: onSubmit,
onCancel: formModal.closeModal,
});
let formRecord = {};
const schemas = [
...formSchemas({redoModalHeight: formModal.redoModalHeight}),
].filter((schema) => {
return ['buttonCode', 'buttonName', 'buttonIcon', 'buttonStatus'].includes(schema.field);
}).map(schema => {
if ('buttonCode' === schema.field) {
return {
...schema,
dynamicRules: () => [],
dynamicDisabled: () => true,
}
} else if ('buttonIcon' === schema.field) {
return {
...schema,
ifShow: () => true,
//
dynamicDisabled: ({values}) => ['bpm', 'edit', 'detail', 'delete'].includes(values.buttonCode),
}
}
return schema
});
// form
const [registerForm, {resetFields, setFieldsValue, validate}] = useForm({
// update-begin--author:liaozhiyang---date:20240618---forTV360X-1306
schemas: schemas,
// update-end--author:liaozhiyang---date:20240618---forTV360X-1306
showActionButtonGroup: false,
});
async function openFormModal(data: Recordable) {
isUpdate.value = data.isUpdate;
formRecord = {...(data.record ?? {})};
formModal.openModal();
await nextTick();
await resetFields();
setFieldsValue(formRecord);
}
//
function onEdit(record: Recordable) {
openFormModal({isUpdate: true, record});
}
function onCancel() {
closeModal();
}
async function onSubmit() {
try {
formModalProps.confirmLoading = true;
let values = await validate();
values = Object.assign({cgformHeadId: code.value}, formRecord, values);
console.log('onSubmit - values :', values)
const isUpdate = values.id != null
await saveOrUpdate(values, isUpdate);
reload();
formModal.closeModal();
} finally {
formModalProps.confirmLoading = false;
}
}
/**
* 操作栏
*/
function getTableAction(record: Recordable) {
return [
{
label: '编辑',
onClick: () => onEdit(record),
},
];
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,228 @@
<template>
<BasicModal @register="registerModal" title="自定义按钮" :width="1200" defaultFullscreen @cancel="onCancel">
<template #footer>
<a-button @click="onCancel">关闭</a-button>
<div v-if="aiTestMode" style="float: left">
<a-button @click="onGenButtons">生成测试数据</a-button>
</div>
</template>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button @click="onAdd" type="primary" preIcon="ant-design:plus">新增</a-button>
<a-button @click="onOpenBIButton" preIcon="ant-design:setting">管理内置按钮</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="onBatchDelete">
<a-icon type="delete" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button style="margin-left: 8px">
批量操作
<a-icon type="down" />
</a-button>
</a-dropdown>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 表单区域 -->
<BasicModal v-bind="formModalProps">
<a-spin :spinning="formModalProps.confirmLoading">
<BasicForm @register="registerForm" />
</a-spin>
</BasicModal>
</BasicModal>
<!-- 管理内置按钮 -->
<BuiltInButtonList @register="registerBIButtonModal" :record="record"/>
</template>
<script lang="ts">
import { ref, reactive, computed, nextTick, defineComponent } from 'vue';
import { useOnlineTest } from '../../hooks/aitest/useOnlineTest';
import { useListPage } from '/@/hooks/system/useListPage';
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { list, doBatchDelete, saveOrUpdate } from './button.api';
import { columns, formSchemas } from './button.data';
import BuiltInButtonList from './BuiltInButtonList.vue';
import {useMessage} from "@/hooks/web/useMessage";
export default defineComponent({
name: 'CustomButtonList',
components: { BasicModal, BasicTable, TableAction, BasicForm, BuiltInButtonList },
emits: ['register'],
setup() {
const {createMessage: $message} = useMessage();
const code = ref('');
const record = ref()
//
const { doRequest, doDeleteRecord, tableContext } = useListPage({
tableProps: {
api: (params) => list(code.value, params),
columns,
canResize: false,
useSearchForm: false,
beforeFetch(params) {
return Object.assign(params, { column: 'orderNum', order: 'asc' });
},
},
});
// table
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
//
const [registerModal, { closeModal }] = useModalInner(async (data) => {
code.value = data.row.id;
record.value = data.row
reload();
});
// useOnlineAiTest
const { aiTestMode, genButtons } = useOnlineTest({}, { reload }, null);
// form
const [registerFormModal, formModal] = useModal();
const isUpdate = ref(false);
const formModalProps = reactive({
onRegister: registerFormModal,
title: computed(() => (isUpdate?.value ? '修改' : '新增')),
width: 800,
confirmLoading: false,
onOk: onSubmit,
onCancel: formModal.closeModal,
});
let formRecord = {};
// form
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
// update-begin--author:liaozhiyang---date:20240618---forTV360X-1306
schemas: formSchemas({ redoModalHeight: formModal.redoModalHeight }),
// update-end--author:liaozhiyang---date:20240618---forTV360X-1306
showActionButtonGroup: false,
});
async function openFormModal(data) {
isUpdate.value = data.isUpdate;
formRecord = { ...(data.record ?? {}) };
formModal.openModal();
await nextTick();
await resetFields();
setFieldsValue(formRecord);
}
//
function onAdd() {
openFormModal({ isUpdate: false });
}
//
function onEdit(record) {
openFormModal({ isUpdate: true, record });
}
function onCancel() {
closeModal();
}
function onGenButtons() {
genButtons(code.value);
}
//
async function onBatchDelete() {
doRequest(() => doBatchDelete(selectedRowKeys.value));
}
async function onSubmit() {
try {
formModalProps.confirmLoading = true;
let values = await validate();
values = Object.assign({ cgformHeadId: code.value }, formRecord, values);
await saveOrUpdate(values, isUpdate.value);
reload();
formModal.closeModal();
} finally {
formModalProps.confirmLoading = false;
}
}
// -------------------- [Begin] ------------------
const [registerBIButtonModal, bIButtonModal] = useModal()
/**
* 打开管理内置按钮弹窗
*/
function onOpenBIButton() {
if (record.value?.tableType == 3) {
$message.warn('附表不支持管理内置按钮,请选择对应主表')
return
}
bIButtonModal.openModal(true, {});
}
// ---------------------- [End] ------------------
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: () => onEdit(record),
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record): ActionItem[] {
return [
{
label: '删除',
popConfirm: {
title: '确定删除吗?',
placement: 'left',
confirm: () => doDeleteRecord(() => doBatchDelete([record.id])),
},
},
];
}
return {
code,
record,
onAdd,
onEdit,
onBatchDelete,
aiTestMode,
onGenButtons,
registerModal,
registerTable,
selectedRowKeys,
rowSelection,
onCancel,
getTableAction,
getDropDownAction,
registerForm,
formModalProps,
registerBIButtonModal,
onOpenBIButton,
};
},
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,42 @@
import { defHttp } from '/@/utils/http/axios';
export enum Api {
list = '/online/cgform/button/list/',
delete = '/online/cgform/button/delete',
deleteBatch = '/online/cgform/button/deleteBatch',
save = '/online/cgform/button/add',
edit = '/online/cgform/button/edit',
builtInList = '/online/cgform/button/builtInList/',
}
export const list = (code: string, params) => defHttp.get({ url: Api.list + code, params });
//
export function doBatchDelete(idList: string[]) {
return defHttp.delete(
{
url: Api.deleteBatch,
params: {
ids: idList.join(','),
},
},
{ joinParamsToUrl: true }
);
}
/**
* 保存或者更新
*/
export const saveOrUpdate = (params, isUpdate: boolean) => {
if (isUpdate) {
return defHttp.put({ url: Api.edit, params });
} else {
return defHttp.post({ url: Api.save, params });
}
};
/**
* 加载内置按钮列表
*/
export const builtInList = (code: string, params) => defHttp.get({url: Api.builtInList + code, params});

View File

@ -0,0 +1,194 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
// @ts-ignore
import {getButtonIconRender} from "./button.data.tsx";
export const columns: BasicColumn[] = [
{ title: '按钮编码', align: 'center', dataIndex: 'buttonCode' },
{ title: '按钮名称', align: 'center', dataIndex: 'buttonName' },
{
title: '按钮样式',
align: 'center',
dataIndex: 'buttonStyle',
customRender({ text, record }) {
if (text === 'form') {
let p = record.optPosition;
return text + '(' + (p == '2' ? '底部' : '侧面') + ')';
} else {
return text;
}
},
},
{ title: '按钮类型', align: 'center', dataIndex: 'optType' },
{ title: '排序', align: 'center', dataIndex: 'orderNum' },
{
title: '按钮图标',
align: 'center',
dataIndex: 'buttonIcon',
customRender: ({text}) => {
return getButtonIconRender({text});
},
},
{ title: '表达式', align: 'center', dataIndex: 'exp' },
{
title: '按钮状态',
align: 'center',
dataIndex: 'buttonStatus',
customRender({ text }) {
if (text == 1) {
return '激活';
} else {
return '未激活';
}
},
},
];
export const formSchemas = ({ redoModalHeight }): FormSchema[] => {
return [
{
label: '按钮编码',
field: 'buttonCode',
component: 'Input',
required: true,
// update-begin--author:liaozhiyang---date:20240521---forTV360X-139
dynamicRules: () => {
return [
{
validator: (_, value) => {
//return Promise
return new Promise((resolve, reject) => {
const reg = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
if (reg.test(value)) {
resolve();
} else {
reject('编码只能包含字母、数字、下划线 (_) 和美元符号 ($)且不能以数字开头');
}
});
},
},
// update-begin--author:liaozhiyang---date:20240701---forTV360X-1693sqljava
{
validator: (_, value) => {
//return Promise
return new Promise((resolve, reject) => {
const exclude = ['add', 'edit', 'detail', 'delete', 'batch_delete', 'import', 'export', 'query', 'reset', 'bpm', 'super_query', 'form_confirm'];
if (exclude.includes(value)) {
reject('不可使用内置按钮编码,请在“管理内置按钮”中修改内置按钮');
} else {
resolve();
}
});
},
},
// update-end--author:liaozhiyang---date:20240701---forTV360X-1693sqljava
];
},
// update-end--author:liaozhiyang---date:20240521---forTV360X-139
},
{
label: '按钮名称',
field: 'buttonName',
component: 'Input',
required: true,
},
{
label: '按钮样式',
field: 'buttonStyle',
component: 'Select',
componentProps: {
options: [
{ label: 'Link', value: 'link' },
{ label: 'Button', value: 'button' },
{ label: 'Form', value: 'form' },
],
// update-begin--author:liaozhiyang---date:20240618---forTV360X-1306
onChange: () => {
redoModalHeight();
},
// update-end--author:liaozhiyang---date:20240618---forTV360X-1306
},
defaultValue: 'link',
},
{
label: '按钮位置',
field: 'optPosition',
component: 'Select',
componentProps: {
allowClear: false,
options: [
// { label: '', value: '1' },
{ label: '底部', value: '2' },
],
},
defaultValue: '2',
show: ({ model }) => model.buttonStyle === 'form',
},
{
label: '按钮类型',
field: 'optType',
component: 'Select',
componentProps: {
allowClear: false,
options: [
{ label: 'Js', value: 'js' },
{ label: 'Action', value: 'action' },
],
},
defaultValue: 'js',
},
{
label: '排序',
field: 'orderNum',
component: 'InputNumber',
componentProps: {
style: 'width: 100%',
},
},
{
label: '按钮图标',
field: 'buttonIcon',
// update-begin--author:liaozhiyang---date:20240528---forTV360X-136
component: 'IconPicker',
componentProps: {
clearSelect: true,
iconPrefixSave: false,
},
// update-end--author:liaozhiyang---date:20240528---forTV360X-136
ifShow: ({ values, model }) => {
if (values.buttonStyle == 'button' || values.buttonStyle == 'form') {
return true;
} else {
// model.buttonIcon = null;
return false;
}
},
},
{
label: '表达式',
field: 'exp',
component: 'Input',
// update-begin--author:liaozhiyang---date:20240603---forTV360X-89link
ifShow: ({ values, model }) => {
if (values.buttonStyle == 'link') {
return true;
} else {
model.exp = '';
return false;
}
},
// update-end--author:liaozhiyang---date:20240603---forTV360X-89link
},
{
label: '按钮状态',
field: 'buttonStatus',
component: 'RadioButtonGroup',
componentProps: {
options: [
{ label: '激活', value: '1' },
{ label: '未激活', value: '0' },
],
},
defaultValue: '1',
},
]
};

Some files were not shown because too many files have changed in this diff Show More