mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-29 16:05:31 +08:00
前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题
This commit is contained in:
33
jeecgboot-vue3/src/components/JVxeCustom/index.ts
Normal file
33
jeecgboot-vue3/src/components/JVxeCustom/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { registerComponent, registerAsyncComponent, registerASyncComponentReal } from '/@/components/jeecg/JVxeTable';
|
||||
import { JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { DictSearchSpanCell, DictSearchInputCell } from './src/components/JVxeSelectDictSearchCell';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
export async function registerJVxeCustom() {
|
||||
// ----------------- ⚠ 注意事项 ⚠ -----------------
|
||||
// 当组件内包含 BasicModal 时,必须使用异步引入!
|
||||
// 否则将会导致 i18n 失效!
|
||||
// ----------------- ⚠ 注意事项 ⚠ -----------------
|
||||
|
||||
// 注册【Popup】(普通封装方式)
|
||||
await registerAsyncComponent(JVxeTypes.popup, import('./src/components/JVxePopupCell.vue'));
|
||||
|
||||
// 注册【字典搜索下拉】组件(高级封装方式)
|
||||
registerComponent(JVxeTypes.selectDictSearch, DictSearchInputCell, DictSearchSpanCell);
|
||||
|
||||
// 注册【文件上传】组件
|
||||
await registerAsyncComponent(JVxeTypes.file, import('./src/components/JVxeFileCell.vue'));
|
||||
// 注册【图片上传】组件
|
||||
await registerAsyncComponent(JVxeTypes.image, import('./src/components/JVxeImageCell.vue'));
|
||||
// 注册【用户选择】组件
|
||||
await registerAsyncComponent(JVxeTypes.userSelect, import('./src/components/JVxeUserSelectCell.vue'));
|
||||
// 注册【部门选择】组件
|
||||
await registerAsyncComponent(JVxeTypes.departSelect, import('./src/components/JVxeDepartSelectCell.vue'));
|
||||
// 注册【省市区选择】组件
|
||||
// await registerAsyncComponent(JVxeTypes.pca, import('./src/components/JVxePcaCell.vue'));
|
||||
// update-begin--author:liaozhiyang---date:20240308---for:【QQYUN-8241】为避免首次加载china-area-data,JVxePcaCell组件需异步加载
|
||||
registerASyncComponentReal(
|
||||
JVxeTypes.pca,
|
||||
createAsyncComponent(() => import('./src/components/JVxePcaCell.vue'))
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20240308---for:【QQYUN-8241】为避免首次加载china-area-data,JVxePcaCell组件需异步加载
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<!-- <div>-->
|
||||
<!-- <a-input-->
|
||||
<!-- v-show="!departIds"-->
|
||||
<!-- @click="openSelect"-->
|
||||
<!-- placeholder="请点击选择部门"-->
|
||||
<!-- v-model="departNames"-->
|
||||
<!-- readOnly-->
|
||||
<!-- :disabled="componentDisabled"-->
|
||||
<!-- class="jvxe-select-input">-->
|
||||
<!-- <a-icon slot="prefix" type="cluster" title="部门选择控件"/>-->
|
||||
<!-- </a-input>-->
|
||||
<!-- <j-select-depart-modal-->
|
||||
<!-- ref="innerDepartSelectModal"-->
|
||||
<!-- :modal-width="modalWidth"-->
|
||||
<!-- :multi="multi"-->
|
||||
<!-- :rootOpened="rootOpened"-->
|
||||
<!-- :depart-id="departIds"-->
|
||||
<!-- @ok="handleOK"-->
|
||||
<!-- @initComp="initComp"/>-->
|
||||
<!-- <span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds">-->
|
||||
<!-- <span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>-->
|
||||
<!-- <a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
<div :class="[prefixCls]">
|
||||
<JSelectDept v-bind="getProps" @change="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
//import { JSelectDept } from '/@/components/Form';
|
||||
import JSelectDept from '/@/components/Form/src/jeecg/components/JSelectDept.vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
// import JSelectDepartModal from '@/components/jeecgbiz/modal/JSelectDepartModal'
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { isArray, isEmpty, isString } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeDepartSelectCell',
|
||||
components: { JSelectDept },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, handleChangeCommon, useCellDesign } = useJVxeComponent(props);
|
||||
const { prefixCls } = useCellDesign('depart-select');
|
||||
|
||||
const selectedValue = computed(() => {
|
||||
let val: any = innerValue.value;
|
||||
if (val == null) {
|
||||
return val;
|
||||
}
|
||||
if (isEmpty(val)) {
|
||||
return [];
|
||||
}
|
||||
if (isArray(val)) {
|
||||
return val;
|
||||
}
|
||||
if (isString(val)) {
|
||||
return (<string>val).split(',');
|
||||
}
|
||||
return [val];
|
||||
});
|
||||
|
||||
const multiple = computed(() => cellProps.value['multi'] != false);
|
||||
|
||||
const getProps = computed(() => {
|
||||
return {
|
||||
...cellProps.value,
|
||||
value: selectedValue.value,
|
||||
showButton: false,
|
||||
// 不允许搜索
|
||||
showSearch: false,
|
||||
// 设置最大的显示个数
|
||||
maxTagCount: 1,
|
||||
// 显示提示重写,去掉省略号
|
||||
maxTagPlaceholder: ({ length }) => '+' + length,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(values) {
|
||||
handleChangeCommon(values.join(','));
|
||||
}
|
||||
|
||||
return {
|
||||
prefixCls,
|
||||
selectedValue,
|
||||
multiple,
|
||||
cellProps,
|
||||
getProps,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// departNames: '',
|
||||
// departIds: '',
|
||||
// selectedOptions: [],
|
||||
// customReturnField: 'id',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// custProps() {
|
||||
// const { departIds, originColumn: col, caseId, cellProps } = this
|
||||
// return {
|
||||
// ...cellProps,
|
||||
// value: departIds,
|
||||
// field: col.field || col.key,
|
||||
// groupId: caseId,
|
||||
// class: 'jvxe-select',
|
||||
// }
|
||||
// },
|
||||
// componentDisabled() {
|
||||
// if (this.cellProps.disabled == true) {
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// },
|
||||
// modalWidth() {
|
||||
// if (this.cellProps.modalWidth) {
|
||||
// return this.cellProps.modalWidth
|
||||
// } else {
|
||||
// return 500
|
||||
// }
|
||||
// },
|
||||
// multi() {
|
||||
// if (this.cellProps.multi == false) {
|
||||
// return false
|
||||
// } else {
|
||||
// return true
|
||||
// }
|
||||
// },
|
||||
// rootOpened() {
|
||||
// if (this.cellProps.open == false) {
|
||||
// return false
|
||||
// } else {
|
||||
// return true
|
||||
// }
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
// innerValue: {
|
||||
// immediate: true,
|
||||
// handler(val) {
|
||||
// if (val == null || val === '') {
|
||||
// this.departIds = ''
|
||||
// } else {
|
||||
// this.departIds = val
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
},
|
||||
methods: {
|
||||
// openSelect() {
|
||||
// this.$refs.innerDepartSelectModal.show()
|
||||
// },
|
||||
// handleEmpty() {
|
||||
// this.handleOK('')
|
||||
// },
|
||||
// handleOK(rows, idstr) {
|
||||
// let value = ''
|
||||
// if (!rows && rows.length <= 0) {
|
||||
// this.departNames = ''
|
||||
// this.departIds = ''
|
||||
// } else {
|
||||
// value = rows.map(row => row[this.customReturnField]).join(',')
|
||||
// this.departNames = rows.map(row => row['departName']).join(',')
|
||||
// this.departIds = idstr
|
||||
// }
|
||||
// this.handleChangeCommon(this.departIds)
|
||||
// },
|
||||
// initComp(departNames) {
|
||||
// this.departNames = departNames
|
||||
// },
|
||||
// handleChange(value) {
|
||||
// this.handleChangeCommon(value)
|
||||
// },
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true,
|
||||
},
|
||||
translate: {
|
||||
enabled: false,
|
||||
},
|
||||
aopEvents: {
|
||||
editActived({ $event }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
props: this.props,
|
||||
className: '.ant-select .ant-select-selection-search-input',
|
||||
isClick: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-vxe-cell-depart-select';
|
||||
|
||||
.@{prefix-cls} {
|
||||
// 限制tag最大长度为100px,防止选中文字过多的选项时换行
|
||||
.ant-select .ant-select-selection-overflow-item {
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]" :key="fileKey">
|
||||
<div style="position: relative">
|
||||
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<LoadingOutlined />
|
||||
<span style="margin-left: 5px">上传中…</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-else-if="file.status === 'done'" :title="file.name">
|
||||
<Icon icon="ant-design:paper-clip" />
|
||||
<span style="margin-left: 5px">{{ ellipsisFileName }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-else :title="file.message || '上传失败'">
|
||||
<Icon icon="ant-design:exclamation-circle" style="color: red" />
|
||||
<span style="margin-left: 5px">{{ ellipsisFileName }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<Dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px">
|
||||
<a-tooltip title="操作">
|
||||
<Icon v-if="file.status !== 'uploading'" icon="ant-design:setting" style="cursor: pointer" />
|
||||
</a-tooltip>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><Icon icon="ant-design:download" /> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item :disabled="cellProps.disabled" v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><Icon icon="ant-design:delete" /> 删除</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item :disabled="cellProps.disabled" @click="handleMoreOperation">
|
||||
<span><Icon icon="ant-design:bars" /> 更多</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-upload
|
||||
v-if="!cellProps.disabledTable"
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{ isup: 1 }"
|
||||
:multiple="false"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload"
|
||||
>
|
||||
<a-button preIcon="ant-design:upload">{{ originColumn.btnText || '点击上传' }}</a-button>
|
||||
</a-upload>
|
||||
<JUploadModal :value="modalValue" @register="registerModel" @change="onModalChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { useFileCell, enhanced, components } from '../hooks/useFileCell';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeFileCell',
|
||||
components: components,
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
return useFileCell(props, UploadTypeEnum.file);
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: enhanced,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]" :key="fileKey">
|
||||
<div class="j-vxe-image-list">
|
||||
<template v-if="!file || !(file['url'] || file['path'] || file['message'])">
|
||||
<a-tooltip :title="'请稍后: ' + JSON.stringify(file) + (file['url'] || file['path'] || file['message'])">
|
||||
<LoadingOutlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="file['path']">
|
||||
<template v-for="src of imgList">
|
||||
<img class="j-vxe-image" :src="src" alt="图片错误" @[clickEvent]="handleMoreOperation" @click="handlePreview" />
|
||||
</template>
|
||||
</template>
|
||||
<a-tooltip v-else :title="file.message || '上传失败'" @[clickEvent]="handleClickShowImageError">
|
||||
<Icon icon="ant-design:exclamation-circle" style="color: red" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div class="j-vxe-image-upload" v-if="!cellProps.disabledTable">
|
||||
<a-upload
|
||||
:accept="acceptFileType"
|
||||
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" />
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
<JUploadModal :value="modalValue" @register="registerModel" @change="onModalChange" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent ,unref } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { components, enhanced, useFileCell } from '../hooks/useFileCell';
|
||||
import { createImgPreview } from '/@/components/Preview/index';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeImageCell',
|
||||
components: components,
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
|
||||
const { createErrorModal } = useMessage();
|
||||
const setup = useFileCell(props, UploadTypeEnum.image, { multiple: true });
|
||||
// update-begin--author:liaozhiyang---date:20240604---for:【TV360X-470】jVxetable上传图片组件限制类型
|
||||
const acceptFileType = 'image/*';
|
||||
// update-end--author:liaozhiyang---date:20240604---for:【TV360X-470】jVxetable上传图片组件限制类型
|
||||
// update-begin--author:liaozhiyang---date:20240105---for:【issues/953】online子表vxe-table展现形式详情图片上传可点击
|
||||
const clickEvent = computed(() => {
|
||||
return unref(setup.cellProps).disabled ? null : 'click';
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240105---for:【issues/953】online子表vxe-table展现形式详情图片上传可点击
|
||||
const { innerFile, maxCount } = setup;
|
||||
|
||||
const imgList = computed(() => {
|
||||
if (innerFile.value) {
|
||||
if (innerFile.value['url']) {
|
||||
return [innerFile.value['url']];
|
||||
} else if (innerFile.value['path']) {
|
||||
let paths = innerFile.value['path'].split(',');
|
||||
return paths.map((p) => getFileAccessHttpUrl(p));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// 弹出上传出错详细信息
|
||||
function handleClickShowImageError() {
|
||||
let file = innerFile.value || null;
|
||||
if (file && file['message']) {
|
||||
createErrorModal({
|
||||
title: '上传出错',
|
||||
content: '错误信息:' + file['message'],
|
||||
maskClosable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240523---for:【TV360X-121】jvxetable文件组件禁用状态(详情)下可下载
|
||||
const handlePreview = () => {
|
||||
if (unref(setup.cellProps).disabled) {
|
||||
createImgPreview({ imageList: imgList.value });
|
||||
}
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240523---for:【TV360X-121】jvxetable文件组件禁用状态(详情)下可下载
|
||||
return {
|
||||
...setup,
|
||||
imgList,
|
||||
maxCount,
|
||||
handleClickShowImageError,
|
||||
clickEvent,
|
||||
handlePreview,
|
||||
acceptFileType,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: enhanced,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.j-vxe-image {
|
||||
height: 32px;
|
||||
max-width: 100px !important;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-plus {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
background-color: #fafafa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.j-vxe-image-list,
|
||||
.j-vxe-image-upload {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<a-cascader v-bind="getProps" class="pca-select" @change="handleChange" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { regionData, getRealCode } from '/@/components/Form/src/utils/areaDataUtil';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxePcaCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, handleChangeCommon } = useJVxeComponent(props);
|
||||
|
||||
const selectedValue = computed(() => {
|
||||
let val: any = innerValue.value;
|
||||
if (!val) {
|
||||
return []
|
||||
}
|
||||
let arr = getRealCode(val, 3);
|
||||
return arr;
|
||||
});
|
||||
|
||||
const getProps = computed(() => {
|
||||
return {
|
||||
...cellProps.value,
|
||||
options: regionData,
|
||||
showOverflow: false,
|
||||
value: selectedValue.value,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(arr) {
|
||||
let str = '';
|
||||
if(arr && arr.length==3){
|
||||
str = arr[2];
|
||||
}
|
||||
handleChangeCommon(str);
|
||||
}
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
selectedValue,
|
||||
getProps
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true,
|
||||
},
|
||||
translate: {
|
||||
enabled: false,
|
||||
},
|
||||
aopEvents: {
|
||||
editActived({ $event }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
props: this.props,
|
||||
className: '.ant-select .ant-select-selection-search-input',
|
||||
isClick: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.pca-select{
|
||||
.ant-select-selection-placeholder{
|
||||
color: #bfbfbf !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<JPopup v-bind="popupProps" @focus="handleFocus" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
//import { JPopup } from '/@/components/Form';
|
||||
import JPopup from '/@/components/Form/src/jeecg/components/JPopup.vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { dispatchEvent, vModel } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { isEmpty } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxePopupCell',
|
||||
components: { JPopup },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, row, originColumn, cellProps, handleChangeCommon } = useJVxeComponent(props);
|
||||
const groupId = ref<string>('j-vxe-popup');
|
||||
const popupProps = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选
|
||||
return {
|
||||
...cellProps.value,
|
||||
value: innerValue.value,
|
||||
field: originColumn.value.field || originColumn.value.key,
|
||||
code: originColumn.value.popupCode,
|
||||
fieldConfig: originColumn.value.fieldConfig,
|
||||
// orgFields: originColumn.value.orgFields,
|
||||
// destFields: originColumn.value.destFields,
|
||||
groupId: groupId.value,
|
||||
param: originColumn.value.params,
|
||||
sorter: originColumn.value.sorter,
|
||||
setFieldsValue: (values) => {
|
||||
if (!isEmpty(values)) {
|
||||
let popupValue = '';
|
||||
Object.keys(values).forEach((key) => {
|
||||
let currentValue = values[key];
|
||||
// 当前列直接赋值,其他列通过vModel赋值
|
||||
if (key === originColumn.value.key) {
|
||||
popupValue = currentValue;
|
||||
} else {
|
||||
vModel(currentValue, row, key);
|
||||
}
|
||||
});
|
||||
handleChangeCommon(popupValue);
|
||||
}
|
||||
},
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20230811---for:【issues/675】子表字段Popup弹框数据不更新
|
||||
const handleFocus = () => {
|
||||
groupId.value = '';
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20230811---for:【issues/675】子表字段Popup弹框数据不更新
|
||||
return {
|
||||
handleFocus,
|
||||
popupProps,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived({ $event }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
props: this.props,
|
||||
className: '.ant-input',
|
||||
isClick: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,288 @@
|
||||
import { computed, ref, watch, defineComponent, h } from 'vue';
|
||||
import { cloneDeep, debounce } from 'lodash-es';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
|
||||
import { ajaxGetDictItems, getDictItemsByCode } from '/@/utils/dict';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { useResolveComponent as rc } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
/** value - label map,防止重复查询(刷新清空缓存) */
|
||||
const LabelMap = new Map<string, any>();
|
||||
// 请求id
|
||||
let requestId = 0;
|
||||
|
||||
/** 显示组件,自带翻译 */
|
||||
export const DictSearchSpanCell = defineComponent({
|
||||
name: 'JVxeSelectSearchSpanCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerOptions, innerSelectValue, innerValue } = useSelectDictSearch(props);
|
||||
return () => {
|
||||
return h('span', {}, [filterDictText(innerOptions.value, innerSelectValue.value || innerValue.value)]);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// 输入选择组件
|
||||
export const DictSearchInputCell = defineComponent({
|
||||
name: 'JVxeSelectSearchInputCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { createMessage } = useMessage();
|
||||
const { dict, loading, isAsync, options, innerOptions, originColumn, cellProps, innerSelectValue, handleChangeCommon } =
|
||||
useSelectDictSearch(props);
|
||||
const hasRequest = ref(false);
|
||||
// 提示信息
|
||||
const tipsContent = computed(() => {
|
||||
return originColumn.value.tipsContent || '请输入搜索内容';
|
||||
});
|
||||
// 筛选函数
|
||||
const filterOption = computed(() => {
|
||||
if (isAsync.value) {
|
||||
//【jeecgboot-vue3/issues/I5QRT8】JVxeTypes.selectDictSearch sync问题
|
||||
return ()=>true;
|
||||
}
|
||||
return (input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
});
|
||||
|
||||
/** 加载数据 */
|
||||
const loadData = debounce((value) => {
|
||||
const currentRequestId = ++requestId;
|
||||
loading.value = true;
|
||||
innerOptions.value = [];
|
||||
if (value == null || value.trim() === '') {
|
||||
loading.value = false;
|
||||
hasRequest.value = false;
|
||||
return;
|
||||
}
|
||||
// 字典code格式:table,text,code
|
||||
hasRequest.value = true;
|
||||
loadDictByKeyword(dict.value, value)
|
||||
.then((res) => {
|
||||
if (currentRequestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
let { success, result, message } = res;
|
||||
if (success) {
|
||||
innerOptions.value = result;
|
||||
result.forEach((item) => {
|
||||
LabelMap.set(item.value, [item]);
|
||||
});
|
||||
} else {
|
||||
createMessage.warning(message || '查询失败');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}, 300);
|
||||
|
||||
function handleChange(selectedValue) {
|
||||
innerSelectValue.value = selectedValue;
|
||||
handleChangeCommon(innerSelectValue.value);
|
||||
}
|
||||
|
||||
function handleSearch(value) {
|
||||
if (isAsync.value) {
|
||||
// 在输入时也应该开启加载,因为loadData加了消抖,所以会有800ms的用户主观上认为的卡顿时间
|
||||
loading.value = true;
|
||||
if (innerOptions.value.length > 0) {
|
||||
innerOptions.value = [];
|
||||
}
|
||||
loadData(value);
|
||||
}
|
||||
}
|
||||
|
||||
function renderOptionItem() {
|
||||
let optionItems: any[] = [];
|
||||
options.value.forEach(({ value, text, label, title, disabled }) => {
|
||||
optionItems.push(
|
||||
h(
|
||||
rc('a-select-option'),
|
||||
{
|
||||
key: value,
|
||||
value: value,
|
||||
disabled: disabled,
|
||||
},
|
||||
{
|
||||
default: () => text || label || title,
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
return optionItems;
|
||||
}
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
rc('a-select'),
|
||||
{
|
||||
...cellProps.value,
|
||||
value: innerSelectValue.value,
|
||||
filterOption: filterOption.value,
|
||||
showSearch: true,
|
||||
allowClear: true,
|
||||
autofocus: true,
|
||||
defaultOpen: true,
|
||||
style: 'width: 100%',
|
||||
onSearch: handleSearch,
|
||||
onChange: handleChange,
|
||||
},
|
||||
{
|
||||
default: () => renderOptionItem(),
|
||||
notFoundContent: () => {
|
||||
if (loading.value) {
|
||||
return h(rc('a-spin'), { size: 'small' });
|
||||
} else if (hasRequest.value) {
|
||||
return h('div', '没有查询到任何数据');
|
||||
} else {
|
||||
return h('div', [tipsContent.value]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived({ $event }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
props: this.props,
|
||||
className: '.ant-select .ant-select-selection-search-input',
|
||||
isClick: false,
|
||||
handler: (el) => el.focus(),
|
||||
});
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
|
||||
function useSelectDictSearch(props) {
|
||||
const setup = useJVxeComponent(props);
|
||||
const { innerValue, originColumn } = setup;
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 内部选择值
|
||||
const innerSelectValue = ref(null);
|
||||
// 内部 options
|
||||
const innerOptions = ref<any[]>([]);
|
||||
|
||||
const dict = computed(() => originColumn.value.dict);
|
||||
// 是否是异步模式
|
||||
const isAsync = computed(() => {
|
||||
let isAsync = originColumn.value.async;
|
||||
return isAsync != null && isAsync !== '' ? !!isAsync : true;
|
||||
});
|
||||
const options = computed(() => {
|
||||
if (isAsync.value) {
|
||||
return innerOptions.value;
|
||||
} else {
|
||||
return originColumn.value.options || [];
|
||||
}
|
||||
});
|
||||
|
||||
/** 公共属性监听 */
|
||||
watch(
|
||||
innerValue,
|
||||
(value: string) => {
|
||||
if (value == null || value === '') {
|
||||
innerSelectValue.value = null;
|
||||
} else {
|
||||
loadDataByValue(value);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(dict, () => loadDataByDict());
|
||||
|
||||
// 根据 value 查询数据,用于回显
|
||||
async function loadDataByValue(value) {
|
||||
if (isAsync.value) {
|
||||
if (innerSelectValue.value !== value) {
|
||||
if (LabelMap.has(value)) {
|
||||
innerOptions.value = cloneDeep(LabelMap.get(value));
|
||||
} else {
|
||||
let result = await loadDictItem(dict.value, value);
|
||||
if (result && result.length > 0) {
|
||||
innerOptions.value = [{ value: value, text: result[0] }];
|
||||
LabelMap.set(value, cloneDeep(innerOptions.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
innerSelectValue.value = (value || '').toString();
|
||||
}
|
||||
|
||||
// 初始化字典
|
||||
async function loadDataByDict() {
|
||||
if (!isAsync.value) {
|
||||
// 如果字典项集合有数据
|
||||
if (!originColumn.value.options || originColumn.value.options.length === 0) {
|
||||
// 根据字典Code, 初始化字典数组
|
||||
let dictStr = '';
|
||||
if (dict.value) {
|
||||
let arr = dict.value.split(',');
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
let tbInfo = arr[0].split('where');
|
||||
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1]);
|
||||
} else {
|
||||
dictStr = dict.value;
|
||||
}
|
||||
if (dict.value.indexOf(',') === -1) {
|
||||
//优先从缓存中读取字典配置
|
||||
let cache = getDictItemsByCode(dict.value);
|
||||
if (cache) {
|
||||
innerOptions.value = cache;
|
||||
return;
|
||||
}
|
||||
}
|
||||
let { success, result } = await ajaxGetDictItems(dictStr, null);
|
||||
if (success) {
|
||||
innerOptions.value = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...setup,
|
||||
loading,
|
||||
innerOptions,
|
||||
innerSelectValue,
|
||||
dict,
|
||||
isAsync,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取字典项 */
|
||||
function loadDictItem(dict: string, key: string) {
|
||||
return defHttp.get({
|
||||
url: `/sys/dict/loadDictItem/${dict}`,
|
||||
params: {
|
||||
key: key,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据关键字获取字典项(搜索) */
|
||||
function loadDictByKeyword(dict: string, keyword: string) {
|
||||
return defHttp.get(
|
||||
{
|
||||
url: `/sys/dict/loadDict/${dict}`,
|
||||
params: {
|
||||
keyword: keyword,
|
||||
},
|
||||
},
|
||||
{
|
||||
isTransformResponse: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div :class="[prefixCls]">
|
||||
<JSelectUser v-bind="getProps" @change="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
//import { JSelectUser } from '/@/components/Form';
|
||||
import JSelectUser from '/@/components/Form/src/jeecg/components/JSelectUser.vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { isArray, isEmpty, isString } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeUserSelectCell',
|
||||
components: { JSelectUser },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, handleChangeCommon, useCellDesign } = useJVxeComponent(props);
|
||||
const { prefixCls } = useCellDesign('user-select');
|
||||
|
||||
const selectedValue = computed(() => {
|
||||
let val: any = innerValue.value;
|
||||
if (val == null) {
|
||||
return val;
|
||||
}
|
||||
if (isEmpty(val)) {
|
||||
return [];
|
||||
}
|
||||
if (isArray(val)) {
|
||||
return val;
|
||||
}
|
||||
if (isString(val)) {
|
||||
// @ts-ignore
|
||||
return val.split(',');
|
||||
}
|
||||
return [val];
|
||||
});
|
||||
// @ts-ignore
|
||||
const multiple = computed(() => cellProps.value.multi != false);
|
||||
|
||||
const getProps = computed(() => {
|
||||
return {
|
||||
...cellProps.value,
|
||||
value: selectedValue.value,
|
||||
showButton: false,
|
||||
// 不允许搜索
|
||||
showSearch: false,
|
||||
// 设置最大的显示个数
|
||||
maxTagCount: 1,
|
||||
// 显示提示重写,去掉省略号
|
||||
maxTagPlaceholder: ({ length }) => '+' + length,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(values) {
|
||||
handleChangeCommon(values.join(','));
|
||||
}
|
||||
|
||||
return {
|
||||
prefixCls,
|
||||
selectedValue,
|
||||
multiple,
|
||||
cellProps,
|
||||
getProps,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true,
|
||||
},
|
||||
translate: {
|
||||
enabled: false,
|
||||
},
|
||||
aopEvents: {
|
||||
editActived({ $event }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
props: this.props,
|
||||
className: '.ant-select .ant-select-selection-search-input',
|
||||
isClick: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-vxe-cell-user-select';
|
||||
|
||||
.@{prefix-cls} {
|
||||
// 限制tag最大长度为100px,防止选中文字过多的选项时换行
|
||||
.ant-select .ant-select-selection-overflow-item {
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,102 @@
|
||||
import { computed } from 'vue';
|
||||
import { fileGetValue, fileSetValue, useJVxeUploadCell } from '/@/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell';
|
||||
import { uploadUrl } from '/@/api/common/api';
|
||||
import { JUploadModal, UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/src/types/JVxeComponent';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { Dropdown } from 'ant-design-vue';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export function useFileCell(props, fileType: UploadTypeEnum, options?) {
|
||||
const setup = useJVxeUploadCell(props, { token: true, action: uploadUrl, ...options });
|
||||
|
||||
const { innerFile, handleChangeCommon, originColumn } = setup;
|
||||
const [registerModel, { openModal }] = useModal();
|
||||
|
||||
// 截取文件名
|
||||
const ellipsisFileName = computed(() => {
|
||||
let length = 5;
|
||||
let file = innerFile.value;
|
||||
if (!file || !file.name) {
|
||||
return '';
|
||||
}
|
||||
if (file.name.length > length) {
|
||||
return file.name.substr(0, length) + '…';
|
||||
}
|
||||
return file.name;
|
||||
});
|
||||
|
||||
const modalValue = computed(() => {
|
||||
if (innerFile.value) {
|
||||
if (innerFile.value['url']) {
|
||||
return innerFile.value['url'];
|
||||
} else if (innerFile.value['path']) {
|
||||
return innerFile.value['path'];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const maxCount = computed(() => {
|
||||
let maxCount = originColumn.value.maxCount;
|
||||
// online 扩展JSON
|
||||
if (originColumn.value && originColumn.value.fieldExtendJson) {
|
||||
let json = JSON.parse(originColumn.value.fieldExtendJson);
|
||||
maxCount = json.uploadnum ? json.uploadnum : 0;
|
||||
}
|
||||
return maxCount ?? 0;
|
||||
});
|
||||
|
||||
// 点击更多按钮
|
||||
function handleMoreOperation() {
|
||||
openModal(true, {
|
||||
removeConfirm: true,
|
||||
mover: true,
|
||||
download: true,
|
||||
...originColumn.value.props,
|
||||
maxCount: maxCount.value,
|
||||
fileType: fileType,
|
||||
});
|
||||
}
|
||||
|
||||
// 更多上传回调
|
||||
function onModalChange(path) {
|
||||
if (path) {
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-235】富文本禁用状态下图片上传按钮文字看不清
|
||||
if (innerFile.value === null) {
|
||||
innerFile.value = {};
|
||||
}
|
||||
// update-end-author:liaozhiyang---date:20240524---for:【TV360X-235】富文本禁用状态下图片上传按钮文字看不清
|
||||
innerFile.value.path = path;
|
||||
handleChangeCommon(innerFile.value);
|
||||
} else {
|
||||
//update-begin-author:liusq date:2023-06-05 for: [issues/530]JVxeTable 的JVxeTypes.image类型,无法全部删除上传图片
|
||||
handleChangeCommon(null);
|
||||
//update-end-author:liusq date:2023-06-05 for: [issues/530]JVxeTable 的JVxeTypes.image类型,无法全部删除上传图片
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...setup,
|
||||
modalValue,
|
||||
maxCount,
|
||||
ellipsisFileName,
|
||||
registerModel,
|
||||
onModalChange,
|
||||
handleMoreOperation,
|
||||
};
|
||||
}
|
||||
|
||||
export const components = {
|
||||
Icon,
|
||||
Dropdown,
|
||||
LoadingOutlined,
|
||||
JUploadModal,
|
||||
};
|
||||
|
||||
export const enhanced = {
|
||||
switches: { visible: true },
|
||||
getValue: (value) => fileGetValue(value),
|
||||
setValue: (value) => fileSetValue(value),
|
||||
} as JVxeComponent.EnhancedPartial;
|
||||
Reference in New Issue
Block a user