--添加测试模块
This commit is contained in:
@ -24,6 +24,7 @@ import SelectDepartment from './components/SelectDepartment.vue';
|
||||
import SelectDepartmentV2 from './components/SelectDepartmentV2.vue';
|
||||
import SelectUser from './components/SelectUser.vue';
|
||||
import SelectUserV2 from './components/SelectUserV2.vue';
|
||||
import SelectUserShowTree from './components/SelectUserShowTree.vue';
|
||||
import CommonInfo from './components/CommonInfo.vue';
|
||||
import SelectArea from './components/SelectArea.vue';
|
||||
import AutoCodeRule from './components/AutoCodeRule.vue';
|
||||
@ -66,6 +67,7 @@ import { XjrDatePicker } from '/@/components/DatePicker';
|
||||
import { Slider } from '/@/components/Slider';
|
||||
import { CodeTextArea } from '/@/components/Input';
|
||||
import { OneForOne } from '/@/components/OneForOne';
|
||||
import { CustomComponent } from '/@/components/CustomComponent';
|
||||
import SubForm from './components/SubFormV2.vue';
|
||||
import ErpApply from './components/ErpApply.vue';
|
||||
import ErpUpload from './components/ErpUpload.vue';
|
||||
@ -102,6 +104,7 @@ componentMap.set('Rate', Rate);
|
||||
componentMap.set('DeptTree', SelectDepartment);
|
||||
componentMap.set('Dept', SelectDepartmentV2);
|
||||
componentMap.set('User', SelectUserV2);
|
||||
componentMap.set('UserTree', SelectUserShowTree);
|
||||
componentMap.set('Info', CommonInfo);
|
||||
componentMap.set('Area', SelectArea);
|
||||
componentMap.set('SubForm', SubForm);
|
||||
@ -145,6 +148,7 @@ componentMap.set('ErpApply', ErpApply);
|
||||
componentMap.set('ErpUpload', ErpUpload);
|
||||
componentMap.set('ErpCheck', ErpCheck);
|
||||
componentMap.set('AutoComplete', AutoComplete);
|
||||
componentMap.set('CustomComponent', CustomComponent);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="field-readonly">
|
||||
<div :class="isWordWrap ? '' : 'field-readonly'" :title="fieldValue">
|
||||
<div v-if="schema.component === 'RichTextEditor'" v-html="htmlValue"> </div>
|
||||
{{ fieldValue }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
@ -17,6 +17,14 @@
|
||||
const textComponents = ['Input', 'AutoCodeRule', 'DatePicker', 'TimePicker', 'Text', 'InputTextArea', 'InputNumber'];
|
||||
const fieldValue = ref(genFieldValue(props.value));
|
||||
const htmlValue = ref(getHtmlValue(props.value));
|
||||
const isWordWrap = computed(() => {
|
||||
const schema = props.schema;
|
||||
const { componentProps } = schema;
|
||||
if (componentProps?.wordWrap) {
|
||||
return componentProps.wordWrap;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
function parseRangeVal(val, component) {
|
||||
if (component !== 'RangePicker' || !val) {
|
||||
@ -75,8 +83,10 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="less" scoped>
|
||||
.field-readonly {
|
||||
white-space: pre-wrap;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="depart-select" style="width: 100%" @click="show">
|
||||
<a-input v-model:value="departNames" :bordered="bordered" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
|
||||
<div v-if="disabled && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="departNames">
|
||||
{{departNames}}
|
||||
</div>
|
||||
<a-input v-model:value="departNames" v-if="disabledShowBorder || !disabled" :bordered="bordered" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
|
||||
<template v-if="prefix" #prefix>
|
||||
<Icon :icon="prefix" />
|
||||
</template>
|
||||
@ -81,7 +84,16 @@
|
||||
justCompany: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
wordWrap:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabledShowBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
formData:Object
|
||||
});
|
||||
const selectedNodes = ref([]);
|
||||
const loading = ref(true);
|
||||
@ -181,7 +193,11 @@
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
if(props.defaultDeptField) {
|
||||
defaultDepts.value = props.row[props.defaultDeptField]
|
||||
if(props.row) {
|
||||
defaultDepts.value = props.row[props.defaultDeptField];
|
||||
}else{
|
||||
defaultDepts.value = props.formData[props.defaultDeptField];
|
||||
}
|
||||
}
|
||||
if (props.value) {
|
||||
if(props.isArray && !props.value.length) {
|
||||
@ -227,6 +243,11 @@
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.field-readonly {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis
|
||||
}
|
||||
.choose-dep-box {
|
||||
display: flex;
|
||||
height: 95%;
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
<template>
|
||||
<div class="user-select-list">
|
||||
<div class="user-select-item" v-for="item in data" @click="selectItem(item)">
|
||||
<div class="user-select-item-left" v-if="showDep && item.departmentPathName">
|
||||
{{ `${item.name}(${item.departmentPathName})` }}
|
||||
</div>
|
||||
<div class="user-select-item-left" v-else>
|
||||
{{ `${item.name}` }}
|
||||
</div>
|
||||
<div class="user-select-item-right">
|
||||
<div class="select-circle" :class="item.selected ? 'selected' : ''" v-if="!viewList">
|
||||
<check-outlined v-if="item.selected" />
|
||||
<template v-for="item in data">
|
||||
<div class="user-select-item" @click="selectItem(item)" :class="isDisabledItem(item) ? 'disabled-item' : ''">
|
||||
<div class="user-select-item-left" v-if="showDep && item.departmentPathName">
|
||||
{{ `${item.name}(${item.departmentPathName})` }}
|
||||
</div>
|
||||
<div class="delete-circle" v-if="canDel" @click="delItem(item)">
|
||||
<CloseOutlined />
|
||||
<div class="user-select-item-left" v-else>
|
||||
{{ `${item.name}` }}
|
||||
</div>
|
||||
<div class="user-select-item-right" v-if="!isDisabledItem(item)">
|
||||
<div class="select-circle" :class="item.selected ? 'selected' : ''" v-if="!viewList">
|
||||
<check-outlined v-if="item.selected" />
|
||||
</div>
|
||||
<div class="delete-circle" v-if="canDel" @click="delItem(item)">
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="empty-box" v-if="!data.length">
|
||||
<a-empty :image="simpleImage">
|
||||
<template #description>
|
||||
@ -58,8 +61,18 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabledSelectList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
})
|
||||
function isDisabledItem(item) {
|
||||
return props.disabledSelectList.includes(item.id)
|
||||
}
|
||||
function selectItem(item) {
|
||||
if(isDisabledItem(item)) {
|
||||
return
|
||||
}
|
||||
emits('selectId', item.id)
|
||||
}
|
||||
function delItem(item) {
|
||||
@ -83,6 +96,9 @@ function delItem(item) {
|
||||
// margin: auto;
|
||||
}
|
||||
}
|
||||
.disabled-item {
|
||||
background: #e3e3e3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -102,11 +118,14 @@ function delItem(item) {
|
||||
overflow: hidden; //超出隐藏
|
||||
text-overflow: ellipsis; //文本超出时显示省略号
|
||||
white-space: nowrap; //设置文本不换行
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-select-item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
|
||||
.select-circle {
|
||||
width: 16px;
|
||||
|
||||
143
src/components/Form/src/components/SelectUserShowTree.vue
Normal file
143
src/components/Form/src/components/SelectUserShowTree.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="selectUser-show-tree">
|
||||
<div class="content">
|
||||
<div class="user-list" v-if="treeData.length" :style="{height: height, width: width}">
|
||||
<a-tree v-model:expandedKeys="expandedKeys" :treeData="treeData" :key="treeKey" :fieldNames="{
|
||||
children: 'children',
|
||||
title: 'name',
|
||||
key: 'id'
|
||||
}">
|
||||
</a-tree>
|
||||
</div>
|
||||
<a-button style="margin-top: 10px" @click="showDialog" v-if="!disabled">选择用户</a-button>
|
||||
</div>
|
||||
<SelectUserV2
|
||||
ref="selectUser"
|
||||
v-model:value="selected"
|
||||
v-bind="filteredProps"
|
||||
justDialog
|
||||
@change="changeSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import SelectUserV2 from './SelectUserV2.vue'
|
||||
import { getUserByDepartTree } from '/@/api/system/user';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const emits = defineEmits(['update:value', 'change']);
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
prefix: String,
|
||||
suffix: String,
|
||||
placeholder: String,
|
||||
readonly: Boolean,
|
||||
disabled: Boolean,
|
||||
size: String,
|
||||
justDialog: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
row: Object, // 行数据,在表格里用的到
|
||||
sepTextField: String, // 将文本表示存储在独立字段
|
||||
onlyUserCompany: { // 仅在登录用户公司范围内筛选
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
buttonShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultDeptField: { // 默认选择公司范围key值
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
justCompany: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isOnlyCurrentDepartment: { // 仅当前部门,无下级部门
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
});
|
||||
|
||||
// 过滤掉value属性,避免冲突
|
||||
const filteredProps = computed(() => {
|
||||
const { value, ...rest } = props;
|
||||
return rest;
|
||||
});
|
||||
const treeKey = ref(1)
|
||||
|
||||
const selected = ref(props.value || [])
|
||||
const selectUser = ref()
|
||||
|
||||
const expandedKeys = ref([])
|
||||
|
||||
const treeData = ref([])
|
||||
|
||||
const changeSelect = async (ids, options) => {
|
||||
emits('update:value', ids)
|
||||
emits('change', ids, options)
|
||||
}
|
||||
|
||||
async function getTreeData(list) {
|
||||
let tree = await getUserByDepartTree(list)
|
||||
expandedKeys.value = []
|
||||
setTreeData(tree)
|
||||
treeData.value = tree
|
||||
treeKey.value = Date.now()
|
||||
}
|
||||
|
||||
const setTreeData = (list, indexList = []) => {
|
||||
list.forEach((item, index) => {
|
||||
let list = cloneDeep(indexList)
|
||||
list.push(index)
|
||||
item.id = list.join('-')
|
||||
expandedKeys.value.push(item.id)
|
||||
if(item.children && item.children.length){
|
||||
setTreeData(item.children, cloneDeep(list))
|
||||
} else if (item.users.length) {
|
||||
item.children = item.users
|
||||
}
|
||||
})
|
||||
}
|
||||
const showDialog = () => {
|
||||
selectUser.value.show()
|
||||
}
|
||||
// 监听props.value变化
|
||||
watch(() => props.value, (newVal) => {
|
||||
selected.value = newVal
|
||||
if(selected.value) {
|
||||
getTreeData(selected.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.selectUser-show-tree {
|
||||
.content{
|
||||
.user-list {
|
||||
overflow: auto;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div :class="{ disabled }" class="form-select-user" @click="show">
|
||||
<a-button type="primary" v-if="buttonShow">{{ t('添加人员') }}</a-button>
|
||||
<a-input v-model:value="userNames" v-if="!buttonShow" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
|
||||
<a-button type="primary" v-if="buttonShow && !justDialog">{{ t('添加人员') }}</a-button>
|
||||
<div v-if="disabled && !buttonShow && !justDialog && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="userNames">
|
||||
{{userNames}}
|
||||
</div>
|
||||
<a-input v-model:value="userNames" v-if="!buttonShow && !justDialog && (disabledShowBorder || !disabled)" :placeholder="placeholder" :size="size" readonly>
|
||||
<template v-if="prefix" #prefix>
|
||||
<Icon :icon="prefix" />
|
||||
</template>
|
||||
@ -9,7 +12,7 @@
|
||||
<Icon :icon="suffix" />
|
||||
</template>
|
||||
</a-input>
|
||||
<ModalPanel :title="t('选择人员')" :visible="visible" :width="900" class="select-user-model" @close="close" @submit="submit">
|
||||
<ModalPanel :title="t('选择人员')" :visible="visible" :width="900" class="select-user-model" @close="close" @submit="submit" :confirmLoading="confirmLoading">
|
||||
<div class="select-user">
|
||||
<div class="select-user-left">
|
||||
<a-tabs v-model:activeKey="activeKey" :tabBarGutter="20">
|
||||
@ -29,20 +32,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-select-box">
|
||||
<div class="select-depart-all" @click="selectAll">
|
||||
<div class="select-depart-all" @click="selectAll" v-if="multiple">
|
||||
<span>全选</span>
|
||||
<div class="select-circle" :class="departAllSelected ? 'selected' : ''" v-if="!viewList">
|
||||
<check-outlined v-if="departAllSelected" />
|
||||
</div>
|
||||
</div>
|
||||
<SelectUserListV2 :multiple="multiple" :data="searchDepartMemberList" emptyDescription="暂无人员" @selectId="changeDepMemberSelect"></SelectUserListV2>
|
||||
<SelectUserListV2 :multiple="multiple" :data="searchDepartMemberList" emptyDescription="暂无人员" @selectId="changeDepMemberSelect" :disabledSelectList="disabledSelectList"></SelectUserListV2>
|
||||
<div v-if="false" class="user-select-pagination">
|
||||
<a-pagination v-model:current="searchDepartMemberParams.limit" :pageSize="searchDepartMemberParams.size" :total="searchDepartMemberTotal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="activeKey === 'allPerson'" class="all-user-select-box">
|
||||
<SelectUserListV2 :multiple="multiple" :data="searchAllMemberList" @selectId="changeMemberSelect" show-dep></SelectUserListV2>
|
||||
<SelectUserListV2 :multiple="multiple" :data="searchAllMemberList" @selectId="changeMemberSelect" show-dep :disabledSelectList="disabledSelectList"></SelectUserListV2>
|
||||
<div v-if="searchAllMemberTotal > 25" class="all-user-select-pagination">
|
||||
<a-form-item label="" name="pagination">
|
||||
<a-pagination
|
||||
@ -64,14 +67,14 @@
|
||||
<!-- <div class="selected-user-title sub-title">
|
||||
已选列表
|
||||
</div> -->
|
||||
<SelectUserListV2 :data="selectedMemberList" canDel emptyDescription="暂无已选择人员,<br> 请从左侧添加人员" viewList @delId="delMember"></SelectUserListV2>
|
||||
<SelectUserListV2 :data="selectedMemberList" canDel emptyDescription="暂无已选择人员,<br> 请从左侧添加人员" viewList @delId="delMember" :disabledSelectList="disabledSelectList"></SelectUserListV2>
|
||||
</div>
|
||||
</div>
|
||||
</ModalPanel>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { watch, ref } from 'vue';
|
||||
import { watch, ref, defineExpose } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { ModalPanel } from '/@/components/ModalPanel/index';
|
||||
@ -104,6 +107,10 @@
|
||||
readonly: Boolean,
|
||||
disabled: Boolean,
|
||||
size: String,
|
||||
justDialog: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@ -129,6 +136,26 @@
|
||||
isOnlyCurrentDepartment: { // 仅当前部门,无下级部门
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
submitClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabledShowBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wordWrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
lastSelectedDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
confirmLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
let timeoutId = null;
|
||||
@ -136,6 +163,7 @@
|
||||
const searchPlaceholder = '请输入姓名或工号搜索';
|
||||
const defaultDepts = ref('')
|
||||
const departAllSelected = ref(false)
|
||||
const disabledSelectList = ref([])
|
||||
if(props.onlyUserCompany) {
|
||||
const userStore = useUserStore();
|
||||
const userInfo = userStore.getUserInfo;
|
||||
@ -180,6 +208,9 @@
|
||||
watch(
|
||||
props,
|
||||
async () => {
|
||||
if(props.lastSelectedDisabled) {
|
||||
disabledSelectList.value = props.value.split(',')
|
||||
}
|
||||
if (props.value && !valChanged.value && props.sepTextField) {
|
||||
const idArr = props.value.split(',');
|
||||
let valStr = props.row[camelCaseString(props.sepTextField)];
|
||||
@ -196,7 +227,7 @@
|
||||
resetMemberList = cloneDeep(initValue);
|
||||
return;
|
||||
}
|
||||
if (props.value && !resetMemberList.length) {
|
||||
if (props.value && !Array.isArray(props.value) && !resetMemberList.length) {
|
||||
const list = await getUserMulti(props.value);
|
||||
selectedMemberList.value = list;
|
||||
resetMemberList = cloneDeep(list);
|
||||
@ -282,11 +313,17 @@
|
||||
async function getUserList(params) {
|
||||
return await getUserPageListNew(params);
|
||||
}
|
||||
function isDisabledItem(item) {
|
||||
return disabledSelectList.value.includes(item.id)
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
departAllSelected.value = !departAllSelected.value
|
||||
if(departAllSelected.value) {
|
||||
searchDepartMemberList.value.forEach(item => {
|
||||
if(isDisabledItem(item)) {
|
||||
return
|
||||
}
|
||||
if(!item.selected) {
|
||||
selectedMemberList.value.push(item)
|
||||
}
|
||||
@ -294,6 +331,9 @@
|
||||
})
|
||||
} else {
|
||||
searchDepartMemberList.value.forEach(item => {
|
||||
if(isDisabledItem(item)) {
|
||||
return
|
||||
}
|
||||
if(item.selected) {
|
||||
selectedMemberList.value = selectedMemberList.value.filter(m => m.id !== item.id)
|
||||
}
|
||||
@ -380,7 +420,9 @@
|
||||
emits('selectedId', ids);
|
||||
emits('change', ids, selectedMemberList.value);
|
||||
valChanged.value = false;
|
||||
close();
|
||||
if(props.submitClose) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
@ -390,6 +432,10 @@
|
||||
searchDepartMemberList.value = [];
|
||||
searchAllMemberList.value = [];
|
||||
}
|
||||
defineExpose({
|
||||
show,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
.select-user-model {
|
||||
@ -407,6 +453,11 @@
|
||||
justify-content: space-between;
|
||||
color: rgba(144, 147, 153, 0.7);
|
||||
}
|
||||
.field-readonly {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis
|
||||
}
|
||||
|
||||
.select-user {
|
||||
display: flex;
|
||||
|
||||
@ -52,22 +52,33 @@
|
||||
|
||||
<template v-else-if="column.key !== 'index'">
|
||||
<component
|
||||
v-show="column.width!==0"
|
||||
:is="componentMap.get(column.componentType)"
|
||||
:key="column.dataIndex + record['_key_']"
|
||||
v-model:value="record[column.dataIndex]"
|
||||
:title="readonlySupport(column.componentType) ? record[column.dataIndex] : ''"
|
||||
:bordered="showComponentBorder"
|
||||
:index="index"
|
||||
:mainKey="mainKey"
|
||||
:row="record"
|
||||
v-bind="getComponentsProps(column.componentProps, column.dataIndex, record, index)"
|
||||
v-bind="{...getComponentsProps(column.componentProps, column.dataIndex, record, index), placeholder: getComponentsProps(column.componentProps, column.dataIndex, record, index).disabled ? '' : getComponentsProps(column.componentProps, column.dataIndex, record, index).placeholder}"
|
||||
@blur="onFieldBlur(column, record, index)"
|
||||
@change="onFieldChange(column, record, index)"
|
||||
/>
|
||||
</template>
|
||||
</FormItem>
|
||||
</template>
|
||||
<template v-if="column.key === 'action' && !disabled">
|
||||
<MinusCircleOutlined style="padding-bottom: 20px" @click="remove(record)" />
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="column.actions?.length > 0">
|
||||
<div class="tag-wrapper">
|
||||
<template v-for="action in column.actions">
|
||||
<a-tag class="custom-tag" v-if="showButton(action.show,{record,records:data,formModel,disabled})" @click="action.type === 'delete' ? remove(record) : action.click({record,records:data,formModel,disabled})" :color="action?.color ? action.color : 'blue'">{{action.label}}</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="!disabled">
|
||||
<MinusCircleOutlined style="padding-bottom: 20px" @click="remove(record)" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@ -92,6 +103,7 @@
|
||||
:isSubFormUse="true"
|
||||
:params="{ itemId }"
|
||||
popupType="preload"
|
||||
:backPagination="backPagination"
|
||||
@submit="renderSubFormList"
|
||||
/>
|
||||
</FormItemRest>
|
||||
@ -182,6 +194,8 @@
|
||||
},
|
||||
// 是否开启分页
|
||||
showPagination: Boolean,
|
||||
//后端分页
|
||||
backPagination: Boolean,
|
||||
/**
|
||||
* 选数据按钮名称
|
||||
*/
|
||||
@ -197,7 +211,8 @@
|
||||
//add after hooks
|
||||
addAfter: Function,
|
||||
//表头合并数据
|
||||
multipleHeads: { type: Array as PropType<MutipleHeadInfo[]> }
|
||||
multipleHeads: { type: Array as PropType<MutipleHeadInfo[]> },
|
||||
backPagination: { type: Boolean }
|
||||
});
|
||||
const data = ref<Recordable[]>([]);
|
||||
|
||||
@ -207,14 +222,18 @@
|
||||
const headColums = ref<MutipleHeadInfo[]>([]); // 多表头
|
||||
const originHeads = ref<MutipleHeadInfo[]>([]); // 多表头源数据
|
||||
const columns = ref<SubFormColumn[]>(props.columns);
|
||||
const allColumns = ref([])
|
||||
|
||||
// 注入表单数据
|
||||
const formModel = inject<any>('formModel', null);
|
||||
function readonlySupport(name) {
|
||||
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker)$/.test(name);
|
||||
}
|
||||
|
||||
const onFieldChange = (column, row, rowIndex) => {
|
||||
const evt = column?.componentProps?.events?.change;
|
||||
if (evt) {
|
||||
evt({ column, row, rowIndex, formModel });
|
||||
evt({ column, row, rowIndex, formModel, columns: headColums.length > 0 ? headColums : columns });
|
||||
}
|
||||
};
|
||||
|
||||
@ -235,7 +254,7 @@
|
||||
}
|
||||
|
||||
function addDataKey(rows) {
|
||||
rows.forEach((row) => {
|
||||
rows?.forEach((row) => {
|
||||
if (!row['_key_']) {
|
||||
row['_key_'] = buildUUID();
|
||||
}
|
||||
@ -255,6 +274,7 @@
|
||||
});
|
||||
}
|
||||
setColWidth(columns);
|
||||
allColumns.value = cloneDeep(columns.value)
|
||||
columns.value = filterColum(columns.value);
|
||||
nextTick(() => {
|
||||
//处理多表头
|
||||
@ -271,6 +291,7 @@
|
||||
watch(
|
||||
() => props.columns,
|
||||
(val) => {
|
||||
allColumns.value = cloneDeep(val)
|
||||
columns.value = filterColum(val);
|
||||
setColWidth(columns);
|
||||
}
|
||||
@ -382,11 +403,20 @@
|
||||
emit('update:value', unref(data));
|
||||
};
|
||||
|
||||
const showButton=(show,obj)=>{
|
||||
if(show===undefined||show===null||show===''||show===true||show==='true'){
|
||||
return true;
|
||||
}else if(isFunction(show)){
|
||||
return show(obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const renderSubFormList = async (list) => {
|
||||
list?.forEach((x) => {
|
||||
const dataObj = {};
|
||||
columns.value?.map((item) => {
|
||||
if (!item?.dataIndex) return;
|
||||
allColumns.value?.map((item) => {
|
||||
if (!item?.dataIndex || !item.componentProps.isShow) return;
|
||||
dataObj[item.dataIndex as string] = item.componentProps?.prestrainField ? x[item.componentProps.prestrainField] : null;
|
||||
});
|
||||
|
||||
@ -652,4 +682,14 @@
|
||||
.tbl-toolbar {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tag-wrapper{
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.custom-tag{
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -168,6 +168,10 @@
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 按钮组 [{name: '按钮', click: function(){}}]
|
||||
*/
|
||||
buttons: Array,
|
||||
// 是否开启分页
|
||||
showPagination: Boolean,
|
||||
/**
|
||||
@ -197,6 +201,7 @@
|
||||
const headColums = ref<MutipleHeadInfo[]>([]); // 多表头
|
||||
const originHeads = ref<MutipleHeadInfo[]>([]); // 多表头源数据
|
||||
const columns = ref<SubFormColumn[]>(props.columns);
|
||||
const allColumns = ref([])
|
||||
|
||||
// 注入表单数据
|
||||
const formModel = inject<any>('formModel', null);
|
||||
@ -224,6 +229,9 @@
|
||||
}
|
||||
|
||||
function addDataKey(rows) {
|
||||
if (!rows) {
|
||||
return [];
|
||||
}
|
||||
rows.forEach((row) => {
|
||||
if (!row['_key_']) {
|
||||
row['_key_'] = Math.random();
|
||||
@ -244,6 +252,7 @@
|
||||
});
|
||||
}
|
||||
setColWidth(columns);
|
||||
allColumns.value = cloneDeep(columns.value)
|
||||
columns.value = filterColum(columns.value);
|
||||
nextTick(() => {
|
||||
//处理多表头
|
||||
@ -260,6 +269,7 @@
|
||||
watch(
|
||||
() => props.columns,
|
||||
(val) => {
|
||||
allColumns.value = cloneDeep(val)
|
||||
columns.value = filterColum(val);
|
||||
setColWidth(columns);
|
||||
}
|
||||
@ -327,6 +337,29 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
const removeById = (id) => {
|
||||
data.value = deleteNodeById(data.value, id);
|
||||
};
|
||||
function deleteNodeById(tree, idToDelete) {
|
||||
function deepCloneAndFilter(nodes) {
|
||||
return nodes.map((node) => {
|
||||
if (node.id === idToDelete) {
|
||||
return null; // 当前节点要删除
|
||||
}
|
||||
|
||||
// 递归处理 children
|
||||
|
||||
const newChildren = node.children ? deepCloneAndFilter(node.children) : [];
|
||||
|
||||
return {
|
||||
...node,
|
||||
children: newChildren.length ? newChildren : undefined
|
||||
};
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
return deepCloneAndFilter(tree);
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
//给各个组件赋默认值
|
||||
@ -364,17 +397,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
const remove = (index) => {
|
||||
data.value.splice(index, 1);
|
||||
emit('change', unref(data));
|
||||
emit('update:value', unref(data));
|
||||
const remove = (record) => {
|
||||
let index;
|
||||
if (typeof record === 'number' || typeof record === 'string') {
|
||||
index = Number(record);
|
||||
} else {
|
||||
index = data.value.findIndex((r) => r._key_ === record._key_);
|
||||
}
|
||||
if (index !== -1 && index < data.value.length) {
|
||||
data.value.splice(index, 1);
|
||||
emit('change', unref(data));
|
||||
emit('update:value', unref(data));
|
||||
}
|
||||
};
|
||||
|
||||
const renderSubFormList = async (list) => {
|
||||
list?.forEach((x) => {
|
||||
const dataObj = {};
|
||||
columns.value?.map((item) => {
|
||||
if (!item?.dataIndex) return;
|
||||
allColumns.value?.map((item) => {
|
||||
if (!item?.dataIndex || !item.componentProps.isShow) return;
|
||||
dataObj[item.dataIndex as string] = item.componentProps?.prestrainField ? x[item.componentProps.prestrainField] : null;
|
||||
});
|
||||
|
||||
@ -562,7 +603,8 @@
|
||||
data,
|
||||
headColums,
|
||||
columns,
|
||||
renderSubFormList
|
||||
renderSubFormList,
|
||||
removeById
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
</a-upload>
|
||||
</div>
|
||||
<a-upload
|
||||
:file-list="fileList"
|
||||
:file-list="fileListWithHeader"
|
||||
:maxCount="maxNumber"
|
||||
:accept="accept"
|
||||
:name="name"
|
||||
@ -62,7 +62,7 @@
|
||||
@preview="handlePreview"
|
||||
@click="handleClick"
|
||||
v-else
|
||||
>
|
||||
>
|
||||
<plus-outlined v-if="listType == 'picture-card'" />
|
||||
<div :style="style" v-else>
|
||||
<a-button :loading="loading" :disabled="loading" v-if="!disabled">
|
||||
@ -75,15 +75,45 @@
|
||||
</div>
|
||||
|
||||
<template #itemRender="{ file, actions }">
|
||||
<a-space class="file-space">
|
||||
<template v-if="file.__header&&showDownloadIcon">
|
||||
<div class="file-list-header" style="display: flex; align-items: center; padding: 4px 0;">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isAllSelected"
|
||||
@change="toggleSelectAll"
|
||||
style="margin-right: 8px;"
|
||||
/>全选
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="!selectedIds.length"
|
||||
@click="handleBatchDownload"
|
||||
style="margin-left: 8px;"
|
||||
>批量打包下载</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-space class="file-space">
|
||||
<input
|
||||
v-if="showDownloadIcon"
|
||||
type="checkbox"
|
||||
:checked="selectedIds.includes(file.id)"
|
||||
@change="e => toggleSelectOne(file.id, e)"
|
||||
style="margin-right: 8px;"
|
||||
/>
|
||||
<PaperClipOutlined/>
|
||||
<span class="file-name-span" @click="actions.preview">{{ file.name }}</span>
|
||||
<a-tooltip v-if="showDownloadIcon" title="下载"><span @click="actions.download" class="file-outlined-span"><DownloadOutlined /></span></a-tooltip>
|
||||
<a-tooltip v-if="!disabled && showRemoveIcon" title="删除"><span @click="actions.remove" class="file-outlined-span"><DeleteOutlined /></span></a-tooltip>
|
||||
<a-tooltip v-if="'.doc,.docx,.xls,.xlsx,.pdf'.includes(file.fileType)" title="编辑文档">
|
||||
<span @click="editFile(file)" class="file-outlined-span"><EditOutlined /></span>
|
||||
<a-tooltip v-if="showDownloadIcon" title="下载">
|
||||
<span @click="actions.download" class="file-outlined-span"><DownloadOutlined /></span>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<a-tooltip v-if="!disabled && showRemoveIcon" title="删除">
|
||||
<span @click="actions.remove" class="file-outlined-span"><DeleteOutlined /></span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="'.doc,.docx,.xls,.xlsx,.pdf'.includes(file.fileType)" title="编辑文档">
|
||||
<span @click="editFile(file)" class="file-outlined-span"><EditOutlined /></span>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</a-upload>
|
||||
@ -110,11 +140,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { nextTick, ref, watch, computed } from 'vue';
|
||||
import { Upload } from 'ant-design-vue';
|
||||
import { UploadOutlined, PlusOutlined, DownloadOutlined, DeleteOutlined, EditOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import {deleteSingleFile, getAppToken, getFileList, getOnlineEditUrl} from '/@/api/system/file';
|
||||
import {deleteSingleFile, getAppToken, getFileList, getOnlineEditUrl, getZipFiles} from '/@/api/system/file';
|
||||
import { downloadByUrl } from '/@/utils/file/download';
|
||||
import { uploadMultiApi } from '/@/api/sys/upload';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
@ -122,8 +152,12 @@
|
||||
import { getAppEnvConfig } from '/@/utils/env';
|
||||
import WebOfficeSDK from "/@/assets/libs/open-jssdk-v0.1.3.es.js";
|
||||
import {getToken} from "/@/utils/auth";
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
const { VITE_GLOB_UPLOAD_ALERT_TIP } = getAppEnvConfig();
|
||||
|
||||
const { createSuccessModal, } = useMessage();
|
||||
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
maxNumber: Number,
|
||||
@ -346,6 +380,52 @@
|
||||
previewVisible.value = false;
|
||||
previewTitle.value = '';
|
||||
};
|
||||
|
||||
const selectedIds = ref<string[]>([]);
|
||||
const isAllSelected = computed(() => fileList.value.length > 0 && selectedIds.value.length === fileList.value.length);
|
||||
const fileListWithHeader = computed(() => {
|
||||
// 只在有文件时插入头部
|
||||
if (fileList.value.length) {
|
||||
return [{ __header: true, uid: '__header__' }, ...fileList.value];
|
||||
}
|
||||
return fileList.value;
|
||||
});
|
||||
function toggleSelectAll(e: Event) {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
selectedIds.value = checked ? fileList.value.map(f => f.id) : [];
|
||||
}
|
||||
function toggleSelectOne(id: string, e: Event) {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
if (checked) {
|
||||
selectedIds.value = [...selectedIds.value, id];
|
||||
} else {
|
||||
selectedIds.value = selectedIds.value.filter(item => item !== id);
|
||||
}
|
||||
}
|
||||
async function handleBatchDownload() {
|
||||
if (!selectedIds.value.length) return;
|
||||
// getZipFiles 返回下载url
|
||||
let formName = '';
|
||||
try {
|
||||
formName = route.query.formName as string || '';
|
||||
// 获取当前页面得form name
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
const res = await getZipFiles({ fileIds: selectedIds.value.join(',') , insertionFileName: formName});
|
||||
if (!res) {
|
||||
notification.error({
|
||||
message: 'Tip',
|
||||
description: '批量下载失败,请稍后重试!',
|
||||
});
|
||||
return;
|
||||
} else if (res.type === 'async') {
|
||||
createSuccessModal({ title: 'Tip', content: res.msg });
|
||||
return;
|
||||
} else if (res.type === 'synced') {
|
||||
downloadByUrl({ url: res.url, fileName: res.name || 'files.zip' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.list-upload {
|
||||
|
||||
@ -127,6 +127,7 @@ export type ComponentType =
|
||||
| 'DeptTree'
|
||||
| 'Dept'
|
||||
| 'User'
|
||||
| 'UserTree'
|
||||
| 'Info'
|
||||
| 'Area'
|
||||
| 'SubForm'
|
||||
@ -158,6 +159,7 @@ export type ComponentType =
|
||||
| 'ErpCheck'
|
||||
| 'FormView'
|
||||
| 'XjrIframe'
|
||||
| 'CustomComponent'
|
||||
| 'TableLayout';
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user