jeecgboot3.4.2版本发布,基础功能升级

pull/170/head^2
zhangdaiscott 2022-09-22 14:06:18 +08:00
parent df0441c8f5
commit b777ac0dc4
226 changed files with 4145 additions and 1922 deletions

View File

@ -141,3 +141,10 @@ export const getFileblob = (url, parameter) => {
{ isTransformResponse: false }
);
};
/**
* -
*/
export const uploadMyFile = (url, data) => {
return defHttp.uploadMyFile(url, data);
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -70,13 +70,14 @@
visible: unref(visibleRef),
};
opt.title = undefined;
const { isDetail, width, wrapClassName, getContainer } = opt;
let { isDetail, width, wrapClassName, getContainer } = opt;
if (isDetail) {
if (!width) {
opt.width = '100%';
}
const detailCls = `${prefixCls}__detail`;
opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
wrapClassName = opt['class'] ? opt['class'] : wrapClassName;
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) {
// TODO type error?

View File

@ -25,6 +25,7 @@ export const footerProps = {
},
};
export const basicProps = {
class: {type: [String, Object, Array]},
isDetail: { type: Boolean },
title: { type: String, default: '' },
loadingText: { type: String },

View File

@ -133,6 +133,8 @@ export interface DrawerProps extends DrawerFooterProps {
* The class name of the container of the Drawer dialog.
* @type string
*/
class?: string;
// 兼容老版本的写法后续可能会删除优先写class
wrapClassName?: string;
/**

View File

@ -264,14 +264,26 @@
* @updateBy:zyf
*/
function renderLabelHelpMessage() {
const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
//update-begin-author:taoyan date:2022-9-7 for: VUEN-2061online4 ..
//label
const { label, helpMessage, helpComponentProps, subLabel, labelLength } = props.schema;
let showLabel:string = (label+'')
if(labelLength && showLabel.length>4){
showLabel = showLabel.substr(0, labelLength);
}
const titleObj = {title: label}
const renderLabel = subLabel ? (
<span>
{label} <span class="text-secondary">{subLabel}</span>
</span>
) : (
label
labelLength ? (
<label {...titleObj}>{showLabel}</label>
) : (
label
)
);
//update-end-author:taoyan date:2022-9-7 for: VUEN-2061online4 ..
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
return renderLabel;

View File

@ -2,7 +2,12 @@
<div v-for="(param, index) in dynamicInput.params" :key="index" style="display: flex">
<a-input placeholder="请输入参数key" v-model:value="param.label" style="width: 30%; margin-bottom: 5px" @input="emitChange" />
<a-input placeholder="请输入参数value" v-model:value="param.value" style="width: 30%; margin: 0 0 5px 5px" @input="emitChange" />
<MinusCircleOutlined v-if="dynamicInput.params.length > min" class="dynamic-delete-button" @click="remove(param)" style="width: 50px"></MinusCircleOutlined>
<MinusCircleOutlined
v-if="dynamicInput.params.length > min"
class="dynamic-delete-button"
@click="remove(param)"
style="width: 50px"
></MinusCircleOutlined>
</div>
<div>
<a-button type="dashed" style="width: 60%" @click="add">

View File

@ -102,17 +102,17 @@
};
console.info(param);
loadTreeData(param).then((res) => {
if (res && res.length > 0) {
for (let i of res) {
i.value = i.key;
if (i.leaf == false) {
i.isLeaf = false;
} else if (i.leaf == true) {
i.isLeaf = true;
}
}
treeData.value = res;
}
if(res && res.length>0){
for (let i of res) {
i.value = i.key;
if (i.leaf == false) {
i.isLeaf = false;
} else if (i.leaf == true) {
i.isLeaf = true;
}
}
treeData.value = res;
}
});
}
@ -149,13 +149,12 @@
function asyncLoadTreeData(treeNode) {
let dataRef = treeNode.dataRef;
return new Promise((resolve) => {
if (treeNode.children.length > 0) {
return new Promise<void>((resolve) => {
if (treeNode.children && treeNode.children.length > 0) {
resolve();
return;
}
let pid = dataRef.key;
console.info(treeNode);
let param = {
pid: pid,
condition: props.condition,
@ -178,7 +177,6 @@
}
function addChildren(pid, children, treeArray) {
console.info('treeArray', treeArray);
if (treeArray && treeArray.length > 0) {
for (let item of treeArray) {
if (item.key == pid) {

View File

@ -11,7 +11,7 @@
export default defineComponent({
name: 'JCheckbox',
props: {
value: propTypes.oneOfType([propTypes.string, propTypes.number]),
value:propTypes.oneOfType([propTypes.string, propTypes.number]),
dictCode: propTypes.string,
options: {
type: Array,
@ -31,11 +31,11 @@
watchEffect(() => {
//update-begin-author:taoyan date:2022-7-4 for:issues/I5E7YX AUTO线
let temp = props.value;
if (!temp && temp !== 0) {
checkboxArray.value = [];
} else {
if(!temp && temp!==0){
checkboxArray.value = []
}else{
temp = temp + '';
checkboxArray.value = temp.split(',');
checkboxArray.value = temp.split(',')
}
//update-end-author:taoyan date:2022-7-4 for:issues/I5E7YX AUTO线
//update-begin-author:taoyan date:20220401 for: resetFields

View File

@ -188,16 +188,13 @@
}
);
//update-end-author:taoyan date:2022-5-9 for: codeEditor
//
watch(
() => props.language,
(val) => {
if (val && coder) {
coder.setOption('mode', val);
}
watch(()=>props.language, (val)=>{
if(val && coder){
coder.setOption('mode', val);
}
);
});
const getBindValue = Object.assign({}, unref(props), unref(attrs));
return {

View File

@ -22,8 +22,16 @@
<LoadingOutlined />
</template>
</a-input>
<a-select v-else :placeholder="placeholder" v-bind="attrs" v-model:value="state" :filterOption="handleFilterOption" :getPopupContainer="getPopupContainer" @change="handleChange">
<a-select-option v-if="showChooseOption" :value="undefined"></a-select-option>
<a-select
v-else
:placeholder="placeholder"
v-bind="attrs"
v-model:value="state"
:filterOption="handleFilterOption"
:getPopupContainer="getPopupContainer"
@change="handleChange"
>
<a-select-option v-if="showChooseOption" :value="null"></a-select-option>
<template v-for="item in dictOptions" :key="`${item.value}`">
<a-select-option :value="item.value">
<span style="display: inline-block; width: 100%" :title="item.label">
@ -35,13 +43,14 @@
</template>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, reactive, watchEffect, computed, unref, watch, onMounted } from 'vue';
import { defineComponent, PropType, ref, reactive, watchEffect, computed, unref, watch, onMounted, nextTick } from 'vue';
import { Form } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { initDictOptions } from '/@/utils/dict/index';
import { initDictOptions } from '/@/utils/dict';
import { get, omit } from 'lodash-es';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { CompTypeEnum } from '/@/enums/CompTypeEnum.ts';
import { CompTypeEnum } from '/@/enums/CompTypeEnum';
import { LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
@ -69,6 +78,7 @@
},
emits: ['options-change', 'change'],
setup(props, { emit, refs }) {
const { onFieldChange } = Form.useInjectFormItemContext();
const emitData = ref<any[]>([]);
const dictOptions = ref<any[]>([]);
const attrs = useAttrs();
@ -108,6 +118,7 @@
() => {
if (props.value === '') {
emit('change', '');
nextTick(() => onFieldChange());
}
}
);
@ -132,6 +143,7 @@
function handleChange(e) {
emitData.value = [e?.target?.value || e];
nextTick(() => onFieldChange());
}
/** 用于搜索下拉框中的内容 */

View File

@ -8,7 +8,14 @@
</a>
</template>
</a-input>
<EasyCronModal @register="registerModal" v-model:value="editCronValue" :exeStartTime="exeStartTime" :hideYear="hideYear" :remote="remote" :hideSecond="hideSecond" />
<EasyCronModal
@register="registerModal"
v-model:value="editCronValue"
:exeStartTime="exeStartTime"
:hideYear="hideYear"
:remote="remote"
:hideSecond="hideSecond"
/>
</div>
</template>

View File

@ -15,5 +15,7 @@
length: propTypes.number.def(25),
});
//
const showText = computed(() => (props.value ? (props.value.length > props.length ? props.value.slice(0, props.length) + '...' : props.value) : props.value));
const showText = computed(() =>
props.value ? (props.value.length > props.length ? props.value.slice(0, props.length) + '...' : props.value) : props.value
);
</script>

View File

@ -1,10 +1,6 @@
<template>
<div :class="disabled ? 'jeecg-form-container-disabled' : ''">
<fieldset :disabled="disabled">
<slot name="detail"></slot>
</fieldset>
<slot name="edit"></slot>
<fieldset disabled>
<slot></slot>
</fieldset>
</div>

View File

@ -15,7 +15,9 @@
<!--页脚-->
<template #footer>
<a-button @click="handleClose"></a-button>
<a-button type="primary" @click="handleImport" :disabled="uploadDisabled" :loading="uploading">{{ uploading ? '上传中...' : '开始上传' }}</a-button>
<a-button type="primary" @click="handleImport" :disabled="uploadDisabled" :loading="uploading">{{
uploading ? '上传中...' : '开始上传'
}}</a-button>
</template>
</BasicModal>
</div>

View File

@ -1,5 +1,11 @@
<template>
<a-popover trigger="contextmenu" v-model:visible="visible" :overlayClassName="`${prefixCls}-popover`" :getPopupContainer="getPopupContainer" :placement="position">
<a-popover
trigger="contextmenu"
v-model:visible="visible"
:overlayClassName="`${prefixCls}-popover`"
:getPopupContainer="getPopupContainer"
:placement="position"
>
<template #title>
<span>{{ title }}</span>
<span style="float: right" title="关闭">

View File

@ -13,7 +13,15 @@
<!-- update-begin-author:taoyan date:2022-5-31 for: VUEN-1157 popup 选中后有两个清除图标后边这个清除只是把输入框中数据清除实际值并没有清除 -->
</a-input>
<!--popup弹窗-->
<JPopupOnlReportModal @register="regModal" :code="code" :multi="multi" :sorter="sorter" :groupId="uniqGroupId" :param="param" @ok="callBack"></JPopupOnlReportModal>
<JPopupOnlReportModal
@register="regModal"
:code="code"
:multi="multi"
:sorter="sorter"
:groupId="uniqGroupId"
:param="param"
@ok="callBack"
></JPopupOnlReportModal>
</div>
</template>
<script lang="ts">

View File

@ -64,6 +64,15 @@
type: Function,
default: (node) => node.parentNode,
},
//change
immediateChange: propTypes.bool.def(false),
//update-begin-author:taoyan date:2022-8-15 for: VUEN-1971 online 1
//
params:{
type: Object,
default: ()=>{}
},
//update-end-author:taoyan date:2022-8-15 for: VUEN-1971 online 1
},
emits: ['change', 'update:value'],
setup(props, { emit, refs }) {
@ -116,11 +125,12 @@
const currentLoad = unref(lastLoad);
options.value = [];
loading.value = true;
let keywordInfo = getKeywordParam(value);
// codetable,text,code
defHttp
.get({
url: `/sys/dict/loadDict/${props.dict}`,
params: { keyword: value, pageSize: props.pageSize },
params: { keyword: keywordInfo, pageSize: props.pageSize },
})
.then((res) => {
loading.value = false;
@ -152,11 +162,21 @@
label: res,
};
selectedAsyncValue.value = { ...obj };
//update-begin-author:taoyan date:2022-8-11 for: change--online
if(props.immediateChange == true){
emit('change', value);
}
//update-end-author:taoyan date:2022-8-11 for: change--online
}
});
}
} else {
selectedValue.value = value.toString();
//update-begin-author:taoyan date:2022-8-11 for: change--online
if(props.immediateChange == true){
emit('change', value.toString());
}
//update-end-author:taoyan date:2022-8-11 for: change--online
}
}
@ -191,10 +211,11 @@
} else {
//
loading.value = true;
let keywordInfo = getKeywordParam('');
defHttp
.get({
url: `/sys/dict/loadDict/${dict}`,
params: { pageSize: pageSize, keyword: '' },
params: { pageSize: pageSize, keyword: keywordInfo },
})
.then((res) => {
loading.value = false;
@ -255,6 +276,20 @@
}
// update-end-author:taoyan date:20220407 for: getPopupContainer popContainer
}
//update-begin-author:taoyan date:2022-8-15 for: VUEN-1971 online 1
//
function getKeywordParam(text){
// [orderby:create_time,desc]
if(props.params && props.params.column && props.params.order){
let temp = text||''
return temp+'[orderby:'+props.params.column+','+props.params.order+']'
}else{
return text;
}
}
//update-end-author:taoyan date:2022-8-15 for: VUEN-1971 online 1
return {
attrs,
options,

View File

@ -13,7 +13,7 @@
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectTypes } from 'ant-design-vue/es/select';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectDept',
@ -29,13 +29,13 @@
},
emits: ['options-change', 'change', 'select', 'update:value'],
setup(props, { emit, refs }) {
const emitData = ref<object>();
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectTypes['options']>([]);
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<Recordable>({
value: [],

View File

@ -1,6 +1,15 @@
<!--字典下拉多选-->
<template>
<a-select :value="arrayValue" @change="onChange" mode="multiple" :filter-option="filterOption" :disabled="disabled" :placeholder="placeholder" allowClear :getPopupContainer="getParentContainer">
<a-select
:value="arrayValue"
@change="onChange"
mode="multiple"
:filter-option="filterOption"
:disabled="disabled"
:placeholder="placeholder"
allowClear
:getPopupContainer="getParentContainer"
>
<a-select-option v-for="(item, index) in dictOptions" :key="index" :getPopupContainer="getParentContainer" :value="item.value">
{{ item.text || item.label }}
</a-select-option>
@ -88,9 +97,19 @@
}
);
//
watch(()=>props.options, ()=>{
if (props.dictCode) {
// nothing to do
} else {
dictOptions.value = props.options;
}
});
function onChange(selectedValue) {
if (props.triggerChange) {
emit('change', selectedValue.join(props.spliter));
emit('update:value', selectedValue.join(props.spliter));
} else {
emit('input', selectedValue.join(props.spliter));
emit('update:value', selectedValue.join(props.spliter));

View File

@ -13,7 +13,7 @@
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectTypes } from 'ant-design-vue/es/select';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectPosition',
@ -39,13 +39,13 @@
},
emits: ['options-change', 'change'],
setup(props, { emit, refs }) {
const emitData = ref<object>();
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectTypes['options']>([]);
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<object>({
value: [],

View File

@ -13,7 +13,7 @@
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectTypes } from 'ant-design-vue/es/select';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectRole',
@ -39,13 +39,13 @@
},
emits: ['options-change', 'change'],
setup(props, { emit, refs }) {
const emitData = ref<object>();
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectTypes['options']>([]);
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<Recordable>({
value: [],

View File

@ -14,7 +14,7 @@
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectTypes } from 'ant-design-vue/es/select';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectUser',
@ -39,14 +39,14 @@
},
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit, refs }) {
const emitData = ref<object>();
setup(props, { emit }) {
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectTypes['options']>([]);
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<Recordable>({
value: [],

View File

@ -14,7 +14,7 @@
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectTypes } from 'ant-design-vue/es/select';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectUserByDept',
components: {
@ -35,13 +35,13 @@
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit, refs }) {
const emitData = ref<object>();
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectTypes['options']>([]);
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<object>({
value: [],
@ -107,6 +107,7 @@
state.value = values;
selectValues.value = values;
emit('update:value', values);
emit('options-change', options);
}
function handleChange(values) {

View File

@ -1,6 +1,14 @@
<template>
<div :class="prefixCls">
<a-select v-if="query" v-model:value="value" :options="selectOptions" :disabled="disabled" style="width: 100%" v-bind="attrs" @change="onSelectChange" />
<a-select
v-if="query"
v-model:value="value"
:options="selectOptions"
:disabled="disabled"
style="width: 100%"
v-bind="attrs"
@change="onSelectChange"
/>
<a-switch v-else v-model:checked="checked" :disabled="disabled" v-bind="attrs" @change="onSwitchChange" />
</div>
</template>

View File

@ -43,6 +43,7 @@
pidValue: propTypes.string.def(''),
//update-end---author:wangshuai ---date:20220620 forJTreeSelectpidValue--------------
hasChildField: propTypes.string.def(''),
converIsLeafVal: propTypes.integer.def(1),
condition: propTypes.string.def(''),
multiple: propTypes.bool.def(false),
loadTriggleChange: propTypes.bool.def(false),
@ -96,7 +97,11 @@
*/
async function loadItemByCode() {
if (!props.value || props.value == '0') {
treeValue.value = null;
if(props.multiple){
treeValue.value = [];
}else{
treeValue.value = null;
}
} else {
let params = { key: props.value };
let result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
@ -137,6 +142,7 @@
pid: props.pidValue,
pidField: props.pidField,
hasChildField: props.hasChildField,
converIsLeafVal: props.converIsLeafVal,
condition: props.condition,
tableName: unref(tableName),
text: unref(text),
@ -166,6 +172,7 @@
pid: pid,
pidField: props.pidField,
hasChildField: props.hasChildField,
converIsLeafVal: props.converIsLeafVal,
condition: props.condition,
tableName: unref(tableName),
text: unref(text),

View File

@ -2,7 +2,15 @@
<div>
<a-row class="j-select-row" type="flex" :gutter="8">
<a-col class="left" :class="{ full: !showButton }">
<a-select ref="select" v-model:value="selectValues.value" :mode="multiple" :open="false" :options="options" @change="handleChange" style="width: 100%" />
<a-select
ref="select"
v-model:value="selectValues.value"
:mode="multiple"
:open="false"
:options="options"
@change="handleChange"
style="width: 100%"
/>
</a-col>
<a-col v-if="showButton" class="right">
<a-button type="primary" @click="openModal"></a-button>

View File

@ -9,13 +9,13 @@
v-bind="getBindValue"
@select="onSelect"
@check="onCheck"
:replaceFields="replaceFields"
:fieldNames="fieldNames"
:checkedKeys="checkedKeys"
:checkStrictly="getCheckStrictly"
/>
<!--树操作部分-->
<template #insertFooter>
<a-dropdown placement="topCenter">
<a-dropdown placement="top">
<template #overlay>
<a-menu>
<a-menu-item v-if="multiple" key="1" @click="checkALL(true)"></a-menu-item>
@ -61,13 +61,15 @@
const treeRef = ref<Nullable<TreeActionType>>(null);
const getBindValue = Object.assign({}, unref(props), unref(attrs));
const queryUrl = getQueryUrl();
const [{ visibleChange, checkedKeys, getCheckStrictly, getSelectTreeData, onCheck, onLoadData, treeData, checkALL, expandAll, onSelect }] = useTreeBiz(treeRef, queryUrl, getBindValue);
const [{ visibleChange, checkedKeys, getCheckStrictly, getSelectTreeData, onCheck, onLoadData, treeData, checkALL, expandAll, onSelect }] =
useTreeBiz(treeRef, queryUrl, getBindValue);
const searchInfo = ref(props.params);
const tree = ref([]);
//treeNodekeytreeData
const replaceFields = {
const fieldNames = {
key: props.rowKey,
};
// {children:'children', title:'title', key:'key' }
/**
* 确定选择
*/
@ -98,7 +100,7 @@
onSelect,
checkALL,
expandAll,
replaceFields,
fieldNames,
checkedKeys,
register,
getBindValue,

View File

@ -103,8 +103,32 @@
const tableScroll = ref({ x: true });
const getBindValue = Object.assign({}, unref(props), unref(attrs));
const [
{ visibleChange, loadColumnsInfo, dynamicParamHandler, loadData, handleChangeInTable, combineRowKey, clickThenCheck, filterUnuseSelect, getOkSelectRows },
{ visible, rowSelection, checkedKeys, selectRows, pagination, dataSource, columns, loading, title, iSorter, queryInfo, queryParam, dictOptions },
{
visibleChange,
loadColumnsInfo,
dynamicParamHandler,
loadData,
handleChangeInTable,
combineRowKey,
clickThenCheck,
filterUnuseSelect,
getOkSelectRows,
},
{
visible,
rowSelection,
checkedKeys,
selectRows,
pagination,
dataSource,
columns,
loading,
title,
iSorter,
queryInfo,
queryParam,
dictOptions,
},
] = usePopBiz(getBindValue);
const showSearchFlag = computed(() => unref(queryInfo) && unref(queryInfo).length > 0);

View File

@ -1,7 +1,16 @@
<!--职务选择框-->
<template>
<div>
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="900px" wrapClassName="j-user-select-modal" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
<BasicModal
v-bind="$attrs"
@register="register"
:title="modalTitle"
width="900px"
wrapClassName="j-user-select-modal"
@ok="handleOk"
destroyOnClose
@visible-change="visibleChange"
>
<a-row>
<a-col :span="showSelected ? 18 : 24">
<BasicTable
@ -17,7 +26,12 @@
></BasicTable>
</a-col>
<a-col :span="showSelected ? 6 : 0">
<BasicTable v-bind="selectedTable" :dataSource="selectRows" :useSearchForm="true" :formConfig="{ showActionButtonGroup: false, baseRowStyle: { minHeight: '40px' } }">
<BasicTable
v-bind="selectedTable"
:dataSource="selectRows"
:useSearchForm="true"
:formConfig="{ showActionButtonGroup: false, baseRowStyle: { minHeight: '40px' } }"
>
<!--操作栏-->
<template #action="{ record }">
<a href="javascript:void(0)" @click="handleDeleteSelected(record)"><Icon icon="ant-design:delete-outlined"></Icon></a>
@ -67,7 +81,10 @@
rowKey: 'code',
};
const getBindValue = Object.assign({}, unref(props), unref(attrs), config);
const [{ rowSelection, visibleChange, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(getPositionList, getBindValue);
const [{ rowSelection, visibleChange, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(
getPositionList,
getBindValue
);
const searchInfo = ref(props.params);
//form
const formConfig = {

View File

@ -1,7 +1,16 @@
<!--用户选择框-->
<template>
<div>
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="900px" wrapClassName="j-user-select-modal" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
<BasicModal
v-bind="$attrs"
@register="register"
:title="modalTitle"
width="900px"
wrapClassName="j-user-select-modal"
@ok="handleOk"
destroyOnClose
@visible-change="visibleChange"
>
<a-row>
<a-col :span="showSelected ? 18 : 24">
<BasicTable
@ -22,7 +31,12 @@
</BasicTable>
</a-col>
<a-col :span="showSelected ? 6 : 0">
<BasicTable v-bind="selectedTable" :dataSource="selectRows" :useSearchForm="true" :formConfig="{ showActionButtonGroup: false, baseRowStyle: { minHeight: '40px' } }">
<BasicTable
v-bind="selectedTable"
:dataSource="selectRows"
:useSearchForm="true"
:formConfig="{ showActionButtonGroup: false, baseRowStyle: { minHeight: '40px' } }"
>
<!--操作栏-->
<template #action="{ record }">
<a href="javascript:void(0)" @click="handleDeleteSelected(record)"><Icon icon="ant-design:delete-outlined"></Icon></a>
@ -88,7 +102,10 @@
size: 'small',
};
const getBindValue = Object.assign({}, unref(props), unref(attrs), config);
const [{ rowSelection, visibleChange, selectValues, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(getUserList, getBindValue);
const [{ rowSelection, visibleChange, selectValues, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(
getUserList,
getBindValue
);
const searchInfo = ref(props.params);
//form
const formConfig = {

View File

@ -145,7 +145,9 @@ export interface FormSchema {
// render component
component: ComponentType;
// Component parameters
componentProps?: ((opt: { schema: FormSchema; tableAction: TableActionType; formActionType: FormActionType; formModel: Recordable }) => Recordable) | object;
componentProps?:
| ((opt: { schema: FormSchema; tableAction: TableActionType; formActionType: FormActionType; formModel: Recordable }) => Recordable)
| object;
// Required
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
@ -193,6 +195,9 @@ export interface FormSchema {
// 这个属性自定义的 用于自定义的业务 比如在表单打开的时候修改表单的禁用状态但是又不能重写componentProps因为他的内容太多了所以使用dynamicDisabled和buss实现
buss?: any;
//label字数控制label宽度
labelLength?: number
}
export interface HelpComponentProps {
maxWidth: string;

View File

@ -1,10 +1,10 @@
<template>
<a-input :disabled="disabled" :style="{ width }" :placeholder="t('component.icon.placeholder')" :class="prefixCls" v-model:value="currentSelect">
<a-input :disabled="disabled" :style="{ width }" readOnly :placeholder="t('component.icon.placeholder')" :class="prefixCls" v-model:value="currentSelect">
<template #addonAfter>
<a-popover placement="bottomLeft" trigger="click" v-model="visible" :overlayClassName="`${prefixCls}-popover`">
<template #title>
<div class="flex justify-between">
<a-input :placeholder="t('component.icon.search')" @change="debounceHandleSearchChange" allowClear />
<a-input :placeholder="t('component.icon.search')" @change="debounceHandleSearchChange" allowClear />
</div>
</template>

View File

@ -18,7 +18,16 @@
</div>
</template>
<div class="j-vxe-image-upload">
<a-upload name="file" :data="{ isup: 1 }" :multiple="false" :action="uploadAction" :headers="uploadHeaders" :showUploadList="false" v-bind="cellProps" @change="handleChangeUpload">
<a-upload
name="file"
:data="{ isup: 1 }"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button v-if="!hasFile" preIcon="ant-design:upload">{{ originColumn.btnText || '' }}</a-button>
<div v-if="hasFile && imgList.length < maxCount" class="j-vxe-plus" @click="">
<Icon icon="ant-design:plus" />

View File

@ -32,7 +32,8 @@ export const DictSearchInputCell = defineComponent({
props: useJVxeCompProps(),
setup(props: JVxeComponent.Props) {
const { createMessage } = useMessage();
const { dict, loading, isAsync, options, innerOptions, originColumn, cellProps, innerSelectValue, handleChangeCommon } = useSelectDictSearch(props);
const { dict, loading, isAsync, options, innerOptions, originColumn, cellProps, innerSelectValue, handleChangeCommon } =
useSelectDictSearch(props);
const hasRequest = ref(false);
// 提示信息
const tipsContent = computed(() => {

View File

@ -1,15 +1,7 @@
<template>
<Modal v-bind="getBindValue" @cancel="handleCancel">
<template #closeIcon v-if="!$slots.closeIcon">
<ModalClose
:canFullscreen="getProps.canFullscreen"
:fullScreen="fullScreenRef"
:commentSpan="commentSpan"
:enableComment="getProps.enableComment"
@comment="handleComment"
@cancel="handleCancel"
@fullscreen="handleFullScreen"
/>
<ModalClose :canFullscreen="getProps.canFullscreen" :fullScreen="fullScreenRef" :commentSpan="commentSpan" :enableComment="getProps.enableComment" @comment="handleComment" @cancel="handleCancel" @fullscreen="handleFullScreen" />
</template>
<template #title v-if="!$slots.title">
@ -25,8 +17,8 @@
</template>
<!-- update-begin-author:taoyan date:2022-7-18 for: modal弹窗 支持评论 slot -->
<a-row>
<a-col :span="24 - commentSpan" class="jeecg-modal-content">
<a-row class="jeecg-modal-wrapper">
<a-col :span="24-commentSpan" class="jeecg-modal-content">
<ModalWrapper
:useWrapper="getProps.useWrapper"
:footerOffset="wrapperFooterOffset"
@ -40,18 +32,18 @@
:modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
@ext-height="handleExtHeight"
@height-change="handleHeightChange"
>
@height-change="handleHeightChange">
<slot></slot>
</ModalWrapper>
</a-col>
<a-col :span="commentSpan" class="jeecg-comment-outer">
<slot name="comment"></slot>
</a-col>
</a-row>
<!-- update-end-author:taoyan date:2022-7-18 for: modal弹窗 支持评论 slot -->
<template #[item]="data" v-for="item in Object.keys(omit($slots, 'default'))">
<slot :name="item" v-bind="data || {}"></slot>
</template>
@ -220,18 +212,14 @@
//update-begin-author:taoyan date:2022-7-18 for: modal slot
const commentSpan = ref(0);
watch(
() => props.enableComment,
(flag) => {
handleComment(flag);
},
{ immediate: true }
);
function handleComment(flag) {
if (flag === true) {
commentSpan.value = 6;
} else {
commentSpan.value = 0;
watch(()=>props.enableComment, (flag)=>{
handleComment(flag)
}, {immediate:true});
function handleComment(flag){
if(flag=== true){
commentSpan.value = 6
}else{
commentSpan.value = 0
}
}
//update-end-author:taoyan date:2022-7-18 for: modal slot
@ -252,21 +240,29 @@
handleTitleDbClick,
getWrapperHeight,
commentSpan,
handleComment,
handleComment
};
},
});
</script>
<style lang="less">
/*update-begin-author:taoyan date:2022-7-27 for:modal评论区域样式*/
.jeecg-comment-outer {
border-left: 1px solid #f0f0f0;
.ant-tabs-nav-wrap {
/* text-align: center;*/
.ant-tabs-nav-wrap{
/* text-align: center;*/
}
}
.jeecg-modal-content {
> .scroll-container {
.jeecg-modal-content{
>.scroll-container{
padding: 14px;
}
}
</style>
/*update-end-author:taoyan date:2022-7-27 for:modal评论区域样式*/
// wrapper100%
.jeecg-modal-wrapper,
.jeecg-modal-content {
height: 100%;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div :class="getClass" :style="{ width: closeWidth + 'px' }">
<template v-if="canFullscreen">
<div :class="getClass">
<template v-if="fullScreenStatus">
<Tooltip :title="t('component.modal.restore')" placement="bottom" v-if="fullScreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" />
</Tooltip>
@ -11,14 +11,14 @@
<!-- 是否开启评论区域 -->
<template v-if="enableComment">
<Tooltip title="关闭" placement="bottom" v-if="commentSpan > 0">
<RightSquareOutlined @click="handleCloseComment" style="font-size: 16px" />
<Tooltip title="关闭" placement="bottom" v-if="commentSpan>0">
<RightSquareOutlined @click="handleCloseComment" style="font-size: 16px"/>
</Tooltip>
<Tooltip title="展开" placement="bottom" v-else>
<LeftSquareOutlined @click="handleOpenComment" style="font-size: 16px" />
<LeftSquareOutlined @click="handleOpenComment" style="font-size: 16px"/>
</Tooltip>
</template>
<Tooltip :title="t('component.modal.close')" placement="bottom">
<CloseOutlined @click="handleCancel" />
</Tooltip>
@ -62,29 +62,19 @@
function handleFullScreen(e: Event) {
e?.stopPropagation();
e?.preventDefault();
if (props.commentSpan == 0 || props.enableComment == false) {
if(props.commentSpan==0 || props.enableComment == false){
emit('fullscreen');
}
}
//update-begin-author:taoyan date:2022-7-18 for:
const closeWidth = computed(() => {
if (props.canFullscreen && props.enableComment) {
return 140;
} else {
return 96;
}
});
//update-end-author:taoyan date:2022-7-18 for:
/**
* 开启评论区域
* @param e
*/
function handleOpenComment(e: Event) {
function handleOpenComment(e: Event){
e?.stopPropagation();
e?.preventDefault();
if (props.fullScreen == false) {
if(props.fullScreen==false){
emit('fullscreen');
}
emit('comment', true);
@ -94,21 +84,32 @@
* 关闭评论区域
* @param e
*/
function handleCloseComment(e: Event) {
function handleCloseComment(e: Event){
e?.stopPropagation();
e?.preventDefault();
emit('comment', false);
}
/**
* 有评论的时候不需要设置全屏
*/
const fullScreenStatus = computed(()=>{
if(props.enableComment===true){
return false
}else{
return props.canFullscreen;
}
});
return {
t,
getClass,
prefixCls,
handleCancel,
handleFullScreen,
closeWidth,
handleOpenComment,
handleCloseComment,
fullScreenStatus
};
},
});
@ -120,10 +121,6 @@
height: 95%;
align-items: center;
.ant-modal-close-x {
width: 140px !important;
}
> span {
margin-left: 48px;
font-size: 16px;
@ -141,12 +138,6 @@
font-weight: 700;
}
}
/** 展开/关闭 评论图标样式*/
> span:nth-child(2) {
&:hover {
font-weight: 700;
}
}
}
& span:nth-child(1) {
@ -157,20 +148,12 @@
color: @primary-color;
}
}
/** 展开/关闭 评论图标样式*/
& span:nth-child(2) {
display: inline-block;
padding: 10px 10px 10px 0;
&:hover {
color: @primary-color;
}
}
& span:last-child {
padding: 10px 10px 10px 0;
&:hover {
color: @error-color;
}
}
}
</style>
</style>

View File

@ -7,11 +7,18 @@
bottom: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100%;
height: 100% !important;
max-width: 100% !important;
max-height: 100% !important;
&-content {
height: 100%;
}
.ant-modal-header,
.@{namespace}-basic-title {
cursor: default !important;
}
}
}
@ -70,8 +77,8 @@
&-close-x {
display: inline-block;
/*width: 96px;*/
width: auto;
width: 96px;
/* width: auto;*/
height: 56px;
line-height: 56px;
}

View File

@ -194,6 +194,8 @@ export interface ModalProps {
* @type number
*/
zIndex?: number;
enableComment?: boolean;
}
export interface ModalWrapperProps {

View File

@ -1,6 +1,12 @@
<template>
<div :class="getClass" ref="wrapperRef">
<PageHeader :ghost="ghost" :title="title" v-bind="omit($attrs, 'class')" ref="headerRef" v-if="content || $slots.headerContent || title || getHeaderSlots.length">
<PageHeader
:ghost="ghost"
:title="title"
v-bind="omit($attrs, 'class')"
ref="headerRef"
v-if="content || $slots.headerContent || title || getHeaderSlots.length"
>
<template #default>
<template v-if="content">
{{ content }}
@ -74,7 +80,13 @@
});
const getUpwardSpace = computed(() => props.upwardSpace);
const { redoHeight, setCompensation, contentHeight } = useContentHeight(getIsContentFullHeight, wrapperRef, [headerRef, footerRef], [contentRef], getUpwardSpace);
const { redoHeight, setCompensation, contentHeight } = useContentHeight(
getIsContentFullHeight,
wrapperRef,
[headerRef, footerRef],
[contentRef],
getUpwardSpace
);
setCompensation({ useLayoutFooter: true, elements: [footerRef] });
const getClass = computed(() => {

View File

@ -28,7 +28,8 @@ export default defineComponent({
}
window.getSelection()?.removeAllRanges();
startDrag(e);
barStore.value[bar.value.axis] = e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
barStore.value[bar.value.axis] =
e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
};
const clickTrackHandler = (e: any) => {

View File

@ -1,5 +1,12 @@
<template>
<Menu v-bind="getBindValues" :activeName="activeName" :openNames="getOpenKeys" :class="prefixCls" :activeSubMenuNames="activeSubMenuNames" @select="handleSelect">
<Menu
v-bind="getBindValues"
:activeName="activeName"
:openNames="getOpenKeys"
:class="prefixCls"
:activeSubMenuNames="activeSubMenuNames"
@select="handleSelect"
>
<template v-for="item in items" :key="item.path">
<SimpleSubMenu :item="item" :parent="true" :collapsedShowTitle="collapsedShowTitle" :collapse="collapse" />
</template>

View File

@ -80,7 +80,12 @@
});
function menuHasChildren(menuTreeItem: Menu): boolean {
return !menuTreeItem.meta?.hideChildrenInMenu && Reflect.has(menuTreeItem, 'children') && !!menuTreeItem.children && menuTreeItem.children.length > 0;
return (
!menuTreeItem.meta?.hideChildrenInMenu &&
Reflect.has(menuTreeItem, 'children') &&
!!menuTreeItem.children &&
menuTreeItem.children.length > 0
);
}
return {

View File

@ -14,15 +14,22 @@
</template>
</BasicForm>
<Table ref="tableElRef" v-bind="getBindValues" :rowClassName="getRowClassName" v-show="getEmptyDataIsShowTable" @change="handleTableChange">
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
<HeaderCell :column="column" />
</template>
</Table>
<!-- antd v3 升级兼容阻止数据的收集防止控制台报错 -->
<!-- https://antdv.com/docs/vue/migration-v3-cn -->
<a-form-item-rest>
<Table ref="tableElRef" v-bind="getBindValues" :rowClassName="getRowClassName" v-show="getEmptyDataIsShowTable" @change="handleTableChange">
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #headerCell="{ column }">
<HeaderCell :column="column" />
</template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
<slot name="bodyCell" v-bind="data || {}"></slot>
</template>
</Table>
</a-form-item-rest>
</div>
</template>
<script lang="ts">
@ -97,13 +104,16 @@
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
watchEffect(() => {
unref(isFixedHeightPage) && props.canResize && warn("'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)");
unref(isFixedHeightPage) &&
props.canResize &&
warn("'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)");
});
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } = usePagination(getProps);
const { getRowSelection, getRowSelectionRef, getSelectRows, clearSelectedRowKeys, getSelectRowKeys, deleteSelectRowByKey, setSelectedRowKeys } = useRowSelection(getProps, tableData, emit);
const { getRowSelection, getRowSelectionRef, getSelectRows, clearSelectedRowKeys, getSelectRowKeys, deleteSelectRowByKey, setSelectedRowKeys } =
useRowSelection(getProps, tableData, emit);
const {
handleTableChange: onTableChange,
@ -141,7 +151,10 @@
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
}
const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(getProps, getPaginationInfo);
const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getProps,
getPaginationInfo
);
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef, getColumnsRef, getRowSelectionRef, getDataSourceRef);
@ -381,7 +394,7 @@
border: none !important;
}
.ant-table-body {
.ant-table-content {
overflow-x: hidden !important;
// overflow-y: scroll !important;
}

View File

@ -29,7 +29,7 @@
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle);
const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
@ -42,7 +42,7 @@
.@{prefix-cls} {
&__help {
margin-left: 8px;
color: rgba(0, 0, 0, 0.65) !important;
color: rgb(0 0 0 / 65%) !important;
}
}
</style>

View File

@ -13,7 +13,7 @@
<div :class="`${prefixCls}__toolbar`">
<slot name="toolbar"></slot>
<Divider type="vertical" v-if="$slots.toolbar && showTableSetting" />
<TableSetting :class="`${prefixCls}__toolbar-desktop`" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
<TableSetting :class="`${prefixCls}__toolbar-desktop`" style="white-space: nowrap;" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
<a-popover :overlayClassName="`${prefixCls}__toolbar-mobile`" trigger="click" placement="left" :getPopupContainer="(n) => n.parentElement">
<template #content>
<TableSetting mode="mobile" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />

View File

@ -13,7 +13,10 @@ export interface ComponentProps {
getPopupContainer?: Fn;
}
export const CellComponent: FunctionalComponent = ({ component = 'Input', rule = true, ruleMessage, popoverVisible, getPopupContainer }: ComponentProps, { attrs }) => {
export const CellComponent: FunctionalComponent = (
{ component = 'Input', rule = true, ruleMessage, popoverVisible, getPopupContainer }: ComponentProps,
{ attrs }
) => {
const Comp = componentMap.get(component) as typeof defineComponent;
const DefaultComp = h(Comp, attrs);

View File

@ -7,7 +7,7 @@
v-model:visible="popoverVisible"
placement="bottomLeft"
trigger="click"
@visibleChange="handleVisibleChange"
@visible-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer"
>
@ -93,6 +93,7 @@
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import { defineComponent, ref, reactive, toRefs, watchEffect, nextTick, unref, computed } from 'vue';
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon';
import { ScrollContainer } from '/@/components/Container';
@ -194,10 +195,12 @@
);
watchEffect(() => {
const columns = table.getColumns();
if (columns.length && !state.isInit) {
init();
}
setTimeout(() => {
const columns = table.getColumns();
if (columns.length && !state.isInit) {
init();
}
}, 0);
});
watchEffect(() => {
@ -253,7 +256,7 @@
}
// checkAll change
function onCheckAllChange(e: ChangeEvent) {
function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value);
if (e.target.checked) {
state.checkedList = checkList;
@ -338,14 +341,14 @@
}
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: ChangeEvent) {
function handleIndexCheckChange(e: CheckboxChangeEvent) {
table.setProps({
showIndexColumn: e.target.checked,
});
}
// Control whether the check box is displayed
function handleSelectCheckChange(e: ChangeEvent) {
function handleSelectCheckChange(e: CheckboxChangeEvent) {
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
});
@ -372,7 +375,8 @@
function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns);
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible = columns.findIndex((c: BasicColumn | string) => c === col.value || (typeof c !== 'string' && c.dataIndex === col.value)) !== -1;
const visible =
columns.findIndex((c: BasicColumn | string) => c === col.value || (typeof c !== 'string' && c.dataIndex === col.value)) !== -1;
return { dataIndex: col.value, fixed: col.fixed, visible };
});

View File

@ -4,7 +4,7 @@
<span>{{ t('component.table.settingDens') }}</span>
</template>
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer">
<Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined />
<template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">

View File

@ -1,7 +1,7 @@
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import type { ComputedRef } from 'vue';
import { computed, Ref, ref, toRaw, unref, watch } from 'vue';
import { computed, Ref, ref, toRaw, unref, watch, reactive } from 'vue';
import { renderEditCell } from '../components/editable';
import { usePermission } from '/@/hooks/web/usePermission';
import { useI18n } from '/@/hooks/web/useI18n';
@ -141,11 +141,11 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>, getPagination
return hasPermission(column.auth) && isIfShow(column);
})
.map((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag, title: metaTitle } = column;
const { slots, customRender, format, edit, editRow, flag, title: metaTitle } = column;
if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title;
// column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title as string;
Reflect.deleteProperty(column, 'title');
}
//update-begin-author:taoyan date:20211203 for:【online报表】分组标题显示错误都显示成了联系信息 LOWCOD-2343
@ -165,7 +165,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>, getPagination
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
return column;
return reactive(column);
});
});

View File

@ -25,7 +25,10 @@ function getKey(record: Recordable, rowKey: string | ((record: Record<string, an
return null;
}
export function useCustomRow(propsRef: ComputedRef<BasicTableProps>, { setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options) {
export function useCustomRow(
propsRef: ComputedRef<BasicTableProps>,
{ setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options
) {
const customRow = (record: Recordable, index: number) => {
return {
onClick: (e: Event) => {

View File

@ -22,7 +22,11 @@ interface SearchState {
sortInfo: Recordable;
filterInfo: Record<string, string[]>;
}
export function useDataSource(propsRef: ComputedRef<BasicTableProps>, { getPaginationInfo, setPagination, setLoading, validate, clearSelectedRowKeys, tableData }: ActionType, emit: EmitType) {
export function useDataSource(
propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, validate, clearSelectedRowKeys, tableData }: ActionType,
emit: EmitType
) {
const searchState = reactive<SearchState>({
sortInfo: {},
filterInfo: {},

View File

@ -17,11 +17,8 @@ export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, tableDat
return {
selectedRowKeys: unref(selectedRowKeysRef),
hideDefaultSelections: false,
onChange: (selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys);
// selectedRowKeysRef.value = selectedRowKeys;
// selectedRowRef.value = selectedRows;
},
...omit(rowSelection, ['onChange']),
};
@ -63,9 +60,13 @@ export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, tableDat
function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys;
const allSelectedRows = findNodeAll(toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))), (item) => rowKeys.includes(item[unref(getRowKey) as string]), {
children: propsRef.value.childrenColumnName ?? 'children',
});
const allSelectedRows = findNodeAll(
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
(item) => rowKeys.includes(item[unref(getRowKey) as string]),
{
children: propsRef.value.childrenColumnName ?? 'children',
}
);
const trueSelectedRows: any[] = [];
rowKeys.forEach((key: string) => {
const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);

View File

@ -34,13 +34,12 @@ export function useTableFooter(
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
const bodyDom = bodyDomList[0];
const bodyDom = tableEl.$el.querySelector('.ant-table-content');
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-body') as HTMLDivElement;
const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-content') as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
},

View File

@ -4,7 +4,12 @@ import { unref, computed } from 'vue';
import type { FormProps } from '/@/components/Form';
import { isFunction } from '/@/utils/is';
export function useTableForm(propsRef: ComputedRef<BasicTableProps>, slots: Slots, fetch: (opt?: FetchParams | undefined) => Promise<void>, getLoading: ComputedRef<boolean | undefined>) {
export function useTableForm(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
fetch: (opt?: FetchParams | undefined) => Promise<void>,
getLoading: ComputedRef<boolean | undefined>
) {
const getFormProps = computed((): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
const { submitButtonOptions } = formConfig || {};
@ -13,6 +18,7 @@ export function useTableForm(propsRef: ComputedRef<BasicTableProps>, slots: Slot
...formConfig,
submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
compact: true,
autoSubmitOnEnter: true,
};
});

View File

@ -1,7 +1,8 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { ColumnProps, TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';

View File

@ -1,6 +1,13 @@
<template>
<div :class="prefixCls" :style="{ width: containerWidth }">
<ImgUpload :fullscreen="fullscreen" @uploading="handleImageUploading" @done="handleDone" v-if="showImageUpload" v-show="editorRef" :disabled="disabled" />
<ImgUpload
:fullscreen="fullscreen"
@uploading="handleImageUploading"
@done="handleDone"
v-if="showImageUpload"
v-show="editorRef"
:disabled="disabled"
/>
<textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline"></textarea>
<slot v-else></slot>
</div>

View File

@ -1,6 +1,15 @@
<template>
<div :class="[prefixCls, { fullscreen }]">
<Upload name="file" multiple @change="handleChange" :action="uploadUrl" :showUploadList="false" :data="getBizData()" :headers="getheader()" accept=".jpg,.jpeg,.gif,.png,.webp">
<Upload
name="file"
multiple
@change="handleChange"
:action="uploadUrl"
:showUploadList="false"
:data="getBizData()"
:headers="getheader()"
accept=".jpg,.jpeg,.gif,.png,.webp"
>
<a-button type="primary" v-bind="{ ...getButtonProps }">
{{ t('component.upload.imgUpload') }}
</a-button>

View File

@ -1,5 +1,6 @@
import BasicTree from './src/Tree.vue';
import BasicTree from './src/BasicTree.vue';
import './style';
export { BasicTree };
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
export * from './src/typing';
export * from './src/types/tree';

View File

@ -0,0 +1,456 @@
<script lang="tsx">
import type { CSSProperties } from 'vue';
import type {
FieldNames,
TreeState,
TreeItem,
KeyType,
CheckKeys,
TreeActionType,
} from './types/tree';
import {
defineComponent,
reactive,
computed,
unref,
ref,
watchEffect,
toRaw,
watch,
onMounted,
} from 'vue';
import TreeHeader from './components/TreeHeader.vue';
import { Tree, Spin, Empty } from 'ant-design-vue';
import { TreeIcon } from './TreeIcon';
import { ScrollContainer } from '/@/components/Container';
import { omit, get, difference, cloneDeep } from 'lodash-es';
import { isArray, isBoolean, isEmpty, isFunction } from '/@/utils/is';
import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper';
import { filter, treeToList, eachTree } from '/@/utils/helper/treeHelper';
import { useTree } from './hooks/useTree';
import { useContextMenu } from '/@/hooks/web/useContextMenu';
import { CreateContextOptions } from '/@/components/ContextMenu';
import { treeEmits, treeProps } from './types/tree';
import { createBEM } from '/@/utils/bem';
export default defineComponent({
name: 'BasicTree',
inheritAttrs: false,
props: treeProps,
emits: treeEmits,
setup(props, { attrs, slots, emit, expose }) {
const [bem] = createBEM('tree');
const state = reactive<TreeState>({
checkStrictly: props.checkStrictly,
expandedKeys: props.expandedKeys || [],
selectedKeys: props.selectedKeys || [],
checkedKeys: props.checkedKeys || [],
});
const searchState = reactive({
startSearch: false,
searchText: '',
searchData: [] as TreeItem[],
});
const treeDataRef = ref<TreeItem[]>([]);
const [createContextMenu] = useContextMenu();
const getFieldNames = computed((): Required<FieldNames> => {
const { fieldNames } = props;
return {
children: 'children',
title: 'title',
key: 'key',
...fieldNames,
};
});
const getBindValues = computed(() => {
let propsData = {
blockNode: true,
...attrs,
...props,
expandedKeys: state.expandedKeys,
selectedKeys: state.selectedKeys,
checkedKeys: state.checkedKeys,
checkStrictly: state.checkStrictly,
fieldNames: unref(getFieldNames),
'onUpdate:expandedKeys': (v: KeyType[]) => {
state.expandedKeys = v;
emit('update:expandedKeys', v);
},
'onUpdate:selectedKeys': (v: KeyType[]) => {
state.selectedKeys = v;
emit('update:selectedKeys', v);
},
onCheck: (v: CheckKeys, e) => {
let currentValue = toRaw(state.checkedKeys) as KeyType[];
if (isArray(currentValue) && searchState.startSearch) {
const { key } = unref(getFieldNames);
currentValue = difference(currentValue, getChildrenKeys(e.node.$attrs.node[key]));
if (e.checked) {
currentValue.push(e.node.$attrs.node[key]);
}
state.checkedKeys = currentValue;
} else {
state.checkedKeys = v;
}
const rawVal = toRaw(state.checkedKeys);
emit('update:value', rawVal);
emit('check', rawVal, e);
},
onRightClick: handleRightClick,
};
return omit(propsData, 'treeData', 'class');
});
const getTreeData = computed((): TreeItem[] =>
searchState.startSearch ? searchState.searchData : unref(treeDataRef),
);
const getNotFound = computed((): boolean => {
return !getTreeData.value || getTreeData.value.length === 0;
});
const {
deleteNodeByKey,
insertNodeByKey,
insertNodesByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
} = useTree(treeDataRef, getFieldNames);
function getIcon(params: Recordable, icon?: string) {
if (!icon) {
if (props.renderIcon && isFunction(props.renderIcon)) {
return props.renderIcon(params);
}
}
return icon;
}
async function handleRightClick({ event, node }: Recordable) {
const { rightMenuList: menuList = [], beforeRightClick } = props;
let contextMenuOptions: CreateContextOptions = { event, items: [] };
if (beforeRightClick && isFunction(beforeRightClick)) {
let result = await beforeRightClick(node, event);
if (Array.isArray(result)) {
contextMenuOptions.items = result;
} else {
Object.assign(contextMenuOptions, result);
}
} else {
contextMenuOptions.items = menuList;
}
if (!contextMenuOptions.items?.length) return;
contextMenuOptions.items = contextMenuOptions.items.filter((item) => !item.hidden);
createContextMenu(contextMenuOptions);
}
function setExpandedKeys(keys: KeyType[]) {
state.expandedKeys = keys;
}
function getExpandedKeys() {
return state.expandedKeys;
}
function setSelectedKeys(keys: KeyType[]) {
state.selectedKeys = keys;
}
function getSelectedKeys() {
return state.selectedKeys;
}
function setCheckedKeys(keys: CheckKeys) {
state.checkedKeys = keys;
}
function getCheckedKeys() {
return state.checkedKeys;
}
function checkAll(checkAll: boolean) {
state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]);
}
function expandAll(expandAll: boolean) {
state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]);
}
function onStrictlyChange(strictly: boolean) {
state.checkStrictly = strictly;
}
watch(
() => props.searchValue,
(val) => {
if (val !== searchState.searchText) {
searchState.searchText = val;
}
},
{
immediate: true,
},
);
watch(
() => props.treeData,
(val) => {
if (val) {
handleSearch(searchState.searchText);
}
},
);
function handleSearch(searchValue: string) {
if (searchValue !== searchState.searchText) searchState.searchText = searchValue;
emit('update:searchValue', searchValue);
if (!searchValue) {
searchState.startSearch = false;
return;
}
const { filterFn, checkable, expandOnSearch, checkOnSearch, selectedOnSearch } =
unref(props);
searchState.startSearch = true;
const { title: titleField, key: keyField } = unref(getFieldNames);
const matchedKeys: string[] = [];
searchState.searchData = filter(
unref(treeDataRef),
(node) => {
const result = filterFn
? filterFn(searchValue, node, unref(getFieldNames))
: node[titleField]?.includes(searchValue) ?? false;
if (result) {
matchedKeys.push(node[keyField]);
}
return result;
},
unref(getFieldNames),
);
if (expandOnSearch) {
const expandKeys = treeToList(searchState.searchData).map((val) => {
return val[keyField];
});
if (expandKeys && expandKeys.length) {
setExpandedKeys(expandKeys);
}
}
if (checkOnSearch && checkable && matchedKeys.length) {
setCheckedKeys(matchedKeys);
}
if (selectedOnSearch && matchedKeys.length) {
setSelectedKeys(matchedKeys);
}
}
function handleClickNode(key: string, children: TreeItem[]) {
if (!props.clickRowToExpand || !children || children.length === 0) return;
if (!state.expandedKeys.includes(key)) {
setExpandedKeys([...state.expandedKeys, key]);
} else {
const keys = [...state.expandedKeys];
const index = keys.findIndex((item) => item === key);
if (index !== -1) {
keys.splice(index, 1);
}
setExpandedKeys(keys);
}
}
watchEffect(() => {
treeDataRef.value = props.treeData as TreeItem[];
});
onMounted(() => {
const level = parseInt(props.defaultExpandLevel);
if (level > 0) {
state.expandedKeys = filterByLevel(level);
} else if (props.defaultExpandAll) {
expandAll(true);
}
});
watchEffect(() => {
state.expandedKeys = props.expandedKeys;
});
watchEffect(() => {
state.selectedKeys = props.selectedKeys;
});
watchEffect(() => {
state.checkedKeys = props.checkedKeys;
});
watch(
() => props.value,
() => {
state.checkedKeys = toRaw(props.value || []);
},
{ immediate: true },
);
watch(
() => state.checkedKeys,
() => {
const v = toRaw(state.checkedKeys);
emit('update:value', v);
emit('change', v);
},
);
watchEffect(() => {
state.checkStrictly = props.checkStrictly;
});
const instance: TreeActionType = {
setExpandedKeys,
getExpandedKeys,
setSelectedKeys,
getSelectedKeys,
setCheckedKeys,
getCheckedKeys,
insertNodeByKey,
insertNodesByKey,
deleteNodeByKey,
updateNodeByKey,
getSelectedNode,
checkAll,
expandAll,
filterByLevel: (level: number) => {
state.expandedKeys = filterByLevel(level);
},
setSearchValue: (value: string) => {
handleSearch(value);
},
getSearchValue: () => {
return searchState.searchText;
},
};
function renderAction(node: TreeItem) {
const { actionList } = props;
if (!actionList || actionList.length === 0) return;
return actionList.map((item, index) => {
let nodeShow = true;
if (isFunction(item.show)) {
nodeShow = item.show?.(node);
} else if (isBoolean(item.show)) {
nodeShow = item.show;
}
if (!nodeShow) return null;
return (
<span key={index} class={bem('action')}>
{item.render(node)}
</span>
);
});
}
const treeData = computed(() => {
const data = cloneDeep(getTreeData.value);
eachTree(data, (item, _parent) => {
const searchText = searchState.searchText;
const { highlight } = unref(props);
const {
title: titleField,
key: keyField,
children: childrenField,
} = unref(getFieldNames);
const icon = getIcon(item, item.icon);
const title = get(item, titleField);
const searchIdx = searchText ? title.indexOf(searchText) : -1;
const isHighlight =
searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1;
const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`;
const titleDom = isHighlight ? (
<span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}>
<span>{title.substr(0, searchIdx)}</span>
<span style={highlightStyle}>{searchText}</span>
<span>{title.substr(searchIdx + (searchText as string).length)}</span>
</span>
) : (
title
);
item[titleField] = (
<span
class={`${bem('title')} pl-2`}
onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
>
{slots?.title ? (
getSlot(slots, 'title', item)
) : (
<>
{icon && <TreeIcon icon={icon} />}
{titleDom}
<span class={bem('actions')}>{renderAction(item)}</span>
</>
)}
</span>
);
return item;
});
return data;
});
expose(instance);
return () => {
const { title, helpMessage, toolbar, search, checkable } = props;
const showTitle = title || toolbar || search || slots.headerTitle;
const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
return (
<div class={[bem(), 'h-full', attrs.class]}>
{showTitle && (
<TreeHeader
checkable={checkable}
checkAll={checkAll}
expandAll={expandAll}
title={title}
search={search}
toolbar={toolbar}
helpMessage={helpMessage}
onStrictlyChange={onStrictlyChange}
onSearch={handleSearch}
onClickSearch={($event) => emit('search', $event)}
searchText={searchState.searchText}
>
{extendSlots(slots)}
</TreeHeader>
)}
<Spin spinning={unref(props.loading)} tip="加载中...">
<ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}>
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} />
</ScrollContainer>
<Empty
v-show={unref(getNotFound)}
image={Empty.PRESENTED_IMAGE_SIMPLE}
class="!mt-4"
/>
</Spin>
</div>
);
};
},
});
</script>

View File

@ -1,14 +1,10 @@
import type { VNode, FunctionalComponent } from 'vue';
import { h } from 'vue';
import { isString } from '/@/utils/is';
import { isString } from '@vue/shared';
import { Icon } from '/@/components/Icon';
export interface ComponentProps {
icon: VNode | string;
}
export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => {
export const TreeIcon: FunctionalComponent = ({ icon }: { icon: VNode | string }) => {
if (!icon) return null;
if (isString(icon)) {
return h(Icon, { icon, class: 'mr-1' });

View File

@ -0,0 +1,171 @@
<template>
<div :class="bem()" class="flex px-2 py-1.5 items-center">
<slot name="headerTitle" v-if="slots.headerTitle"></slot>
<BasicTitle :helpMessage="helpMessage" v-if="!slots.headerTitle && title">
{{ title }}
</BasicTitle>
<div
class="flex items-center flex-1 cursor-pointer justify-self-stretch justify-end"
v-if="search || toolbar"
>
<div :class="getInputSearchCls" v-if="search">
<InputSearch
:placeholder="t('common.searchText')"
size="small"
allowClear
v-model:value="searchValue"
@search="$emit('clickSearch', $event)"
/>
</div>
<Dropdown @click.prevent v-if="toolbar">
<Icon icon="ion:ellipsis-vertical" />
<template #overlay>
<Menu @click="handleMenuClick">
<template v-for="item in toolbarList" :key="item.value">
<MenuItem v-bind="{ key: item.value }">
{{ item.label }}
</MenuItem>
<MenuDivider v-if="item.divider" />
</template>
</Menu>
</template>
</Dropdown>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch, useSlots } from 'vue';
import { Dropdown, Menu, MenuItem, MenuDivider, InputSearch } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { BasicTitle } from '/@/components/Basic';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDebounceFn } from '@vueuse/core';
import { createBEM } from '/@/utils/bem';
import { ToolbarEnum } from '../types/tree';
const searchValue = ref('');
const [bem] = createBEM('tree-header');
const props = defineProps({
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
title: {
type: String,
default: '',
},
toolbar: {
type: Boolean,
default: false,
},
checkable: {
type: Boolean,
default: false,
},
search: {
type: Boolean,
default: false,
},
searchText: {
type: String,
default: '',
},
checkAll: {
type: Function,
default: undefined,
},
expandAll: {
type: Function,
default: undefined,
},
} as const);
const emit = defineEmits(['strictly-change', 'search', 'clickSearch']);
const slots = useSlots();
const { t } = useI18n();
const getInputSearchCls = computed(() => {
const titleExists = slots.headerTitle || props.title;
return [
'mr-1',
'w-full',
{
['ml-5']: titleExists,
},
];
});
const toolbarList = computed(() => {
const { checkable } = props;
const defaultToolbarList = [
{ label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL },
{
label: t('component.tree.unExpandAll'),
value: ToolbarEnum.UN_EXPAND_ALL,
divider: checkable,
},
];
return checkable
? [
{ label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL },
{
label: t('component.tree.unSelectAll'),
value: ToolbarEnum.UN_SELECT_ALL,
divider: checkable,
},
...defaultToolbarList,
{ label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY },
{ label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY },
]
: defaultToolbarList;
});
function handleMenuClick(e: { key: ToolbarEnum }) {
const { key } = e;
switch (key) {
case ToolbarEnum.SELECT_ALL:
props.checkAll?.(true);
break;
case ToolbarEnum.UN_SELECT_ALL:
props.checkAll?.(false);
break;
case ToolbarEnum.EXPAND_ALL:
props.expandAll?.(true);
break;
case ToolbarEnum.UN_EXPAND_ALL:
props.expandAll?.(false);
break;
case ToolbarEnum.CHECK_STRICTLY:
emit('strictly-change', false);
break;
case ToolbarEnum.CHECK_UN_STRICTLY:
emit('strictly-change', true);
break;
}
}
function emitChange(value?: string): void {
emit('search', value);
}
const debounceEmitChange = useDebounceFn(emitChange, 200);
watch(
() => searchValue.value,
(v) => {
debounceEmitChange(v);
},
);
watch(
() => props.searchText,
(v) => {
if (v !== searchValue.value) {
searchValue.value = v;
}
},
);
</script>

View File

@ -0,0 +1,207 @@
import type { InsertNodeParams, KeyType, FieldNames, TreeItem } from '../types/tree';
import type { Ref, ComputedRef } from 'vue';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { cloneDeep } from 'lodash-es';
import { unref } from 'vue';
import { forEach } from '/@/utils/helper/treeHelper';
export function useTree(treeDataRef: Ref<TreeDataItem[]>, getFieldNames: ComputedRef<FieldNames>) {
function getAllKeys(list?: TreeDataItem[]) {
const keys: string[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
keys.push(node[keyField]!);
const children = node[childrenField];
if (children && children.length) {
keys.push(...(getAllKeys(children) as string[]));
}
}
return keys as KeyType[];
}
// get keys that can be checked and selected
function getEnabledKeys(list?: TreeDataItem[]) {
const keys: string[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
node.disabled !== true && node.selectable !== false && keys.push(node[keyField]!);
const children = node[childrenField];
if (children && children.length) {
keys.push(...(getEnabledKeys(children) as string[]));
}
}
return keys as KeyType[];
}
function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]) {
const keys: KeyType[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
const children = node[childrenField];
if (nodeKey === node[keyField]) {
keys.push(node[keyField]!);
if (children && children.length) {
keys.push(...(getAllKeys(children) as string[]));
}
} else {
if (children && children.length) {
keys.push(...getChildrenKeys(nodeKey, children));
}
}
}
return keys as KeyType[];
}
// Update node
function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) {
if (!key) return;
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
for (let index = 0; index < treeData.length; index++) {
const element: any = treeData[index];
const children = element[childrenField];
if (element[keyField] === key) {
treeData[index] = { ...treeData[index], ...node };
break;
} else if (children && children.length) {
updateNodeByKey(key, node, element[childrenField]);
}
}
}
// Expand the specified level
function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) {
if (!level) {
return [];
}
const res: (string | number)[] = [];
const data = list || unref(treeDataRef) || [];
for (let index = 0; index < data.length; index++) {
const item = data[index];
const { key: keyField, children: childrenField } = unref(getFieldNames);
const key = keyField ? item[keyField] : '';
const children = childrenField ? item[childrenField] : [];
res.push(key);
if (children && children.length && currentLevel < level) {
currentLevel += 1;
res.push(...filterByLevel(level, children, currentLevel));
}
}
return res as string[] | number[];
}
/**
*
*/
function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) {
const treeData: any = cloneDeep(unref(treeDataRef));
if (!parentKey) {
treeData[push](node);
treeDataRef.value = treeData;
return;
}
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
forEach(treeData, (treeItem) => {
if (treeItem[keyField] === parentKey) {
treeItem[childrenField] = treeItem[childrenField] || [];
treeItem[childrenField][push](node);
return true;
}
});
treeDataRef.value = treeData;
}
/**
*
*/
function insertNodesByKey({ parentKey = null, list, push = 'push' }: InsertNodeParams) {
const treeData: any = cloneDeep(unref(treeDataRef));
if (!list || list.length < 1) {
return;
}
if (!parentKey) {
for (let i = 0; i < list.length; i++) {
treeData[push](list[i]);
}
} else {
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
forEach(treeData, (treeItem) => {
if (treeItem[keyField] === parentKey) {
treeItem[childrenField] = treeItem[childrenField] || [];
for (let i = 0; i < list.length; i++) {
treeItem[childrenField][push](list[i]);
}
treeDataRef.value = treeData;
return true;
}
});
}
}
// Delete node
function deleteNodeByKey(key: string, list?: TreeDataItem[]) {
if (!key) return;
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
for (let index = 0; index < treeData.length; index++) {
const element: any = treeData[index];
const children = element[childrenField];
if (element[keyField] === key) {
treeData.splice(index, 1);
break;
} else if (children && children.length) {
deleteNodeByKey(key, element[childrenField]);
}
}
}
// Get selected node
function getSelectedNode(key: KeyType, list?: TreeItem[], selectedNode?: TreeItem | null) {
if (!key && key !== 0) return null;
const treeData = list || unref(treeDataRef);
treeData.forEach((item) => {
if (selectedNode?.key || selectedNode?.key === 0) return selectedNode;
if (item.key === key) {
selectedNode = item;
return;
}
if (item.children && item.children.length) {
selectedNode = getSelectedNode(key, item.children, selectedNode);
}
});
return selectedNode || null;
}
return {
deleteNodeByKey,
insertNodeByKey,
insertNodesByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
};
}

View File

@ -0,0 +1,195 @@
import type { ExtractPropTypes } from 'vue';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { buildProps } from '/@/utils/props';
export enum ToolbarEnum {
SELECT_ALL,
UN_SELECT_ALL,
EXPAND_ALL,
UN_EXPAND_ALL,
CHECK_STRICTLY,
CHECK_UN_STRICTLY,
}
export const treeEmits = [
'update:expandedKeys',
'update:selectedKeys',
'update:value',
'change',
'check',
'search',
'update:searchValue',
];
export interface TreeState {
expandedKeys: KeyType[];
selectedKeys: KeyType[];
checkedKeys: CheckKeys;
checkStrictly: boolean;
}
export interface FieldNames {
children?: string;
title?: string;
key?: string;
}
export type KeyType = string | number;
export type CheckKeys =
| KeyType[]
| { checked: string[] | number[]; halfChecked: string[] | number[] };
export const treeProps = buildProps({
value: {
type: [Object, Array] as PropType<KeyType[] | CheckKeys>,
},
renderIcon: {
type: Function as PropType<(params: Recordable) => string>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
title: {
type: String,
default: '',
},
toolbar: Boolean,
search: Boolean,
searchValue: {
type: String,
default: '',
},
checkStrictly: Boolean,
clickRowToExpand: {
type: Boolean,
default: false,
},
checkable: Boolean,
defaultExpandLevel: {
type: [String, Number] as PropType<string | number>,
default: '',
},
defaultExpandAll: Boolean,
fieldNames: {
type: Object as PropType<FieldNames>,
},
treeData: {
type: Array as PropType<TreeDataItem[]>,
},
actionList: {
type: Array as PropType<TreeActionItem[]>,
default: () => [],
},
expandedKeys: {
type: Array as PropType<KeyType[]>,
default: () => [],
},
selectedKeys: {
type: Array as PropType<KeyType[]>,
default: () => [],
},
checkedKeys: {
type: Array as PropType<CheckKeys>,
default: () => [],
},
beforeRightClick: {
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
default: undefined,
},
rightMenuList: {
type: Array as PropType<ContextMenuItem[]>,
},
// 自定义数据过滤判断方法(注: 不是整个过滤方法而是内置过滤的判断方法用于增强原本仅能通过title进行过滤的方式)
filterFn: {
type: Function as PropType<
(searchValue: any, node: TreeItem, fieldNames: FieldNames) => boolean
>,
default: undefined,
},
// 高亮搜索值仅高亮具体匹配值通过title值为true时使用默认色值值为#xxx时使用此值替代且高亮开启
highlight: {
type: [Boolean, String] as PropType<Boolean | String>,
default: false,
},
// 搜索完成时自动展开结果
expandOnSearch: Boolean,
// 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
checkOnSearch: Boolean,
// 搜索完成自动select所有结果
selectedOnSearch: Boolean,
loading: {
type: Boolean,
default: false,
},
});
export type TreeProps = ExtractPropTypes<typeof treeProps>;
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;
children?: ContextMenuItem[];
}
export interface ContextMenuOptions {
icon?: string;
styles?: any;
items?: ContextMenuItem[];
}
export interface TreeItem extends TreeDataItem {
icon?: any;
}
export interface TreeActionItem {
render: (record: Recordable) => any;
show?: boolean | ((record: Recordable) => boolean);
}
export interface InsertNodeParams {
parentKey: string | null;
node: TreeDataItem;
list?: TreeDataItem[];
push?: 'push' | 'unshift';
}
export interface TreeActionType {
checkAll: (checkAll: boolean) => void;
expandAll: (expandAll: boolean) => void;
setExpandedKeys: (keys: KeyType[]) => void;
getExpandedKeys: () => KeyType[];
setSelectedKeys: (keys: KeyType[]) => void;
getSelectedKeys: () => KeyType[];
setCheckedKeys: (keys: CheckKeys) => void;
getCheckedKeys: () => CheckKeys;
filterByLevel: (level: number) => void;
insertNodeByKey: (opt: InsertNodeParams) => void;
insertNodesByKey: (opt: InsertNodeParams) => void;
deleteNodeByKey: (key: string) => void;
updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void;
setSearchValue: (value: string) => void;
getSearchValue: () => string;
getSelectedNode: (
key: KeyType,
treeList?: TreeItem[],
selectNode?: TreeItem | null,
) => TreeItem | null;
}

View File

@ -0,0 +1,52 @@
@tree-prefix-cls: ~'@{namespace}-tree';
.@{tree-prefix-cls} {
background-color: @component-background;
.ant-tree-node-content-wrapper {
position: relative;
.ant-tree-title {
position: absolute;
left: 0;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&__title {
position: relative;
display: flex;
align-items: center;
width: 100%;
padding-right: 10px;
&:hover {
.@{tree-prefix-cls}__action {
visibility: visible;
}
}
}
&__content {
overflow: hidden;
}
&__actions {
position: absolute;
//top: 2px;
right: 3px;
display: flex;
}
&__action {
margin-left: 4px;
visibility: hidden;
}
&-header {
border-bottom: 1px solid @border-color-base;
}
}

View File

@ -0,0 +1 @@
import './index.less';

View File

@ -0,0 +1,5 @@
import BasicTree from './src/Tree.vue';
export { BasicTree };
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
export * from './src/typing';

View File

@ -107,7 +107,8 @@
return !getTreeData.value || getTreeData.value.length === 0;
});
const { deleteNodeByKey, insertNodeByKey, insertNodesByKey, filterByLevel, updateNodeByKey, getAllKeys, getChildrenKeys, getEnabledKeys } = useTree(treeDataRef, getReplaceFields);
const { deleteNodeByKey, insertNodeByKey, insertNodesByKey, filterByLevel, updateNodeByKey, getAllKeys, getChildrenKeys, getEnabledKeys } =
useTree(treeDataRef, getReplaceFields);
function getIcon(params: Recordable, icon?: string) {
if (!icon) {

View File

@ -7,7 +7,13 @@
<div class="flex flex-1 justify-end items-center cursor-pointer" v-if="search || toolbar">
<div :class="getInputSearchCls" v-if="search">
<InputSearch :placeholder="t('common.searchText')" size="small" allowClear v-model:value="searchValue" @search="$emit('clickSearch', $event)" />
<InputSearch
:placeholder="t('common.searchText')"
size="small"
allowClear
v-model:value="searchValue"
@search="$emit('clickSearch', $event)"
/>
</div>
<Dropdown @click.prevent v-if="toolbar">
<Icon icon="ion:ellipsis-vertical" />

View File

@ -0,0 +1,17 @@
import type { VNode, FunctionalComponent } from 'vue';
import { h } from 'vue';
import { isString } from '/@/utils/is';
import { Icon } from '/@/components/Icon';
export interface ComponentProps {
icon: VNode | string;
}
export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => {
if (!icon) return null;
if (isString(icon)) {
return h(Icon, { icon, class: 'mr-1' });
}
return Icon;
};

View File

@ -96,7 +96,11 @@
const getUploadBtnText = computed(() => {
const someError = fileListRef.value.some((item) => item.status === UploadResultStatus.ERROR);
return isUploadingRef.value ? t('component.upload.uploading') : someError ? t('component.upload.reUploadFailed') : t('component.upload.startUpload');
return isUploadingRef.value
? t('component.upload.uploading')
: someError
? t('component.upload.reUploadFailed')
: t('component.upload.startUpload');
});
//

View File

@ -1,5 +1,12 @@
<template>
<BasicModal width="800px" :title="t('component.upload.preview')" wrapClassName="upload-preview-modal" v-bind="$attrs" @register="register" :showOkBtn="false">
<BasicModal
width="800px"
:title="t('component.upload.preview')"
wrapClassName="upload-preview-modal"
v-bind="$attrs"
@register="register"
:showOkBtn="false"
>
<FileList :dataSource="fileListRef" :columns="columns" :actionColumn="actionColumn" />
</BasicModal>
</template>

View File

@ -1,7 +1,17 @@
import { Ref, unref, computed } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export function useUploadType({ acceptRef, helpTextRef, maxNumberRef, maxSizeRef }: { acceptRef: Ref<string[]>; helpTextRef: Ref<string>; maxNumberRef: Ref<number>; maxSizeRef: Ref<number> }) {
export function useUploadType({
acceptRef,
helpTextRef,
maxNumberRef,
maxSizeRef,
}: {
acceptRef: Ref<string[]>;
helpTextRef: Ref<string>;
maxNumberRef: Ref<number>;
maxSizeRef: Ref<number>;
}) {
// 文件类型限制
const getAccept = computed(() => {
const accept = unref(acceptRef);

View File

@ -249,7 +249,8 @@
}
return (
<div class={cls} onMousedown={handleDragStart} onTouchstart={handleDragStart} style={unref(getActionStyleRef)} ref={actionElRef}>
{getSlot(slots, 'actionIcon', isPassing) || (isPassing ? <CheckOutlined class={`darg-verify-action__icon`} /> : <DoubleRightOutlined class={`darg-verify-action__icon`} />)}
{getSlot(slots, 'actionIcon', isPassing) ||
(isPassing ? <CheckOutlined class={`darg-verify-action__icon`} /> : <DoubleRightOutlined class={`darg-verify-action__icon`} />)}
</div>
);
};
@ -303,7 +304,16 @@
top: 0;
font-size: 12px;
-webkit-text-size-adjust: none;
background-color: -webkit-gradient(linear, left top, right top, color-stop(0, #333), color-stop(0.4, #333), color-stop(0.5, #fff), color-stop(0.6, #333), color-stop(1, #333));
background-color: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, #333),
color-stop(0.4, #333),
color-stop(0.5, #fff),
color-stop(0.6, #333),
color-stop(1, #333)
);
animation: slidetounlock 3s infinite;
background-clip: text;
user-select: none;

View File

@ -139,7 +139,9 @@
alt="verify"
/>
{state.showTip && (
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}>{state.isPassing ? t('component.verify.time', { time: time.toFixed(1) }) : t('component.verify.error')}</span>
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}>
{state.isPassing ? t('component.verify.time', { time: time.toFixed(1) }) : t('component.verify.error')}
</span>
)}
{!state.showTip && !state.draged && <span class={[`ir-dv-img__tip`, 'normal']}>{t('component.verify.redoTip')}</span>}
</div>

View File

@ -1,16 +1,18 @@
<template>
<Modal v-bind="getProps">
<Spin :spinning="loading">
<div style="padding: 20px">
<div v-html="options.content" style="margin-bottom: 8px"></div>
<BasicForm @register="registerForm">
<template #customInput="{ model, field }">
<Input ref="inputRef" v-model:value="model[field]" :placeholder="placeholder" @pressEnter="onSubmit" @input="onChange" />
</template>
</BasicForm>
</div>
</Spin>
</Modal>
<ConfigProvider :locale="getAntdLocale">
<Modal v-bind="getProps">
<Spin :spinning="loading">
<div style="padding: 20px;">
<div v-html="options.content" style="margin-bottom: 8px"></div>
<BasicForm @register="registerForm">
<template #customInput="{ model, field }">
<Input ref="inputRef" v-model:value="model[field]" :placeholder="placeholder" @pressEnter="onSubmit" @input="onChange" />
</template>
</BasicForm>
</div>
</Spin>
</Modal>
</ConfigProvider>
</template>
<script lang="ts">
@ -18,7 +20,8 @@
import type { ModalProps } from '/@/components/Modal';
import { ref, defineComponent, computed, unref, onMounted, nextTick } from 'vue';
import { BasicForm, useForm } from '/@/components/Form';
import { Modal, Spin, Input } from 'ant-design-vue';
import { Modal, Spin, Input, ConfigProvider } from 'ant-design-vue';
import { useLocale } from '/@/locales/useLocale';
export default defineComponent({
name: 'JPrompt',
@ -27,10 +30,12 @@
Spin,
Input,
BasicForm,
ConfigProvider,
},
emits: ['register'],
setup(props, { emit }) {
const inputRef = ref();
const { getAntdLocale } = useLocale();
const visible = ref(false);
//
const loading = ref(false);
@ -109,9 +114,9 @@
}
function onChange() {
validate();
validate()
}
/** 提交表单 */
async function onSubmit() {
try {
@ -144,6 +149,7 @@
loading,
options,
placeholder,
getAntdLocale,
onChange,
onSubmit,

View File

@ -4,6 +4,7 @@ import { error } from '/@/utils/log';
import JPrompt from '../JPrompt.vue';
export function useJPrompt() {
function createJPrompt(options: JPromptProps) {
let instance = null;
const box = document.createElement('div');

View File

@ -91,8 +91,34 @@
const tableScroll = ref({ x: true });
const getBindValue = Object.assign({}, unref(props), unref(attrs));
const [
{ visibleChange, loadColumnsInfo, dynamicParamHandler, loadData, loadColumnsAndData, handleChangeInTable, combineRowKey, clickThenCheck, filterUnuseSelect, handleExport },
{ hrefComponent, visible, rowSelection, checkedKeys, selectRows, pagination, dataSource, columns, loading, title, iSorter, queryInfo, queryParam, dictOptions },
{
visibleChange,
loadColumnsInfo,
dynamicParamHandler,
loadData,
loadColumnsAndData,
handleChangeInTable,
combineRowKey,
clickThenCheck,
filterUnuseSelect,
handleExport,
},
{
hrefComponent,
visible,
rowSelection,
checkedKeys,
selectRows,
pagination,
dataSource,
columns,
loading,
title,
iSorter,
queryInfo,
queryParam,
dictOptions,
},
] = usePopBiz(getBindValue);
const showSearchFlag = computed(() => unref(queryInfo) && unref(queryInfo).length > 0);

View File

@ -4,12 +4,29 @@
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<template v-if="single_mode === item.mode">
<a-date-picker :showTime="false" valueFormat="YYYY-MM-DD" :placeholder="'请选择' + item.label" v-model:value="queryParam[item.field]"></a-date-picker>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
:placeholder="'请选择' + item.label"
v-model:value="queryParam[item.field]"
></a-date-picker>
</template>
<template v-else>
<a-date-picker :showTime="false" valueFormat="YYYY-MM-DD" placeholder="开始日期" v-model:value="queryParam[item.field + '_begin']" style="width: calc(50% - 15px)"></a-date-picker>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="开始日期"
v-model:value="queryParam[item.field + '_begin']"
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="queryParam[item.field + '_end']" style="width: calc(50% - 15px)"></a-date-picker>
<a-date-picker
:showTime="false"
valueFormat="YYYY-MM-DD"
placeholder="结束日期"
v-model:value="queryParam[item.field + '_end']"
style="width: calc(50% - 15px)"
></a-date-picker>
</template>
</a-form-item>
@ -18,7 +35,12 @@
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<template v-if="single_mode === item.mode">
<a-date-picker :placeholder="'请选择' + item.label" :show-time="true" valueFormat="YYYY-MM-DD HH:mm:ss" v-model:value="queryParam[item.field]"></a-date-picker>
<a-date-picker
:placeholder="'请选择' + item.label"
:show-time="true"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="queryParam[item.field]"
></a-date-picker>
</template>
<template v-else>
<a-date-picker
@ -47,17 +69,34 @@
<a-date-picker :placeholder="'请选择' + item.label" mode="time" valueFormat="HH:mm:ss" v-model:value="queryParam[item.field]"></a-date-picker>
</template>
<template v-else>
<a-date-picker placeholder="请选择开始时间" mode="time" valueFormat="HH:mm:ss" v-model:value="queryParam[item.field + '_begin']" style="width: calc(50% - 15px)"></a-date-picker>
<a-date-picker
placeholder="请选择开始时间"
mode="time"
valueFormat="HH:mm:ss"
v-model:value="queryParam[item.field + '_begin']"
style="width: calc(50% - 15px)"
></a-date-picker>
<span class="group-query-strig">~</span>
<a-date-picker placeholder="请选择结束时间" mode="time" valueFormat="HH:mm:ss" v-model:value="queryParam[item.field + '_end']" style="width: calc(50% - 15px)"></a-date-picker>
<a-date-picker
placeholder="请选择结束时间"
mode="time"
valueFormat="HH:mm:ss"
v-model:value="queryParam[item.field + '_end']"
style="width: calc(50% - 15px)"
></a-date-picker>
</template>
</a-form-item>
<a-form-item v-else-if="item.view === CompTypeEnum.List || item.view === CompTypeEnum.Radio || item.view === CompTypeEnum.Switch" :labelCol="labelCol" :class="'jeecg-online-search'">
<a-form-item
v-else-if="item.view === CompTypeEnum.List || item.view === CompTypeEnum.Radio || item.view === CompTypeEnum.Switch"
:labelCol="labelCol"
:class="'jeecg-online-search'"
>
<template #label>
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<JDictSelectTag v-if="item.config === '1'" :placeholder="'请选择' + item.label" v-model="queryParam[item.field]" :dictCode="getDictCode(item)"> </JDictSelectTag>
<JDictSelectTag v-if="item.config === '1'" :placeholder="'请选择' + item.label" v-model="queryParam[item.field]" :dictCode="getDictCode(item)">
</JDictSelectTag>
<a-select v-else :placeholder="'请选择' + item.label" v-model:value="queryParam[item.field]">
<template v-for="(obj, index) in dictOptions[getDictOptionKey(item)]" :key="index">
<a-select-option :value="obj.value"> {{ obj.text }}</a-select-option>
@ -92,7 +131,8 @@
<template #label>
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<JDictSelectTag v-if="item.config === '1'" v-model:value="queryParam[item.field]" :placeholder="'请选择' + item.label" :dict="getDictCode(item)"> </JDictSelectTag>
<JDictSelectTag v-if="item.config === '1'" v-model:value="queryParam[item.field]" :placeholder="'请选择' + item.label" :dict="getDictCode(item)">
</JDictSelectTag>
<!--TODO 新需要的组件-->
<!-- <j-online-search-select
v-else
@ -121,7 +161,14 @@
<template #label>
<span :title="item.label" class="label-text">{{ item.label }}</span>
</template>
<JPopup :placeholder="'请选择' + item.label" v-model:value="queryParam[item.field]" :formElRef="formElRef" :code="item.dictTable" :field-config="item.dictCode" :multi="true" />
<JPopup
:placeholder="'请选择' + item.label"
v-model:value="queryParam[item.field]"
:formElRef="formElRef"
:code="item.dictTable"
:field-config="item.dictCode"
:multi="true"
/>
</a-form-item>
<a-form-item v-else-if="item.view === CompTypeEnum.Pca" :labelCol="labelCol" :class="'jeecg-online-search'">
@ -131,7 +178,12 @@
<JAreaLinkage :placeholder="'请选择' + item.label" v-model:value="queryParam[item.field]" />
</a-form-item>
<!--TODO 缺少的组件-->
<a-form-item v-else-if="item.view === CompTypeEnum.Checkbox || item.view === CompTypeEnum.ListMulti" :labelCol="labelCol" :label="item.label" :class="'jeecg-online-search'">
<a-form-item
v-else-if="item.view === CompTypeEnum.Checkbox || item.view === CompTypeEnum.ListMulti"
:labelCol="labelCol"
:label="item.label"
:class="'jeecg-online-search'"
>
<!-- <j-select-multiple
v-if="item.config==='1'"
:placeholder=" '请选择'+item.label "

View File

@ -290,7 +290,7 @@ export function usePopBiz(props, tableRef?) {
if (column.isTotal === '1') {
arr.push(column.dataIndex!);
}
// 【VUEN-1569】【online报表】合计无效
// 【VUEN-1569】【online报表】合计无效
if (column.children && column.children.length > 0) {
let subArray = getNeedSumColumns(column.children);
if (subArray.length > 0) {

View File

@ -32,6 +32,12 @@ interface OnlineColumn {
slots?: ScopedSlots;
//超过宽度将自动省略,暂不支持和排序筛选一起使用。
ellipsis?: boolean;
// 是否固定列
fixed?: boolean | 'left' | 'right';
//字段类型 int/string
dbType?:string;
//他表字段用
linkField?:string;
}
export { OnlineColumn, HrefSlots };

View File

@ -1,401 +0,0 @@
<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="handleReset"></a>
</template>
<a-button-group>
<a-button type="primary" @click="handleOpen">
<AppstoreTwoTone :spin="true" />
<span>高级查询</span>
</a-button>
</a-button-group>
</a-tooltip>
<a-button v-else type="primary" preIcon="ant-design:filter-outlined" @click="handleOpen"> </a-button>
</div>
<!-- 高级查询弹框 -->
<teleport to="body">
<BasicModal title="高级查询构造器" :canFullscreen="false" :width="1050" @register="registerFormModal" @ok="handleSubmit">
<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="18">
<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">
<a-space v-for="(item, index) in dynamicRowValues.values" :key="item.key" style="display: flex; margin-bottom: 8px" align="baseline">
<a-form-item :name="['values', index, 'field']" style="width: 180px">
<a-tree-select
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: '180px', overflow: 'auto' }"
@change="handleChangeField(item)"
:tree-data="fieldTreeData"
>
</a-tree-select>
</a-form-item>
<a-form-item :name="['values', index, 'rule']" style="width: 180px">
<a-select style="width: 100%" placeholder="请选择匹配规则" v-model:value="item.rule">
<a-select-option value="eq">等于</a-select-option>
<a-select-option value="like">模糊</a-select-option>
<a-select-option value="right_like">..开始</a-select-option>
<a-select-option value="left_like">..结尾</a-select-option>
<a-select-option value="in">...</a-select-option>
<a-select-option value="ne">不等于</a-select-option>
<a-select-option value="gt">大于</a-select-option>
<a-select-option value="ge">大于等于</a-select-option>
<a-select-option value="lt">小于</a-select-option>
<a-select-option value="le">小于等于</a-select-option>
</a-select>
</a-form-item>
<a-form-item :name="['values', index, 'val']" style="width: 280px">
<online-super-query-val-component
style="width: 100%"
:schema="getSchema(item, index)"
:formModel="item"
:setFormModel="
(key, value) => {
setFormModel(key, value, item);
}
"
/>
</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-col :sm="24" :md="6">
<!-- 查询记录 -->
<a-card class="j-super-query-history-card" :bordered="true">
<template #title><div>保存的查询</div></template>
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" 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>
<span class="icon-cancle"><close-circle-outlined @click="handleRemoveSaveInfo(title)" /></span>
</div>
</template>
<!-- antd-2是这么写的 升级到3会也许会改变写法 -->
<template #custom>
<file-text-outlined />
</template>
</a-tree>
</a-card>
</a-col>
</a-row>
</BasicModal>
</teleport>
<!-- 保存信息弹框 -->
<a-modal title="请输入保存的名称" :visible="saveInfo.visible" @cancel="saveInfo.visible = false" @ok="doSaveQueryInfo">
<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 } 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 } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
export default {
name: 'OnlineSuperQuery',
props: {
config: {
type: Object,
default: [],
},
status: {
type: Boolean,
default: false,
},
},
components: {
BasicModal,
MinusCircleOutlined,
PlusOutlined,
OnlineSuperQueryValComponent,
FileTextOutlined,
CloseCircleOutlined,
AppstoreTwoTone,
Divider,
},
emits: ['search'],
setup(props, { emit }) {
const [registerFormModal, formModal] = useModal();
const { createMessage: $message } = useMessage();
/**
* 关闭按钮事件
*/
function handleCancel() {
formModal.closeModal();
}
/**
* 确认按钮事件
*/
function handleSubmit() {
//console.log('handleSubmit', dynamicRowValues.values)
let dataArray = getQueryInfo(true);
if (dataArray && dataArray.length > 0) {
let result = getSuperQueryParams(dataArray);
//console.log('1', dataArray)
//console.log('2', result)
emit('search', result);
} else {
$message.warning('空条件无法查询!');
}
}
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 handleReset() {
dynamicRowValues.values = [];
addOne(false);
let result = getSuperQueryParams([]);
emit('search', result);
}
const {
formRef,
init,
dynamicRowValues,
matchType,
registerModal,
handleSave,
doSaveQueryInfo,
saveInfo,
saveTreeData,
handleTreeSelect,
handleRemoveSaveInfo,
fieldTreeData,
addOne,
removeOne,
setFormModel,
getSchema,
loading,
getQueryInfo,
initDefaultValues,
} = useSuperQuery();
/*--------------------按钮区域-beign------------------*/
const superQueryFlag = ref(false);
watch(
() => props.status,
(val) => {
superQueryFlag.value = val;
},
{ immediate: true }
);
function handleOpen() {
formModal.openModal();
addOne(true);
}
/*--------------------按钮区域-end------------------*/
function getPopupContainer() {
return document.getElementsByClassName('jee-super-query-form')[0];
}
function onFinish(a) {
console.log('onfinish', a);
}
function handleChangeField(item) {
item['val'] = '';
}
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 }
);
return {
formRef,
registerFormModal,
init,
handleChangeField,
dynamicRowValues,
matchType,
registerModal,
handleSubmit,
handleCancel,
handleSave,
handleReset,
doSaveQueryInfo,
saveInfo,
saveTreeData,
handleTreeSelect,
handleRemoveSaveInfo,
fieldTreeData,
addOne,
removeOne,
setFormModel,
getSchema,
loading,
onFinish,
getPopupContainer,
superQueryFlag,
handleOpen,
initDefaultValues,
};
},
};
</script>
<style scoped lang="less">
.jee-super-query-form .ant-form-item {
margin-bottom: 9px;
}
.j-super-query-history-tree {
::v-deep .ant-tree-switcher {
width: 0px;
}
::v-deep .ant-tree-node-content-wrapper {
width: 100%;
&:hover {
background-color: #e6f7ff !important;
border-radius: 0;
}
}
::v-deep .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;
}
}
}
::v-deep .ant-card-body {
padding: 0;
}
}
.j-super-query-history-card {
::v-deep .ant-card-body,
::v-deep.ant-card-head-title {
padding: 0;
}
}
/*VUEN-1087 【移动端】高级查询显示不全 */
@media only screen and(max-width: 1050px) {
::v-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;
}
}
}
</style>

View File

@ -1,98 +0,0 @@
<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: 'OnlineSuperQueryValComponent',
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,
},
},
setup(props) {
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,
};
return <Comp {...compAttr} />;
}
return () => {
return <div style="width:100%">{renderComponent()}</div>;
};
},
});
</script>

View File

@ -1,524 +0,0 @@
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';
/**
*
*
*/
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;
}
/**
* -model
* */
interface TreeModel {
title: string;
value: string;
isLeaf?: boolean;
disabled?: boolean;
children?: TreeModel[];
order?: number;
}
/**
*
* */
interface SaveModel {
title: string;
content: string;
type: string;
}
export function useSuperQuery() {
const { createMessage: $message } = useMessage();
/** 表单ref*/
const formRef = ref<any>();
/** 数据*/
const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({
values: [],
});
/** and/or */
const matchType = ref('and');
// 弹框显示
const [registerModal, { setModalProps }] = useModalInner(() => {
setModalProps({ confirmLoading: false });
});
// 高级查询类型不支持联动组件需要额外设置联动组件的view为text
const view2QueryViewMap = Object.assign({}, { link_down: 'text' }, FORM_VIEW_TO_QUERY_VIEW);
/**
*
*/
function handleSubmit() {
console.log('handleSubmit', dynamicRowValues.values);
}
/**
*
*/
function handleCancel() {
//closeModal();
}
/**
* val
*/
function setFormModel(key: string, value: any, item: any) {
console.log('setFormModel', key, value);
// formModel[key] = value;
item['val'] = value;
}
// 字段-Properties
const fieldProperties = ref<any>({});
// 字段-左侧查询项-树控件数据
const fieldTreeData = ref<any>([]);
/**
* -
* 1. @--> map
* 2. : :@
*
* @param json
*/
function init(json) {
let { allFields, treeData } = getAllFields(json);
fieldProperties.value = allFields;
fieldTreeData.value = treeData;
}
/**
*
* @param index
*/
function addOne(index) {
let item = {
field: undefined,
rule: 'eq',
val: '',
key: randomString(16),
};
if (index === false) {
// 重置后需要调用
dynamicRowValues.values = [];
dynamicRowValues.values.push(item);
} else if (index === true) {
// 打开弹框是需要调用
if (dynamicRowValues.values.length == 0) {
dynamicRowValues.values.push(item);
}
} else {
// 其余就是 正常的点击加号增加行
dynamicRowValues.values.splice(++index, 0, item);
}
}
/**
*
*/
function removeOne(item: SuperQueryItem) {
let arr = toRaw(dynamicRowValues.values);
let index = -1;
for (let i = 0; i < arr.length; i++) {
if (item.key == arr[i].key) {
index = i;
break;
}
}
if (index != -1) {
dynamicRowValues.values.splice(index, 1);
}
}
// 默认的输入框
const defaultInput = {
field: 'val',
label: '测试',
component: 'Input',
};
/**
* val schema, change
* @param item
* @param index
*/
function getSchema(item, index) {
let map = fieldProperties.value;
let prop = map[item.field];
if (!prop) {
return defaultInput;
}
if (view2QueryViewMap[prop.view]) {
// 如果出现查询条件联动组件出来的场景,请跟踪此处
prop.view = view2QueryViewMap[prop.view];
}
let temp;
// temp.setFormRef(formRef)
temp.noChange();
// 查询条件中的 下拉框popContainer为parentNode
temp.asSearchForm();
temp.updateField(item.field + index);
const setFieldValue = (values) => {
item['val'] = values[item.field];
};
temp.setFunctionForFieldValue(setFieldValue);
let schema = temp.getFormItemSchema();
//schema['valueField'] = 'val'
return schema;
}
/*-----------------------右侧保存信息相关-begin---------------------------*/
/**
*
*/
const saveTreeData = ref<any>('');
// 本地缓存
const $ls = createLocalStorage();
//需要保存的信息(一条)
const saveInfo = reactive({
visible: false,
title: '',
content: '',
saveCode: '',
});
//按钮loading
const loading = ref(false);
// 当前页面路由
const route = useRoute();
// 监听路由信息,路由发生改变,则重新获取保存的查询信息-->currentPageSavedArray
watch(
() => route.fullPath,
(val) => {
console.log('fullpath', val);
initSaveQueryInfoCode();
}
);
// 当前页面存储的 查询信息
const currentPageSavedArray = ref<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() {
let code = SAVE_CODE_PRE + route.fullPath;
saveInfo.saveCode = code;
let list = $ls.get(code);
if (list && list instanceof Array) {
currentPageSavedArray.value = list;
}
}
// 执行一次 获取保存的查询信息
initSaveQueryInfoCode();
/**
*
*/
function handleSave() {
// 获取实际数据转成字符串
let fieldArray = getQueryInfo();
if (!fieldArray) {
$message.warning('空条件不能保存');
return;
}
let content = JSON.stringify(fieldArray);
openSaveInfoModal(content);
}
// 输入保存标题 弹框显示
function openSaveInfoModal(content) {
saveInfo.visible = true;
saveInfo.title = '';
saveInfo.content = content;
}
/**
*
*/
function doSaveQueryInfo() {
let { title, content, saveCode } = saveInfo;
let index = getTitleIndex(title);
if (index >= 0) {
// 已存在是否覆盖
Modal.confirm({
title: '提示',
content: `${title} 已存在,是否覆盖?`,
okText: '确认',
cancelText: '取消',
onOk: () => {
currentPageSavedArray.value.splice(index, 1, {
content,
title,
type: matchType.value,
});
$ls.set(saveCode, currentPageSavedArray.value);
saveInfo.visible = false;
},
});
} else {
currentPageSavedArray.value.push({
content,
title,
type: matchType.value,
});
$ls.set(saveCode, currentPageSavedArray.value);
saveInfo.visible = false;
}
}
// 根据填入的 title找本地存储的信息如果有需要询问是否覆盖
function getTitleIndex(title) {
let savedArray = currentPageSavedArray.value;
let index = -1;
for (let i = 0; i < savedArray.length; i++) {
if (savedArray[i].title == title) {
index = i;
break;
}
}
return index;
}
/**
* /false
*/
function getQueryInfo(isEmit = false) {
let arr = dynamicRowValues.values;
if (!arr || arr.length == 0) {
return false;
}
let fieldArray: any = [];
let fieldProps = fieldProperties.value;
for (let item of arr) {
if (item.field && (item.val || item.val === 0) && item.rule) {
let tempVal: any = toRaw(item.val);
if (tempVal instanceof Array) {
tempVal = tempVal.join(',');
}
let fieldName = getRealFieldName(item);
let obj = {
field: fieldName,
rule: item.rule,
val: tempVal,
};
if (isEmit === true) {
//如果当前数据用于emit事件需要设置dbtype和type
let prop = fieldProps[item.field];
if (prop) {
obj['type'] = prop.view;
obj['dbType'] = prop.type;
}
}
fieldArray.push(obj);
}
}
if (fieldArray.length == 0) {
return false;
}
return fieldArray;
}
//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) {
rowsValues.push(Object.assign({}, { key: randomString(16) }, item));
}
dynamicRowValues.values = rowsValues;
matchType.value = type;
}
}
/**
*
*/
function handleRemoveSaveInfo(title) {
console.log(title);
let index = getTitleIndex(title);
if (index >= 0) {
currentPageSavedArray.value.splice(index, 1);
$ls.set(saveInfo.saveCode, currentPageSavedArray.value);
}
}
/*-----------------------右侧保存信息相关-end---------------------------*/
// 获取所有字段配置信息
function getAllFields(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)
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,
};
Object.keys(subProps).map((subField) => {
let subItem = subProps[subField];
// 保证排序统一
subItem['order'] = subTableOrder + subItem['order'];
let subFieldKey = field + '@' + subField;
allFields[subFieldKey] = subItem;
subNode.children!.push({
title: subItem.title,
value: subFieldKey,
isLeaf: true,
order: subItem['order'],
});
});
orderField(subNode);
treeData.push(subNode);
order++;
} else {
// 主表字段
//let fieldKey = table+'@'+field
let fieldKey = field;
allFields[fieldKey] = item;
treeData.push({
title: item.title,
value: fieldKey,
isLeaf: true,
order: item.order,
});
}
});
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,
};
}

View File

@ -105,7 +105,12 @@
if (res.success) {
nodes = [...successInfo, h('br'), `无失败信息!`];
} else {
nodes = [`失败信息如下:`, renderTextarea(h, res.result.failInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')), h('br'), ...successInfo];
nodes = [
`失败信息如下:`,
renderTextarea(h, res.result.failInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')),
h('br'),
...successInfo,
];
}
return nodes;
},

View File

@ -8,7 +8,9 @@
</a-menu>
</template>
</a-dropdown>
<a-button v-else-if="syncToApp" type="primary" preIcon="ant-design:sync-outlined" @click="handleMenuClick({ key: 'to-app' })">同步{{ name }}</a-button>
<a-button v-else-if="syncToApp" type="primary" preIcon="ant-design:sync-outlined" @click="handleMenuClick({ key: 'to-app' })"
>同步{{ name }}</a-button
>
<a-button v-else type="primary" preIcon="ant-design:sync-outlined" @click="handleMenuClick({ key: 'to-local' })">同步{{ name }}到本地</a-button>
</template>

View File

@ -9,6 +9,8 @@ import {
Alert,
Checkbox,
DatePicker,
TimePicker,
Calendar,
Radio,
Switch,
Card,
@ -66,6 +68,8 @@ export function registerGlobComp(app: App) {
.use(Breadcrumb)
.use(Checkbox)
.use(DatePicker)
.use(TimePicker)
.use(Calendar)
.use(Radio)
.use(Switch)
.use(Card)

View File

@ -89,6 +89,20 @@ export function useTabs(_router?: Router) {
}
}
/**
*
* @param path
*/
function closeSameRoute(path) {
if(path.indexOf('?')>0){
path = path.split('?')[0];
}
let tab = tabStore.getTabList.find((item) => item.path.indexOf(path)>=0)!;
if(tab){
tabStore.closeTab(tab, router);
}
}
return {
refreshPage: () => handleTabAction(TableActionEnum.REFRESH),
closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL),
@ -99,5 +113,6 @@ export function useTabs(_router?: Router) {
close: (tab?: RouteLocationNormalized) => handleTabAction(TableActionEnum.CLOSE, tab),
setTitle: (title: string, tab?: RouteLocationNormalized) => updateTabTitle(title, tab),
updatePath: (fullPath: string, tab?: RouteLocationNormalized) => updateTabPath(fullPath, tab),
closeSameRoute
};
}

View File

@ -106,9 +106,11 @@
if (unref(isMultiTenant) && unref(isMultiDepart)) {
currTitle.value = '切换租户和部门';
} else if (unref(isMultiTenant)) {
currTitle.value = unref(currentTenantName) && unref(currentTenantName).length > 0 ? `租户切换(当前租户 :${unref(currentTenantName)}` : props.title;
currTitle.value =
unref(currentTenantName) && unref(currentTenantName).length > 0 ? `租户切换(当前租户 :${unref(currentTenantName)}` : props.title;
} else if (unref(isMultiDepart)) {
currTitle.value = unref(currentDepartName) && unref(currentDepartName).length > 0 ? `部门切换(当前部门 :${unref(currentDepartName)}` : props.title;
currTitle.value =
unref(currentDepartName) && unref(currentDepartName).length > 0 ? `部门切换(当前部门 :${unref(currentDepartName)}` : props.title;
}
//model
if (unref(isMultiTenant) || unref(isMultiDepart)) {

View File

@ -4,7 +4,11 @@
<div :class="`${prefixCls}-left`">
<!-- logo -->
<AppLogo v-if="getShowHeaderLogo || getIsMobile" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" :style="getLogoWidth" />
<LayoutTrigger v-if="(getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile" :theme="getHeaderTheme" :sider="false" />
<LayoutTrigger
v-if="(getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile"
:theme="getHeaderTheme"
:sider="false"
/>
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
</div>
<!-- left end -->
@ -95,7 +99,17 @@
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
const { getHeaderTheme, getShowFullScreen, getShowNotice, getShowContent, getShowBread, getShowHeaderLogo, getShowHeader, getShowSearch, getUseLockPage } = useHeaderSetting();
const {
getHeaderTheme,
getShowFullScreen,
getShowNotice,
getShowContent,
getShowBread,
getShowHeaderLogo,
getShowHeader,
getShowSearch,
getUseLockPage,
} = useHeaderSetting();
const { getShowLocalePicker } = useLocale();

View File

@ -41,7 +41,17 @@
setup(props) {
const go = useGo();
const { getMenuMode, getMenuType, getMenuTheme, getCollapsed, getCollapsedShowTitle, getAccordion, getIsHorizontal, getIsSidebarType, getSplit } = useMenuSetting();
const {
getMenuMode,
getMenuType,
getMenuTheme,
getCollapsed,
getCollapsedShowTitle,
getAccordion,
getIsHorizontal,
getIsSidebarType,
getSplit,
} = useMenuSetting();
const { getShowLogo } = useRootSetting();
const { prefixCls } = useDesign('layout-menu');
@ -57,7 +67,10 @@
const getIsShowLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
const getUseScroll = computed(() => {
return !unref(getIsHorizontal) && (unref(getIsSidebarType) || props.splitType === MenuSplitTyeEnum.LEFT || props.splitType === MenuSplitTyeEnum.NONE);
return (
!unref(getIsHorizontal) &&
(unref(getIsSidebarType) || props.splitType === MenuSplitTyeEnum.LEFT || props.splitType === MenuSplitTyeEnum.NONE)
);
});
const getWrapperStyle = computed((): CSSProperties => {
@ -128,7 +141,14 @@
return !props.isHorizontal ? (
<SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menus} />
) : (
<BasicMenu {...(menuProps as any)} isHorizontal={props.isHorizontal} type={unref(getMenuType)} showLogo={unref(getIsShowLogo)} mode={unref(getComputedMenuMode as any)} items={menus} />
<BasicMenu
{...(menuProps as any)}
isHorizontal={props.isHorizontal}
type={unref(getMenuType)}
showLogo={unref(getIsShowLogo)}
mode={unref(getComputedMenuMode as any)}
items={menus}
/>
);
}

View File

@ -16,7 +16,16 @@ import { useI18n } from '/@/hooks/web/useI18n';
import { baseHandler } from './handler';
import { HandlerEnum, contentModeOptions, topMenuAlignOptions, getMenuTriggerOptions, routerTransitionOptions, menuTypeList, mixSidebarTriggerOptions, tabsThemeOptions } from './enum';
import {
HandlerEnum,
contentModeOptions,
topMenuAlignOptions,
getMenuTriggerOptions,
routerTransitionOptions,
menuTypeList,
mixSidebarTriggerOptions,
tabsThemeOptions,
} from './enum';
import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST, APP_PRESET_COLOR_LIST } from '/@/settings/designSetting';
@ -25,8 +34,19 @@ const { t } = useI18n();
export default defineComponent({
name: 'SettingDrawer',
setup(_, { attrs }) {
const { getContentMode, getShowFooter, getShowBreadCrumb, getShowBreadCrumbIcon, getShowLogo, getFullContent, getColorWeak, getGrayMode, getLockTime, getShowDarkModeToggle, getThemeColor } =
useRootSetting();
const {
getContentMode,
getShowFooter,
getShowBreadCrumb,
getShowBreadCrumbIcon,
getShowLogo,
getFullContent,
getColorWeak,
getGrayMode,
getLockTime,
getShowDarkModeToggle,
getThemeColor,
} = useRootSetting();
const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = useTransitionSetting();
@ -103,7 +123,12 @@ export default defineComponent({
return (
<>
<SwitchItem title={t('layout.setting.splitMenu')} event={HandlerEnum.MENU_SPLIT} def={unref(getSplit)} disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} />
<SwitchItem
title={t('layout.setting.splitMenu')}
event={HandlerEnum.MENU_SPLIT}
def={unref(getSplit)}
disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
/>
{/*<SwitchItem*/}
{/* title={t('layout.setting.mixSidebarFixed')}*/}
{/* event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR}*/}
@ -171,7 +196,12 @@ export default defineComponent({
options={triggerOptions}
disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
/>
<SelectItem title={t('layout.setting.contentMode')} event={HandlerEnum.CONTENT_MODE} def={unref(getContentMode)} options={contentModeOptions} />
<SelectItem
title={t('layout.setting.contentMode')}
event={HandlerEnum.CONTENT_MODE}
def={unref(getContentMode)}
options={contentModeOptions}
/>
<InputNumberItem
title={t('layout.setting.autoScreenLock')}
min={0}
@ -198,7 +228,12 @@ export default defineComponent({
function renderContent() {
return (
<>
<SwitchItem title={t('layout.setting.menuDrag')} event={HandlerEnum.MENU_HAS_DRAG} def={unref(getCanDrag)} disabled={!unref(getShowMenuRef)} />
<SwitchItem
title={t('layout.setting.menuDrag')}
event={HandlerEnum.MENU_HAS_DRAG}
def={unref(getCanDrag)}
disabled={!unref(getShowMenuRef)}
/>
<SwitchItem
title={t('layout.setting.collapseMenuDisplayName')}
event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE}
@ -206,7 +241,12 @@ export default defineComponent({
disabled={!unref(getShowMenuRef) || !unref(getCollapsed) || unref(getIsMixSidebar)}
/>
<SwitchItem title={t('layout.setting.tabs')} event={HandlerEnum.TABS_SHOW} def={unref(getShowMultipleTab)} />
<SwitchItem title={t('layout.setting.breadcrumb')} event={HandlerEnum.SHOW_BREADCRUMB} def={unref(getShowBreadCrumb)} disabled={!unref(getShowHeader)} />
<SwitchItem
title={t('layout.setting.breadcrumb')}
event={HandlerEnum.SHOW_BREADCRUMB}
def={unref(getShowBreadCrumb)}
disabled={!unref(getShowHeader)}
/>
{/*<SwitchItem*/}
{/* title={t('layout.setting.breadcrumbIcon')}*/}
@ -287,7 +327,7 @@ export default defineComponent({
}
return () => (
<BasicDrawer {...attrs} title={t('layout.setting.drawerTitle')} width={330} wrapClassName="setting-drawer">
<BasicDrawer {...attrs} title={t('layout.setting.drawerTitle')} width={330} class="setting-drawer">
{unref(getShowDarkModeToggle) && <Divider>{() => t('layout.setting.darkMode')}</Divider>}
{unref(getShowDarkModeToggle) && <AppDarkModeToggle class="mx-auto" />}
<Divider>{() => t('layout.setting.navMode')}</Divider>

View File

@ -1,7 +1,13 @@
<template>
<div :class="prefixCls">
<span> {{ title }}</span>
<Switch v-bind="getBindValue" @change="handleChange" :disabled="disabled" :checkedChildren="t('layout.setting.on')" :unCheckedChildren="t('layout.setting.off')" />
<Switch
v-bind="getBindValue"
@change="handleChange"
:disabled="disabled"
:checkedChildren="t('layout.setting.on')"
:unCheckedChildren="t('layout.setting.off')"
/>
</div>
</template>
<script lang="ts">

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