jeecgboot3.4.2版本发布,基础功能升级
parent
df0441c8f5
commit
b777ac0dc4
|
@ -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 |
|
@ -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?
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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-2061【样式】online表单超出4个 .. 省略显示
|
||||
//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-2061【样式】online表单超出4个 .. 省略显示
|
||||
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
|
||||
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
|
||||
return renderLabel;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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不会清空当前信息,界面显示上一次的数据
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
/** 用于搜索下拉框中的内容 */
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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="关闭">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
// 字典code格式:table,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,
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
pidValue: propTypes.string.def(''),
|
||||
//update-end---author:wangshuai ---date:20220620 for:JTreeSelect组件pidValue还原成空,否则会影响自定义组件树示例--------------
|
||||
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),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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([]);
|
||||
//替换treeNode中key字段为treeData中对应的字段
|
||||
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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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评论区域样式*/
|
||||
|
||||
// wrapper设为100%,兼容之前写过的弹窗自定义样式
|
||||
.jeecg-modal-wrapper,
|
||||
.jeecg-modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -194,6 +194,8 @@ export interface ModalProps {
|
|||
* @type number
|
||||
*/
|
||||
zIndex?: number;
|
||||
|
||||
enableComment?: boolean;
|
||||
}
|
||||
|
||||
export interface ModalWrapperProps {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
});
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
|
@ -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' });
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
import './index.less';
|
|
@ -0,0 +1,5 @@
|
|||
import BasicTree from './src/Tree.vue';
|
||||
|
||||
export { BasicTree };
|
||||
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
|
||||
export * from './src/typing';
|
|
@ -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) {
|
|
@ -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" />
|
|
@ -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;
|
||||
};
|
|
@ -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');
|
||||
});
|
||||
|
||||
// 上传前校验
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -32,6 +32,12 @@ interface OnlineColumn {
|
|||
slots?: ScopedSlots;
|
||||
//超过宽度将自动省略,暂不支持和排序筛选一起使用。
|
||||
ellipsis?: boolean;
|
||||
// 是否固定列
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
//字段类型 int/string
|
||||
dbType?:string;
|
||||
//他表字段用
|
||||
linkField?:string;
|
||||
}
|
||||
|
||||
export { OnlineColumn, HrefSlots };
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue