将开源版本的修改打补丁应用到商业版
This commit is contained in:
@@ -75,11 +75,13 @@ import JRangeNumber from './jeecg/components/JRangeNumber.vue';
|
||||
import LinkTableForQuery from '/@/components/jeecg/super/desform/LinkTableForQuery.vue';
|
||||
import CascaderPcaForQuery from '/@/components/jeecg/super/desform/CascaderPcaForQuery.vue';
|
||||
import UserSelect from './jeecg/components/userSelect/index.vue';
|
||||
import JRangeDate from './jeecg/components/JRangeDate.vue'
|
||||
import JRangeTime from './jeecg/components/JRangeTime.vue'
|
||||
import JInputSelect from './jeecg/components/JInputSelect.vue'
|
||||
import JRangeDate from './jeecg/components/JRangeDate.vue';
|
||||
import JRangeTime from './jeecg/components/JRangeTime.vue';
|
||||
import JInputSelect from './jeecg/components/JInputSelect.vue';
|
||||
import RoleSelectInput from './jeecg/components/roleSelect/RoleSelectInput.vue';
|
||||
import {DatePickerInFilter, CascaderPcaInFilter} from "@/components/InFilter";
|
||||
import SzSelectUser from '@/components/semri/userComponent/SzUserSelect.vue';
|
||||
import SzSelectUserByDept from '@/components/semri/userComponent/SzSelectUserByDept.vue';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
@@ -178,8 +180,8 @@ componentMap.set('RangeDate', JRangeDate);
|
||||
componentMap.set('RangeTime', JRangeTime);
|
||||
componentMap.set('RoleSelect', RoleSelectInput);
|
||||
componentMap.set('JInputSelect', JInputSelect);
|
||||
|
||||
|
||||
componentMap.set('SzSelectUser', SzSelectUser);
|
||||
componentMap.set('SzSelectUserByDept',SzSelectUserByDept)
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
},
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
//排除用户id的集合
|
||||
excludeUserIdList:{
|
||||
excludeUserIdList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
@@ -110,15 +110,19 @@
|
||||
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
const excludeUserIdList = ref<any>([]);
|
||||
|
||||
|
||||
/**
|
||||
* 需要监听一下excludeUserIdList,否则modal获取不到
|
||||
*/
|
||||
watch(()=>props.excludeUserIdList,(data)=>{
|
||||
excludeUserIdList.value = data;
|
||||
},{ immediate: true })
|
||||
*/
|
||||
watch(
|
||||
() => props.excludeUserIdList,
|
||||
(data) => {
|
||||
excludeUserIdList.value = data;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
|
||||
|
||||
/**
|
||||
* 打卡弹出框
|
||||
*/
|
||||
@@ -169,7 +173,7 @@
|
||||
send(tempSave);
|
||||
};
|
||||
const send = (values) => {
|
||||
let result = typeof props.value == "string" ? values.join(',') : values;
|
||||
let result = typeof props.value == 'string' ? values.join(',') : values;
|
||||
emit('update:value', result);
|
||||
emit('change', result);
|
||||
};
|
||||
|
||||
@@ -163,6 +163,11 @@
|
||||
field: 'realname',
|
||||
component: 'JInput',
|
||||
},
|
||||
{
|
||||
label: '密级',
|
||||
field: 'userSecurityLevel',
|
||||
component: 'JInput',
|
||||
},
|
||||
],
|
||||
};
|
||||
//定义表格列
|
||||
|
||||
@@ -158,5 +158,7 @@ export type ComponentType =
|
||||
| 'RangeTime'
|
||||
| 'JRangeNumber'
|
||||
| 'JLinkTableCard'
|
||||
| 'JInputSelect';
|
||||
| 'JInputSelect'
|
||||
| 'SzSelectUser'
|
||||
| 'SzSelectUserByDept';
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div class="JSelectDept">
|
||||
<JSelectBiz @change="handleSelectChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" />
|
||||
|
||||
<a-form-item v-show="false">
|
||||
<SzDeptSelectModal
|
||||
@register="regModal"
|
||||
@getSelectResult="setValue"
|
||||
v-bind="getBindValue"
|
||||
:rowKey="rowKey"
|
||||
:labelKey="labelKey"
|
||||
@close="handleClose"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, watchEffect, watch, provide, unref, toRaw } from 'vue';
|
||||
import SzDeptSelectModal from './modal/SzDeptSelectModal.vue';
|
||||
import JSelectBiz from '@/components/Form/src/jeecg/components/base/JSelectBiz.vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SzSelectDept',
|
||||
components: {
|
||||
SzDeptSelectModal,
|
||||
JSelectBiz,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
// 外部 v-model 绑定的值(ID字符串或数组)
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
// 显示文本的字段名
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'departName',
|
||||
},
|
||||
// 存储值的字段名
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
// 是否允许多选
|
||||
multiple: propTypes.bool.def(true),
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value', 'select'],
|
||||
setup(props, { emit }) {
|
||||
const [regModal, { openModal }] = useModal();
|
||||
const attrs = useAttrs();
|
||||
|
||||
const selectOptions = ref<SelectValue>([]); // 存储 [{id, departName}]
|
||||
const selectValues = reactive<Recordable>({ value: [] }); // 存储 [id]
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
let tempSave: any = [];
|
||||
|
||||
// 依赖注入给 JSelectBiz 组件使用,用于显示标签和加载状态
|
||||
provide('selectOptions', selectOptions);
|
||||
provide('selectValues', selectValues);
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
/**
|
||||
* 监听外部 value 变化进行初始化
|
||||
*/
|
||||
watchEffect(() => {
|
||||
tempSave = [];
|
||||
if (props.value) {
|
||||
initValue();
|
||||
} else {
|
||||
selectValues.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 初始化值:将字符串转为数组
|
||||
*/
|
||||
function initValue() {
|
||||
let val = props.value ? props.value : [];
|
||||
if (typeof val === 'string' && val !== 'null' && val !== 'undefined') {
|
||||
selectValues.value = val.split(',');
|
||||
tempSave = val.split(',');
|
||||
} else {
|
||||
selectValues.value = Array.isArray(val) ? val : [];
|
||||
tempSave = cloneDeep(selectValues.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【关键】接收弹窗传回的精简数据
|
||||
* @param options 格式:[{id: 'xxx', departName: 'xxx'}]
|
||||
* @param values 格式:['xxx']
|
||||
*/
|
||||
function setValue(options, values) {
|
||||
// 将后端字段 [id, departName] 转换为 a-select 认识的 [value, label]
|
||||
selectOptions.value = options.map((item) => ({
|
||||
label: item.departName, // 这一步让框里显示出名字
|
||||
value: item.id, // 这一步让值对应上
|
||||
}));
|
||||
|
||||
selectValues.value = values;
|
||||
send(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据给父组件
|
||||
*/
|
||||
const send = (values) => {
|
||||
let result = typeof props.value === 'string' ? values.join(',') : values;
|
||||
emit('update:value', result);
|
||||
emit('change', result);
|
||||
};
|
||||
|
||||
function handleOpen() {
|
||||
openModal(true, { isUpdate: false });
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
if (tempSave.length) {
|
||||
selectValues.value = cloneDeep(tempSave);
|
||||
} else {
|
||||
send(tempSave);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectChange(values) {
|
||||
tempSave = cloneDeep(values);
|
||||
send(tempSave);
|
||||
}
|
||||
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
|
||||
return {
|
||||
attrs,
|
||||
selectOptions,
|
||||
selectValues,
|
||||
loadingEcho,
|
||||
getBindValue,
|
||||
regModal,
|
||||
setValue,
|
||||
handleOpen,
|
||||
handleClose,
|
||||
handleSelectChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.JSelectDept {
|
||||
> .ant-form-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
// 1. 修改引入路径,Jeecg 使用的是 defHttp
|
||||
import { defHttp } from '@/utils/http/axios';
|
||||
|
||||
// 根据部门+角色+密级筛选用户
|
||||
export function queryDept(params) {
|
||||
return defHttp.get({
|
||||
// 2. 注意:Jeecg 默认配置了 baseUrl,通常不需要写 /jeecg-boot 前缀
|
||||
url: '/sys/sysDepart/querySecondLevelDepts',
|
||||
// 3. GET 请求的参数 key 是 params,而不是 data
|
||||
params,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="modalTitle"
|
||||
:width="showSelected ? '1200px' : '900px'"
|
||||
wrapClassName="j-user-select-modal"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:maxHeight="maxHeight"
|
||||
:centered="true"
|
||||
destroyOnClose
|
||||
@visible-change="visibleChange"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="showSelected ? 18 : 24">
|
||||
<BasicTable
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:scroll="tableScroll"
|
||||
v-bind="getBindValue"
|
||||
:useSearchForm="true"
|
||||
:formConfig="formConfig"
|
||||
:api="queryDept"
|
||||
:searchInfo="searchInfo"
|
||||
:rowSelection="rowSelection"
|
||||
:indexColumnProps="indexColumnProps"
|
||||
:afterFetch="afterFetch"
|
||||
:beforeFetch="beforeFetch"
|
||||
>
|
||||
<template #tableTitle></template>
|
||||
</BasicTable>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="showSelected ? 6 : 0" v-if="showSelected" style="padding-left: 10px">
|
||||
<BasicTable v-bind="selectedTable" :dataSource="selectRows">
|
||||
<template #action="{ record }">
|
||||
<a href="javascript:void(0)" @click="handleDeleteSelected(record)">
|
||||
<Icon icon="ant-design:delete-outlined" style="color: #ff4d4f"></Icon>
|
||||
</a>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, unref, ref, watch, h } from 'vue';
|
||||
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||
import { queryDept } from '../dept';
|
||||
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
|
||||
import { useSelectBiz } from '@/components/Form/src/jeecg/hooks/useSelectBiz';
|
||||
import { useAttrs } from '@/hooks/core/useAttrs';
|
||||
import { selectProps } from '@/components/Form/src/jeecg/props/props';
|
||||
import { Icon } from '@/components/Icon';
|
||||
import { JDictSelectTag } from "@/components/Form";
|
||||
import { render } from "@/utils/common/renderUtils";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SzDeptSelectModal',
|
||||
components: {
|
||||
BasicModal,
|
||||
Icon,
|
||||
BasicTable: createAsyncComponent(() => import('/src/components/Table/src/BasicTable.vue'), {
|
||||
loading: true,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
...selectProps,
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择部门',
|
||||
},
|
||||
},
|
||||
emits: ['register', 'getSelectResult', 'close'],
|
||||
setup(props, { emit }) {
|
||||
const tableScroll = ref<any>({ x: false });
|
||||
const tableRef = ref();
|
||||
const maxHeight = ref(600);
|
||||
|
||||
// 内部状态注册
|
||||
const [register, { closeModal }] = useModalInner(() => {
|
||||
if (window.innerWidth < 900) {
|
||||
tableScroll.value = { x: 900 };
|
||||
} else {
|
||||
tableScroll.value = { x: false };
|
||||
}
|
||||
// 延迟设置选中行,确保 table 渲染完成
|
||||
setTimeout(() => {
|
||||
if (tableRef.value) {
|
||||
tableRef.value.setSelectedRowKeys(selectValues['value'] || []);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
// 基础配置
|
||||
const config = {
|
||||
canResize: false,
|
||||
bordered: true,
|
||||
size: 'small',
|
||||
rowKey: 'id', // 必须确保后端返回 id 字段
|
||||
labelKey: 'departName',
|
||||
};
|
||||
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs), config);
|
||||
|
||||
// 使用 Jeecg 核心 Hook
|
||||
const [{ rowSelection, visibleChange, selectValues, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows, showSelected }] =
|
||||
useSelectBiz(queryDept, getBindValue, emit);
|
||||
|
||||
const searchInfo = ref(props.params);
|
||||
|
||||
// 监听外部 keys 变化同步给 table
|
||||
watch(rowSelection.selectedRowKeys, (newVal) => {
|
||||
if (tableRef.value) {
|
||||
tableRef.value.setSelectedRowKeys(newVal);
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索表单配置
|
||||
const formConfig = {
|
||||
// 在这里增加 labelWidth
|
||||
labelWidth: 65,
|
||||
baseColProps: { xs: 24, sm: 12, md: 8, lg: 8, xl: 8, xxl: 8 },
|
||||
schemas: [
|
||||
{
|
||||
label: '部门类别',
|
||||
field: 'departType',
|
||||
// 也可以在具体的 schema 里单独控制
|
||||
// labelWidth: 100,
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'depart_type',
|
||||
|
||||
placeholder: '请选择部门类别',
|
||||
|
||||
stringToNumber: true,
|
||||
|
||||
'onUpdate:value': (val) => {
|
||||
// 这里的 val 就是选中的值
|
||||
console.log('监听到值改变:', val);
|
||||
// 触发 Table 刷新
|
||||
tableRef.value?.reload();
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 主表列定义
|
||||
const columns = [
|
||||
{ title: '部门名称', dataIndex: 'departName', width: 180, align: 'left' },
|
||||
{ title: '机构编码', dataIndex: 'orgCode', width: 120 },
|
||||
{
|
||||
title: '机构类型',
|
||||
dataIndex: 'departType',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDict(text, 'depart_type');
|
||||
},
|
||||
},
|
||||
{ title: '排序', dataIndex: 'departOrder', width: 80 },
|
||||
];
|
||||
|
||||
// 右侧已选表配置
|
||||
const selectedTable = {
|
||||
pagination: false,
|
||||
showIndexColumn: false,
|
||||
scroll: { y: 390 },
|
||||
size: 'small',
|
||||
canResize: false,
|
||||
bordered: true,
|
||||
rowKey: 'id',
|
||||
columns: [
|
||||
{ title: '部门名称', dataIndex: 'departName', width: 100 },
|
||||
{ title: '操作', dataIndex: 'action', align: 'center', width: 40, slots: { customRender: 'action' } },
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* 【修改后的 handleOk】
|
||||
* 只提取 id 和 departName
|
||||
*/
|
||||
function handleOk() {
|
||||
// 直接从 tableRef 获取选中的原始行数据,避免 label 为 undefined 的问题
|
||||
const rows = tableRef.value.getSelectRows();
|
||||
|
||||
// 1. 提取精简的对象列表
|
||||
const options = rows.map((item) => ({
|
||||
id: item.id,
|
||||
departName: item.departName,
|
||||
}));
|
||||
|
||||
// 2. 提取 ID 列表
|
||||
const values = rows.map((item) => item.id);
|
||||
|
||||
console.log('确定选择内容:', options);
|
||||
|
||||
// 3. 向上抛出事件
|
||||
emit('getSelectResult', options, values);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function afterFetch(record) {
|
||||
return record;
|
||||
}
|
||||
|
||||
function beforeFetch(params) {
|
||||
return Object.assign({ column: 'createTime', order: 'desc' }, params);
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return {
|
||||
handleOk,
|
||||
searchInfo,
|
||||
register,
|
||||
indexColumnProps,
|
||||
visibleChange,
|
||||
getBindValue,
|
||||
queryDept, // 必须暴露给 template 中的 :api
|
||||
formConfig,
|
||||
columns,
|
||||
rowSelection,
|
||||
selectRows,
|
||||
showSelected,
|
||||
selectedTable,
|
||||
handleDeleteSelected,
|
||||
tableScroll,
|
||||
tableRef,
|
||||
afterFetch,
|
||||
handleCancel,
|
||||
maxHeight,
|
||||
beforeFetch,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!-- 文件上传弹窗组件 -->
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
title="选择文件密级并上传"
|
||||
:width="600"
|
||||
:confirm-loading="uploading"
|
||||
@ok="handleUpload"
|
||||
@cancel="handleCancel"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<a-form layout="vertical" style="padding: 0 20px">
|
||||
<a-form-item label="文件密级" required>
|
||||
<a-radio-group v-model:value="selectedSecretLevel">
|
||||
<a-radio
|
||||
v-for="option in secretLevelOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
<a-tag :color="getSecretLevelColor(option.value)">
|
||||
{{ option.label }}
|
||||
</a-tag>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<!-- <div class="secret-tip">-->
|
||||
<!-- <Icon icon="ant-design:info-circle-outlined" />-->
|
||||
<!-- 文件密级将决定谁能查看此文件,请谨慎选择-->
|
||||
<!-- </div>-->
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="选择文件" required>
|
||||
<a-upload
|
||||
:before-upload="beforeUpload"
|
||||
:file-list="tempFileList"
|
||||
@remove="handleRemoveFile"
|
||||
:multiple="multiple"
|
||||
:max-count="multiple ? undefined : 1"
|
||||
>
|
||||
<a-button>
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
{{ multiple ? '选择文件(可多选)' : '选择文件' }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-alert
|
||||
v-if="selectedFiles.length > 0"
|
||||
:message="`已选择 ${selectedFiles.length} 个文件`"
|
||||
type="info"
|
||||
show-icon
|
||||
>
|
||||
<template #description>
|
||||
<div class="selected-files-summary">
|
||||
<div v-for="(f, idx) in selectedFiles" :key="f.uid" class="summary-item">
|
||||
{{ idx + 1}}. {{ f.name }}({{ formatFileSize(f.size) }})
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { Icon } from '/src/components/Icon';
|
||||
import { useMessage } from '/src/hooks/web/useMessage';
|
||||
import { uploadMyFile } from '/src/api/common/api';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
secretLevelOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
businessId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'upload-success', 'upload-error']);
|
||||
|
||||
const selectedSecretLevel = ref<number>(1);
|
||||
const selectedFiles = ref<any[]>([]);
|
||||
const tempFileList = ref<any[]>([]);
|
||||
const uploading = ref<boolean>(false);
|
||||
|
||||
function getSecretLevelColor(level: number) {
|
||||
const colors = {
|
||||
1: 'success',
|
||||
2: 'processing',
|
||||
3: 'warning',
|
||||
4: 'error'
|
||||
};
|
||||
return colors[level] || 'default';
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function beforeUpload(file) {
|
||||
const isValidSize = file.size / 1024 / 1024 < 500;
|
||||
if (!isValidSize) {
|
||||
createMessage.error(`文件"${file.name}"大小超过500MB,已跳过`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!props.multiple) {
|
||||
selectedFiles.value = [file];
|
||||
tempFileList.value = [{
|
||||
uid: file.uid,
|
||||
name: file.name,
|
||||
status: 'done',
|
||||
size: file.size
|
||||
}];
|
||||
} else {
|
||||
selectedFiles.value = [...selectedFiles.value, file];
|
||||
tempFileList.value = [...tempFileList.value, {
|
||||
uid: file.uid,
|
||||
name: file.name,
|
||||
status: 'done',
|
||||
size: file.size
|
||||
}];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleRemoveFile(file) {
|
||||
selectedFiles.value = selectedFiles.value.filter(f => f.uid !== file.uid);
|
||||
tempFileList.value = tempFileList.value.filter(f => f.uid !== file.uid);
|
||||
}
|
||||
|
||||
async function handleUpload() {
|
||||
if (selectedFiles.value.length === 0) {
|
||||
createMessage.warning('请先选择文件');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedSecretLevel.value) {
|
||||
createMessage.warning('请选择文件密级');
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
|
||||
try {
|
||||
const uploadPromises = selectedFiles.value.map(file => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('secretLevel', selectedSecretLevel.value.toString());
|
||||
if (props.businessId) {
|
||||
formData.append('business_id', props.businessId);
|
||||
}
|
||||
return uploadMyFile('/sys/file/upload', formData).then(res => ({
|
||||
rawFile: file,
|
||||
response: res
|
||||
}));
|
||||
});
|
||||
|
||||
const results = await Promise.allSettled(uploadPromises);
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const successResults: any[] = [];
|
||||
|
||||
results.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const result = r.value?.response?.data;
|
||||
if (result && result.success) {
|
||||
successCount++;
|
||||
successResults.push({
|
||||
fileId: result.result.fileId,
|
||||
fileName: r.value.rawFile.name,
|
||||
filePath: result.result.savePath,
|
||||
fileSize: r.value.rawFile.size,
|
||||
secretLevel: selectedSecretLevel.value,
|
||||
fileUploadType: result.result.fileUploadType,
|
||||
bound: !!props.businessId
|
||||
});
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (successCount > 0) {
|
||||
createMessage.success(`成功上传 ${successCount} 个文件${failCount > 0 ? `,${failCount} 个失败` : ''}`);
|
||||
emit('upload-success', { success: true, results: successResults });
|
||||
handleCancel();
|
||||
} else {
|
||||
createMessage.error('所有文件上传失败');
|
||||
emit('upload-error', new Error('所有文件上传失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
createMessage.error('文件上传失败');
|
||||
emit('upload-error', error);
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('update:visible', false);
|
||||
setTimeout(() => {
|
||||
selectedSecretLevel.value = 1;
|
||||
selectedFiles.value = [];
|
||||
tempFileList.value = [];
|
||||
}, 300);
|
||||
}
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal && props.secretLevelOptions.length > 0) {
|
||||
selectedSecretLevel.value = props.secretLevelOptions[0].value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.secret-tip {
|
||||
margin-top: 8px;
|
||||
color: #faad14;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.selected-files-summary {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
|
||||
.summary-item {
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
color: #595959;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,467 @@
|
||||
<!-- 文件上传下载删除组件 -->
|
||||
<template>
|
||||
<div class="s-upload-file-container">
|
||||
<!-- 上传按钮区域 -->
|
||||
<div class="upload-btn-wrapper" v-if="!disabled">
|
||||
<a-button type="primary" @click="handleOpenUploadModal">
|
||||
<Icon icon="ant-design:upload-outlined" />
|
||||
选择文件上传
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表区域 -->
|
||||
<div class="file-list-wrapper">
|
||||
<div v-if="displayFileList.length > 0" class="file-list-header">
|
||||
<span class="file-count">共 {{ displayFileList.length }} 个文件</span>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
:loading="downloadingAll"
|
||||
@click="handleDownloadAll"
|
||||
>
|
||||
<Icon icon="ant-design:download-outlined" />
|
||||
下载全部
|
||||
</a-button>
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!loading && displayFileList.length === 0" description="暂无文件" />
|
||||
<div v-else class="file-list">
|
||||
<div
|
||||
v-for="file in displayFileList"
|
||||
:key="file.fileId || file.id"
|
||||
class="file-item"
|
||||
>
|
||||
<!-- 文件信息 -->
|
||||
<div class="file-info">
|
||||
<Icon icon="ant-design:file-outlined" class="file-icon" />
|
||||
<span class="file-name">{{ file.fileName }}</span>
|
||||
<a-tag :color="getSecretLevelColor(file.secretLevel)" class="secret-tag">
|
||||
{{ getSecretLevelLabel(file.secretLevel) }}
|
||||
</a-tag>
|
||||
<a-tag v-if="file.pending" color="orange" class="secret-tag">
|
||||
待保存
|
||||
</a-tag>
|
||||
<span v-if="file.fileSize" class="file-size">{{ formatFileSize(file.fileSize) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="file-actions">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleDownload(file)"
|
||||
>
|
||||
<Icon icon="ant-design:download-outlined" />
|
||||
下载
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="!disabled"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(file)"
|
||||
>
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
删除
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- 上传弹窗 -->
|
||||
<SFileUploadModal
|
||||
v-model:visible="uploadModalVisible"
|
||||
:secret-level-options="secretLevelOptions"
|
||||
:business-id="bussinessId"
|
||||
:multiple="multiple"
|
||||
@upload-success="handleUploadSuccess"
|
||||
@upload-error="handleUploadError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { Icon } from '/src/components/Icon';
|
||||
import { defHttp } from '/src/utils/http/axios';
|
||||
import { useMessage } from '/src/hooks/web/useMessage';
|
||||
import SFileUploadModal from './SFileUploadModal.vue';
|
||||
|
||||
const { createMessage, createConfirm } = useMessage();
|
||||
|
||||
const props = defineProps({
|
||||
bussinessId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
bussinessSecretLevel: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
validator: (value: number) => [1, 2, 3, 4].includes(value)
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['upload-success', 'delete-success', 'upload-error']);
|
||||
|
||||
const serverFileList = ref<any[]>([]);
|
||||
const pendingFileList = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const uploadModalVisible = ref<boolean>(false);
|
||||
const binding = ref<boolean>(false);
|
||||
const downloadingAll = ref<boolean>(false);
|
||||
|
||||
const displayFileList = computed(() => {
|
||||
return [
|
||||
...serverFileList.value.map(f => ({ ...f, pending: false })),
|
||||
...pendingFileList.value.map(f => ({ ...f, pending: true }))
|
||||
];
|
||||
});
|
||||
|
||||
const SECRET_LEVEL_CONFIG = {
|
||||
1: { label: '非密', color: 'success' },
|
||||
2: { label: '内部', color: 'processing' },
|
||||
3: { label: '秘密', color: 'warning' },
|
||||
4: { label: '机密', color: 'error' }
|
||||
};
|
||||
|
||||
function getSecretLevelInfo(level: number) {
|
||||
return SECRET_LEVEL_CONFIG[level] || SECRET_LEVEL_CONFIG[1];
|
||||
}
|
||||
|
||||
function getSecretLevelLabel(level: number) {
|
||||
return getSecretLevelInfo(level).label;
|
||||
}
|
||||
|
||||
function getSecretLevelColor(level: number) {
|
||||
return getSecretLevelInfo(level).color;
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (!bytes || bytes === 0) return '';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
const secretLevelOptions = computed(() => {
|
||||
const level = props.bussinessSecretLevel;
|
||||
const options = [];
|
||||
options.push({ label: '非密', value: 1 });
|
||||
if (level >= 2) options.push({ label: '内部', value: 2 });
|
||||
if (level >= 3) options.push({ label: '秘密', value: 3 });
|
||||
if (level >= 4) options.push({ label: '机密', value: 4 });
|
||||
return options;
|
||||
});
|
||||
|
||||
function handleOpenUploadModal() {
|
||||
uploadModalVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleUploadSuccess(result: any) {
|
||||
if (result.results && Array.isArray(result.results)) {
|
||||
result.results.forEach((fileInfo: any) => {
|
||||
if (!fileInfo.bound) {
|
||||
pendingFileList.value.push({
|
||||
fileId: fileInfo.fileId,
|
||||
fileName: fileInfo.fileName,
|
||||
filePath: fileInfo.filePath,
|
||||
fileSize: fileInfo.fileSize,
|
||||
secretLevel: fileInfo.secretLevel,
|
||||
fileUploadType: fileInfo.fileUploadType
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (props.bussinessId) {
|
||||
await loadFileList();
|
||||
}
|
||||
emit('upload-success', result);
|
||||
}
|
||||
|
||||
function handleUploadError(error: any) {
|
||||
emit('upload-error', error);
|
||||
}
|
||||
|
||||
async function handleDownload(file: any) {
|
||||
try {
|
||||
const fileUrl = file.filePath;
|
||||
const fileName = file.fileName || fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
|
||||
const domainUrl = import.meta.env.VITE_GLOB_DOMAIN_URL;
|
||||
const data = await defHttp.get(
|
||||
{ url: `${domainUrl}/sys/file/download`, params: { fileUrl }, responseType: 'blob' },
|
||||
{ isTransformResponse: false }
|
||||
);
|
||||
if (!data || data.size === 0) {
|
||||
createMessage.warning('文件下载失败');
|
||||
return;
|
||||
}
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(new Blob([data]), fileName);
|
||||
} else {
|
||||
const blobUrl = window.URL.createObjectURL(new Blob([data]));
|
||||
const link = document.createElement('a');
|
||||
link.style.display = 'none';
|
||||
link.href = blobUrl;
|
||||
link.setAttribute('download', fileName);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
createMessage.error('文件下载失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadAll() {
|
||||
const files = displayFileList.value;
|
||||
if (files.length === 0) return;
|
||||
|
||||
downloadingAll.value = true;
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
await handleDownload(file);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
console.error(`下载文件"${file.fileName}"失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadingAll.value = false;
|
||||
|
||||
if (successCount > 0) {
|
||||
createMessage.success(`成功下载 ${successCount} 个文件${failCount > 0 ? `,${failCount} 个失败` : ''}`);
|
||||
} else {
|
||||
createMessage.error('所有文件下载失败');
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(file: any) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: `确定要删除文件"${file.fileName}"吗?`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await defHttp.get({
|
||||
url: '/sys/file/delete',
|
||||
params: { id: file.fileId || file.id }
|
||||
});
|
||||
createMessage.success('文件删除成功');
|
||||
if (file.pending) {
|
||||
pendingFileList.value = pendingFileList.value.filter(f => f.fileId !== file.fileId);
|
||||
} else {
|
||||
serverFileList.value = serverFileList.value.filter(f => (f.id || f.fileId) !== (file.id || file.fileId));
|
||||
}
|
||||
emit('delete-success', file.fileId || file.id);
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
createMessage.error('文件删除失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFileList() {
|
||||
if (!props.bussinessId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const result = await defHttp.get({
|
||||
url: '/sys/file/getFileInfoByBussinessId',
|
||||
params: { bussinessId: props.bussinessId }
|
||||
});
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
serverFileList.value = result;
|
||||
} else {
|
||||
serverFileList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文件列表失败:', error);
|
||||
serverFileList.value = [];
|
||||
createMessage.error('加载文件列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
//这个是在表单id还没有生成之前上传了附件,附件表没有存储bussinessId。当业务表保存后有id了,再把bussinessId更新到这些附件上去
|
||||
async function bindBussinessId(newBussinessId: string) {
|
||||
if (!newBussinessId) {
|
||||
createMessage.warning('bussinessId 不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pendingFileList.value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fileIds = pendingFileList.value.map(f => f.fileId).join(',');
|
||||
|
||||
try {
|
||||
binding.value = true;
|
||||
await defHttp.get({
|
||||
url: '/sys/file/updateFilesInfo',
|
||||
params: {
|
||||
bussinessId: newBussinessId,
|
||||
fileIds: fileIds
|
||||
}
|
||||
});
|
||||
createMessage.success(`成功关联 ${pendingFileList.value.length} 个文件`);
|
||||
pendingFileList.value = [];
|
||||
await loadFileList();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('关联文件失败:', error);
|
||||
createMessage.error('关联文件失败,请重试');
|
||||
return false;
|
||||
} finally {
|
||||
binding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getPendingFileIds(): string[] {
|
||||
return pendingFileList.value.map(f => f.fileId);
|
||||
}
|
||||
|
||||
function hasPendingFiles(): boolean {
|
||||
return pendingFileList.value.length > 0;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
bindBussinessId,
|
||||
getPendingFileIds,
|
||||
hasPendingFiles,
|
||||
loadFileList
|
||||
});
|
||||
|
||||
watch(() => props.bussinessId, (newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
loadFileList();
|
||||
} else {
|
||||
serverFileList.value = [];
|
||||
}
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.s-upload-file-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
.upload-btn-wrapper {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.file-list-wrapper {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
|
||||
.file-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.file-count {
|
||||
font-size: 13px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
.file-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.file-icon {
|
||||
font-size: 16px;
|
||||
color: #1890ff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.secret-tag {
|
||||
margin-left: 4px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
# SUploadFile 文件上传组件使用指南
|
||||
|
||||
## 组件介绍
|
||||
|
||||
`SUploadFile` 是一个支持密级管理的通用文件上传下载删除组件,适用于 JeecgBoot 项目。
|
||||
|
||||
## 组件位置
|
||||
|
||||
```
|
||||
jeecgboot-vue3/src/components/semri/fileComponent/SUploadFile.vue
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 文件加密上传(支持密级选择)
|
||||
- ✅ 文件解密下载
|
||||
- ✅ 文件删除
|
||||
- ✅ 密级权限控制(文件密级 <= 业务密级)
|
||||
- ✅ 支持禁用状态(禁用后不能上传和删除)
|
||||
- ✅ 文件列表展示(带密级标签)
|
||||
- ✅ 支持业务关联(通过 businessId)
|
||||
|
||||
## Props 参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 必填 | 说明 |
|
||||
|------|------|--------|------|------|
|
||||
| bussinessId | String | '' | 否 | 业务ID,用于关联和查询文件 |
|
||||
| bussinessSecretLevel | Number | 3 | 否 | 业务密级(1-非密,2-内部,3-秘密,4-机密) |
|
||||
| disabled | Boolean | false | 否 | 是否禁用(禁用后不能上传和删除,但可以下载) |
|
||||
|
||||
## Events 事件
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| upload-success | result | 上传成功时触发,返回上传结果 |
|
||||
| delete-success | fileId | 删除成功时触发,返回文件ID |
|
||||
| upload-error | error | 上传失败时触发,返回错误信息 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="项目文件">
|
||||
<SUploadFile
|
||||
v-model:bussinessId="formData.id"
|
||||
:bussiness-secret-level="formData.secretLevel"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import SUploadFile from '/@/semri/fileComponent/SUploadFile.vue';
|
||||
|
||||
const formData = ref({
|
||||
id: '',
|
||||
secretLevel: 3 // 秘密
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### 带事件监听的用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="项目文件">
|
||||
<SUploadFile
|
||||
v-model:bussinessId="formData.id"
|
||||
:bussiness-secret-level="formData.secretLevel"
|
||||
:disabled="formData.readOnly"
|
||||
@upload-success="handleUploadSuccess"
|
||||
@delete-success="handleDeleteSuccess"
|
||||
@upload-error="handleUploadError"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { createMessage } from '/@/hooks/web/useMessage';
|
||||
import SUploadFile from '/@/semri/fileComponent/SUploadFile.vue';
|
||||
|
||||
const formData = ref({
|
||||
id: '123456',
|
||||
secretLevel: 3, // 秘密
|
||||
readOnly: false
|
||||
});
|
||||
|
||||
// 上传成功处理
|
||||
function handleUploadSuccess(result) {
|
||||
console.log('上传成功:', result);
|
||||
createMessage.success('文件上传完成');
|
||||
}
|
||||
|
||||
// 删除成功处理
|
||||
function handleDeleteSuccess(fileId) {
|
||||
console.log('删除成功,文件ID:', fileId);
|
||||
createMessage.success('文件删除完成');
|
||||
}
|
||||
|
||||
// 上传失败处理
|
||||
function handleUploadError(error) {
|
||||
console.error('上传失败:', error);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 不同密级的使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 业务密级为非密,只能上传非密文件 -->
|
||||
<SUploadFile
|
||||
bussiness-id="001"
|
||||
:bussiness-secret-level="1"
|
||||
/>
|
||||
|
||||
<!-- 业务密级为内部,可以上传非密、内部文件 -->
|
||||
<SUploadFile
|
||||
bussiness-id="002"
|
||||
:bussiness-secret-level="2"
|
||||
/>
|
||||
|
||||
<!-- 业务密级为秘密,可以上传非密、内部、秘密文件 -->
|
||||
<SUploadFile
|
||||
bussiness-id="003"
|
||||
:bussiness-secret-level="3"
|
||||
/>
|
||||
|
||||
<!-- 业务密级为机密,可以上传所有密级的文件 -->
|
||||
<SUploadFile
|
||||
bussiness-id="004"
|
||||
:bussiness-secret-level="4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SUploadFile from '/@/semri/fileComponent/SUploadFile.vue';
|
||||
</script>
|
||||
```
|
||||
|
||||
### 禁用状态(只读)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="项目文件">
|
||||
<SUploadFile
|
||||
bussiness-id="123456"
|
||||
:bussiness-secret-level="3"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SUploadFile from '/@/semri/fileComponent/SUploadFile.vue';
|
||||
</script>
|
||||
```
|
||||
|
||||
## 密级规则
|
||||
|
||||
### 文件密级选项规则
|
||||
|
||||
组件会根据业务密级自动生成可选的文件密级:
|
||||
|
||||
| 业务密级 | 可选文件密级 |
|
||||
|---------|------------|
|
||||
| 1-非密 | 1-非密 |
|
||||
| 2-内部 | 1-非密,2-内部 |
|
||||
| 3-秘密 | 1-非密,2-内部,3-秘密 |
|
||||
| 4-机密 | 1-非密,2-内部,3-秘密,4-机密 |
|
||||
|
||||
### 密级显示颜色
|
||||
|
||||
- **非密**:绿色 (success)
|
||||
- **内部**:蓝色 (processing)
|
||||
- **秘密**:橙色 (warning)
|
||||
- **机密**:红色 (error)
|
||||
|
||||
## API 接口
|
||||
|
||||
组件内部调用的后端接口:
|
||||
|
||||
- **上传文件**: `POST /sys/file/upload`
|
||||
- 参数:file, secretLevel, business_id
|
||||
- **获取文件列表**: `GET /sys/file/getFileInfoByBussinessId`
|
||||
- 参数:bussinessId
|
||||
- **下载文件**: `GET /sys/file/download`
|
||||
- 参数:fileUrl
|
||||
- **删除文件**: `GET /sys/file/delete`
|
||||
- 参数:id
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **文件大小限制**:单个文件不超过 100MB
|
||||
2. **密级校验**:文件密级必须 <= 业务密级
|
||||
3. **禁用状态**:禁用时不显示上传和删除按钮,但可以下载文件
|
||||
4. **业务ID**:如果提供了业务ID,组件会自动加载该业务关联的文件列表
|
||||
5. **Token 处理**:组件自动处理 token,无需手动添加
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么上传按钮不显示?
|
||||
A: 检查是否设置了 `disabled="true"`,禁用状态下不会显示上传按钮。
|
||||
|
||||
### Q: 为什么只能选择某些密级?
|
||||
A: 文件密级受业务密级限制,只能选择小于等于业务密级的文件密级。
|
||||
|
||||
### Q: 如何获取上传后文件的ID?
|
||||
A: 监听 `upload-success` 事件,返回的结果中包含 `fileId`。
|
||||
|
||||
### Q: 删除文件时如何自定义确认文案?
|
||||
A: 当前版本删除确认文案是固定的,如需自定义请修改组件源码。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2026-03-27)
|
||||
- 初始版本发布
|
||||
- 支持文件上传、下载、删除功能
|
||||
- 支持密级管理
|
||||
- 支持禁用状态
|
||||
@@ -0,0 +1,171 @@
|
||||
<!--用户选择组件-->
|
||||
<template>
|
||||
<div>
|
||||
<JSelectBiz @change="handleChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JSelectBiz>
|
||||
<UserSelectByDepModal
|
||||
:rowKey="rowKey"
|
||||
@register="regModal"
|
||||
@getSelectResult="setValue"
|
||||
v-bind="getBindValue"
|
||||
:userSecurityLevel="interUserSecurityLevel"
|
||||
></UserSelectByDepModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { unref } from 'vue';
|
||||
import UserSelectByDepModal from '@/components/semri/userComponent/modal/SzUserSelectByDepModal.vue';
|
||||
import JSelectBiz from '@/components/Form/src/jeecg/components/base/JSelectBiz.vue';
|
||||
import { defineComponent, ref, reactive, watchEffect, watch, provide } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import SzUserSelectModal from '@/components/semri/userComponent/modal/SzUserSelectModal.vue';
|
||||
export default defineComponent({
|
||||
name: 'SzSelectUserByDept',
|
||||
components: {
|
||||
SzUserSelectModal,
|
||||
UserSelectByDepModal,
|
||||
JSelectBiz,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
userSecurityLevel: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
const emitData = ref<any[]>();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
//表单值
|
||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||
//下拉框选项值
|
||||
const selectOptions = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues = reactive<object>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const tag = ref(false);
|
||||
const attrs = useAttrs();
|
||||
const interUserSecurityLevel = ref<number>(props.userSecurityLevel);
|
||||
|
||||
/**
|
||||
* 监听组件值
|
||||
*/
|
||||
watchEffect(() => {
|
||||
initValue();
|
||||
});
|
||||
|
||||
/**
|
||||
* 监听selectValues变化
|
||||
*/
|
||||
watch(selectValues, () => {
|
||||
if (selectValues) {
|
||||
// update-begin--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
if (props.value === undefined && selectValues.value?.length == 0) {
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
state.value = selectValues.value;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 打卡弹出框
|
||||
*/
|
||||
function handleOpen() {
|
||||
tag.value = true;
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串值转化为数组
|
||||
*/
|
||||
function initValue() {
|
||||
let value = props.value ? props.value : [];
|
||||
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
|
||||
state.value = value.split(',');
|
||||
selectValues.value = value.split(',');
|
||||
} else {
|
||||
selectValues.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置下拉框的值
|
||||
*/
|
||||
function setValue(options, values) {
|
||||
selectOptions.value = options;
|
||||
//emitData.value = values.join(",");
|
||||
state.value = values;
|
||||
selectValues.value = values;
|
||||
emit('update:value', values);
|
||||
emit('options-change', options);
|
||||
}
|
||||
|
||||
function handleChange(values) {
|
||||
emit('update:value', values);
|
||||
}
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
return {
|
||||
state,
|
||||
attrs,
|
||||
selectOptions,
|
||||
getBindValue,
|
||||
selectValues,
|
||||
loadingEcho,
|
||||
tag,
|
||||
regModal,
|
||||
setValue,
|
||||
handleOpen,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,234 @@
|
||||
<!--用户选择组件-->
|
||||
<template>
|
||||
<div class="JselectUser">
|
||||
<JSelectBiz @change="handleSelectChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JSelectBiz>
|
||||
<!-- update-begin--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
|
||||
<a-form-item>
|
||||
<SzUserSelectModal
|
||||
:rowKey="rowKey"
|
||||
@register="regModal"
|
||||
@getSelectResult="setValue"
|
||||
v-bind="getBindValue"
|
||||
:excludeUserIdList="excludeUserIdList"
|
||||
:userSecurityLevel="interUserSecurityLevel"
|
||||
@close="handleClose"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- update-end--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { unref } from 'vue';
|
||||
import SzUserSelectModal from '@/components/semri/userComponent/modal/SzUserSelectModal.vue';
|
||||
import JSelectBiz from '@/components/Form/src/jeecg/components/base/JSelectBiz.vue';
|
||||
import { defineComponent, ref, reactive, watchEffect, watch, provide } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { SzUserSelectByDeptRoleSecurityModal } from '@/components/semri/userComponent/modal/SzUserSelectByDeptRoleSecurityModal.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SzSelectUser',
|
||||
components: {
|
||||
SzUserSelectModal,
|
||||
JSelectBiz,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
userSecurityLevel: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
//排除用户id的集合
|
||||
excludeUserIdList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit }) {
|
||||
const emitData = ref<any[]>();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
//表单值
|
||||
// const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||
//下拉框选项值
|
||||
const selectOptions = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues = reactive<Recordable>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
const interUserSecurityLevel = ref<number>(props.userSecurityLevel);
|
||||
let tempSave: any = [];
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const tag = ref(false);
|
||||
const attrs = useAttrs();
|
||||
|
||||
/**
|
||||
* 监听组件值
|
||||
*/
|
||||
watchEffect(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240611---for:【TV360X-576】已选中了数据,再次选择打开弹窗点击取消,数据清空了
|
||||
//update-begin-author:liusq---date:2024-06-03--for: [TV360X-840]用户授权,没有选择,点取消,也会回显一个选过的用户
|
||||
tempSave = [];
|
||||
//update-end-author:liusq---date:2024-06-03--for:[TV360X-840]用户授权,没有选择,点取消,也会回显一个选过的用户
|
||||
// update-end--author:liaozhiyang---date:20240611---for:【TV360X-576】已选中了数据,再次选择打开弹窗点击取消,数据清空了
|
||||
props.value && initValue();
|
||||
// 查询条件重置的时候 界面显示未清空
|
||||
if (!props.value) {
|
||||
selectValues.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 监听selectValues变化
|
||||
*/
|
||||
// watch(selectValues, () => {
|
||||
// if (selectValues) {
|
||||
// state.value = selectValues.value;
|
||||
// }
|
||||
// });
|
||||
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
const excludeUserIdList = ref<any>([]);
|
||||
|
||||
/**
|
||||
* 需要监听一下excludeUserIdList,否则modal获取不到
|
||||
*/
|
||||
watch(
|
||||
() => props.excludeUserIdList,
|
||||
(data) => {
|
||||
excludeUserIdList.value = data;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
|
||||
/**
|
||||
* 打卡弹出框
|
||||
*/
|
||||
function handleOpen() {
|
||||
tag.value = true;
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串值转化为数组
|
||||
*/
|
||||
function initValue() {
|
||||
let value = props.value ? props.value : [];
|
||||
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
|
||||
// state.value = value.split(',');
|
||||
selectValues.value = value.split(',');
|
||||
tempSave = value.split(',');
|
||||
} else {
|
||||
// 【VUEN-857】兼容数组(行编辑的用法问题)
|
||||
selectValues.value = value;
|
||||
tempSave = cloneDeep(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置下拉框的值
|
||||
*/
|
||||
function setValue(options, values) {
|
||||
selectOptions.value = options;
|
||||
//emitData.value = values.join(",");
|
||||
// state.value = values;
|
||||
selectValues.value = values;
|
||||
send(values);
|
||||
}
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
|
||||
const handleClose = () => {
|
||||
if (tempSave.length) {
|
||||
selectValues.value = cloneDeep(tempSave);
|
||||
} else {
|
||||
send(tempSave);
|
||||
}
|
||||
};
|
||||
const handleSelectChange = (values) => {
|
||||
tempSave = cloneDeep(values);
|
||||
send(tempSave);
|
||||
};
|
||||
const send = (values) => {
|
||||
let result = typeof props.value == 'string' ? values.join(',') : values;
|
||||
emit('update:value', result);
|
||||
emit('change', result);
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
|
||||
return {
|
||||
// state,
|
||||
attrs,
|
||||
selectOptions,
|
||||
getBindValue,
|
||||
selectValues,
|
||||
loadingEcho,
|
||||
tag,
|
||||
regModal,
|
||||
setValue,
|
||||
handleOpen,
|
||||
excludeUserIdList,
|
||||
handleClose,
|
||||
handleSelectChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// update-begin--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式
|
||||
.JselectUser {
|
||||
> .ant-form-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
// 1. 修改引入路径,Jeecg 使用的是 defHttp
|
||||
import { defHttp } from '@/utils/http/axios';
|
||||
|
||||
// 根据部门+角色+密级筛选用户
|
||||
export function queryDepartRoleUserPageList(params) {
|
||||
return defHttp.get({
|
||||
// 2. 注意:Jeecg 默认配置了 baseUrl,通常不需要写 /jeecg-boot 前缀
|
||||
url: '/sys/user/queryUserRoleComponentData',
|
||||
// 3. GET 请求的参数 key 是 params,而不是 data
|
||||
params,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<!--通过部门选择用户-->
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="1200px" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
|
||||
<a-row :gutter="10">
|
||||
<a-col :md="7" :sm="24">
|
||||
<a-card :style="{ minHeight: '613px', overflow: 'auto' }">
|
||||
<!--组织机构-->
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:style="{ minWidth: '250px' }"
|
||||
selectable
|
||||
@select="onDepSelect"
|
||||
:load-data="loadChildrenTreeData"
|
||||
:treeData="departTree"
|
||||
:selectedKeys="selectedDepIds"
|
||||
:expandedKeys="expandedKeys"
|
||||
:clickRowToExpand="false"
|
||||
></BasicTree>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :md="17" :sm="24">
|
||||
<a-card :style="{ minHeight: '613px', overflow: 'auto' }">
|
||||
<!--用户列表-->
|
||||
<BasicTable ref="tableRef" v-bind="getBindValue" :searchInfo="searchInfo" :api="getTableList" :rowSelection="rowSelection"></BasicTable>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, unref, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { BasicTree } from '/src/components/Tree';
|
||||
import { queryTreeList, getTableList as getTableListOrigin } from '/src/api/common/api';
|
||||
import { createAsyncComponent } from '/src/utils/factory/createAsyncComponent';
|
||||
import { useSelectBiz } from '/src/components/Form/src/jeecg/hooks/useSelectBiz';
|
||||
import { useAttrs } from '/src/hooks/core/useAttrs';
|
||||
import { queryDepartTreeSync as queryDepartTreeSyncOrigin } from '/src/views/system/depart/depart.api';
|
||||
import { selectProps } from '/src/components/Form/src/jeecg/props/props';
|
||||
export default defineComponent({
|
||||
name: 'SzUserSelectByDepModal',
|
||||
components: {
|
||||
//此处需要异步加载BasicTable
|
||||
BasicModal,
|
||||
BasicTree,
|
||||
BasicTable: createAsyncComponent(() => import('/src/components/Table/src/BasicTable.vue'), {
|
||||
loading: true,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
...selectProps,
|
||||
//选择框标题
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
userSecurityLevel: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
},
|
||||
emits: ['register', 'getSelectResult'],
|
||||
setup(props, { emit, refs }) {
|
||||
const tableRef = ref();
|
||||
const treeRef = ref();
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner(async (data) => {
|
||||
await queryDepartTree();
|
||||
});
|
||||
const attrs = useAttrs();
|
||||
const departTree = ref([]);
|
||||
const selectedDepIds = ref([]);
|
||||
const expandedKeys = ref([]);
|
||||
const searchInfo = {};
|
||||
/**
|
||||
*表格配置
|
||||
*/
|
||||
const tableProps = {
|
||||
columns: [
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'username',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '用户姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex_dictText',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
// width: 50,
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
// width: 50,
|
||||
},
|
||||
{
|
||||
title: '密级',
|
||||
dataIndex: 'userSecurityLevel_dictText',
|
||||
},
|
||||
],
|
||||
useSearchForm: true,
|
||||
canResize: false,
|
||||
showIndexColumn: false,
|
||||
striped: true,
|
||||
bordered: true,
|
||||
size: 'small',
|
||||
formConfig: {
|
||||
//labelWidth: 200,
|
||||
baseColProps: {
|
||||
xs: 24,
|
||||
sm: 8,
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
xxl: 10,
|
||||
},
|
||||
//update-begin-author:liusq date:2023-10-30 for: [issues/5514]组件页面显示错位
|
||||
actionColOptions: {
|
||||
xs: 24,
|
||||
sm: 12,
|
||||
md: 12,
|
||||
lg: 12,
|
||||
xl: 8,
|
||||
xxl: 8,
|
||||
},
|
||||
//update-end-author:liusq date:2023-10-30 for: [issues/5514]组件页面显示错位
|
||||
schemas: [
|
||||
{
|
||||
label: '账号',
|
||||
field: 'username',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
field: 'realname',
|
||||
component: 'JInput',
|
||||
},
|
||||
{
|
||||
label: '密级',
|
||||
field: 'searchSecurityLevel',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
defaultValue: 3,
|
||||
dictCode: 'user_security_level',
|
||||
placeholder: '请选择密级',
|
||||
stringToNumber: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
resetFunc: customResetFunc,
|
||||
},
|
||||
};
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs), tableProps);
|
||||
const [{ rowSelection, visibleChange, indexColumnProps, getSelectResult, reset }] = useSelectBiz(getTableList, getBindValue);
|
||||
|
||||
function getTableList(params) {
|
||||
params = parseParams(params);
|
||||
return getTableListOrigin({ ...params });
|
||||
}
|
||||
|
||||
function queryDepartTreeSync(params) {
|
||||
params = parseParams(params);
|
||||
return queryDepartTreeSyncOrigin({ ...params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
* @param params
|
||||
*/
|
||||
function parseParams(params) {
|
||||
if (props?.params) {
|
||||
return {
|
||||
...params,
|
||||
...props.params,
|
||||
};
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载树形数据
|
||||
*/
|
||||
function queryDepartTree() {
|
||||
queryDepartTreeSync().then((res) => {
|
||||
if (res) {
|
||||
departTree.value = res;
|
||||
// 默认展开父节点
|
||||
//expandedKeys.value = unref(departTree).map(item => item.id)
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 加载子级部门
|
||||
*/
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
pid: treeNode.eventKey,
|
||||
});
|
||||
const asyncTreeAction = unref(treeRef);
|
||||
if (asyncTreeAction) {
|
||||
asyncTreeAction.updateNodeByKey(treeNode.eventKey, { children: result });
|
||||
asyncTreeAction.setExpandedKeys([treeNode.eventKey, ...asyncTreeAction.getExpandedKeys()]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
/**
|
||||
* 点击树节点,筛选出对应的用户
|
||||
*/
|
||||
function onDepSelect(keys) {
|
||||
if (keys[0] != null) {
|
||||
if (unref(selectedDepIds)[0] !== keys[0]) {
|
||||
selectedDepIds.value = [keys[0]];
|
||||
}
|
||||
searchInfo['departId'] = unref(selectedDepIds).join(',');
|
||||
tableRef.value.reload();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 自定义重置方法
|
||||
* */
|
||||
async function customResetFunc() {
|
||||
console.log('自定义查询');
|
||||
//树节点清空
|
||||
selectedDepIds.value = [];
|
||||
//查询条件清空
|
||||
searchInfo['departId'] = '';
|
||||
//选择项清空
|
||||
reset();
|
||||
}
|
||||
/**
|
||||
* 确定选择
|
||||
*/
|
||||
function handleOk() {
|
||||
getSelectResult((options, values) => {
|
||||
//回传选项和已选择的值
|
||||
emit('getSelectResult', options, values);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
//config,
|
||||
handleOk,
|
||||
searchInfo,
|
||||
register,
|
||||
indexColumnProps,
|
||||
visibleChange,
|
||||
getBindValue,
|
||||
rowSelection,
|
||||
|
||||
departTree,
|
||||
selectedDepIds,
|
||||
expandedKeys,
|
||||
treeRef,
|
||||
tableRef,
|
||||
getTableList,
|
||||
onDepSelect,
|
||||
loadChildrenTreeData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<a-modal :open="open" title="选择用户(部门+角色+密级)" :width="1000" :mask-closable="false" @cancel="handleCancel" @ok="handleOk" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
|
||||
<a-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
size="middle"
|
||||
:columns="columns"
|
||||
:data-source="userList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ y: 400 }"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, nextTick } from 'vue';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { queryDepartRoleUserPageList } from '../api/user';
|
||||
|
||||
defineOptions({ name: 'SzUserSelectByDeptRoleSecurityModal' });
|
||||
|
||||
const props = defineProps({
|
||||
open: { type: Boolean, default: false },
|
||||
deptId: { type: String, default: null },
|
||||
roleId: { type: String, default: null },
|
||||
securityValue: { type: Number, default: null },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:open', 'confirm']);
|
||||
|
||||
// --- 状态定义 ---
|
||||
const loading = ref(false);
|
||||
const userList = ref([]);
|
||||
const selectedRowKeys = ref([]);
|
||||
const selectedRows = ref([]);
|
||||
|
||||
const innerDeptRoleId = ref(props.deptId);
|
||||
const innerRoleId = ref(props.roleId);
|
||||
const innerSecurityValue = ref(props.securityValue);
|
||||
|
||||
// 分页参数:注意 a-table 期待的属性名是 total, current, pageSize
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
});
|
||||
|
||||
// --- 表单配置 ---
|
||||
const [registerForm, { getFieldsValue, resetFields, validate }] = useForm({
|
||||
labelWidth: 80,
|
||||
// 增加 baseColProps 让布局更紧凑
|
||||
baseColProps: { span: 8 },
|
||||
schemas: [
|
||||
{
|
||||
field: 'departId',
|
||||
label: '部门',
|
||||
component: 'JSelectDept',
|
||||
componentProps: {
|
||||
showButton: false,
|
||||
checkStrictly: true, // 如果需要选子部门不级联
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'roleId',
|
||||
label: '角色',
|
||||
component: 'JSelectRole',
|
||||
},
|
||||
{
|
||||
field: 'searchSecurityLevel',
|
||||
label: '密级',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { dictCode: 'user_security_level' },
|
||||
},
|
||||
],
|
||||
showActionButtonGroup: true,
|
||||
showAdvancedButton: false,
|
||||
actionColOptions: { span: 24 }, // 按钮行占满全屏,右对齐
|
||||
submitButtonOptions: { text: '查询' },
|
||||
resetButtonOptions: { text: '重置' },
|
||||
// 查询逻辑
|
||||
submitFunc: async () => {
|
||||
pagination.current = 1;
|
||||
await loadData();
|
||||
},
|
||||
// 重置逻辑
|
||||
resetFunc: async () => {
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
pagination.current = 1;
|
||||
await loadData();
|
||||
},
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ title: '用户名', dataIndex: 'username', key: 'username', width: 120 },
|
||||
{ title: '真实姓名', dataIndex: 'realname', key: 'realname', width: 120 },
|
||||
{ title: '部门', dataIndex: 'orgCodeTxt', key: 'orgCodeTxt', width: 200 },
|
||||
{ title: '密级', dataIndex: 'userSecurityLevel_dictText', key: 'userSecurityLevel', width: 100 },
|
||||
];
|
||||
|
||||
// --- 核心数据加载逻辑 ---
|
||||
async function loadData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const values = getFieldsValue() || {};
|
||||
const params = {
|
||||
...values,
|
||||
pageNo: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
};
|
||||
|
||||
const res = await queryDepartRoleUserPageList(params);
|
||||
|
||||
// --- 核心修复:兼容性处理 ---
|
||||
// 检查 res 本身是否包含 records (你现在的情况)
|
||||
// 或者 res.result 包含 records (之前的预期)
|
||||
let dataSource = [];
|
||||
let totalCount = 0;
|
||||
|
||||
if (res && res.records) {
|
||||
// 对应你当前的情况
|
||||
dataSource = res.records;
|
||||
totalCount = res.total;
|
||||
} else if (res && res.result && res.result.records) {
|
||||
// 对应之前的预期
|
||||
dataSource = res.result.records;
|
||||
totalCount = res.result.total;
|
||||
}
|
||||
|
||||
userList.value = dataSource || [];
|
||||
pagination.total = parseInt(totalCount || 0);
|
||||
|
||||
console.log('解析后的数据:', userList.value); // 调试用
|
||||
} catch (e) {
|
||||
console.error('加载用户列表失败:', e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 事件处理 ---
|
||||
function onSelectChange(keys, rows) {
|
||||
selectedRowKeys.value = keys;
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
|
||||
function handleTableChange(p) {
|
||||
pagination.current = p.current;
|
||||
pagination.pageSize = p.pageSize;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
// 如果没有选择,可以加个提示,或者直接关闭
|
||||
}
|
||||
emit('confirm', selectedRows.value);
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
// 监听 Modal 打开
|
||||
watch(
|
||||
() => props.open,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
// 必须在 nextTick 后执行,确保 BasicForm 实例已挂载,getFieldsValue 可用
|
||||
await nextTick();
|
||||
loadData();
|
||||
} else {
|
||||
// 弹窗关闭时建议重置表单和选中项
|
||||
resetFields();
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 调整表单底部间距 */
|
||||
:deep(.ant-form) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,325 @@
|
||||
<!--用户选择框-->
|
||||
<template>
|
||||
<div>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="modalTitle"
|
||||
:width="showSelected ? '1200px' : '900px'"
|
||||
wrapClassName="j-user-select-modal"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:maxHeight="maxHeight"
|
||||
:centered="true"
|
||||
destroyOnClose
|
||||
@visible-change="visibleChange"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="showSelected ? 18 : 24">
|
||||
<BasicTable
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:scroll="tableScroll"
|
||||
v-bind="getBindValue"
|
||||
:useSearchForm="true"
|
||||
:formConfig="formConfig"
|
||||
:api="getUserList"
|
||||
:searchInfo="searchInfo"
|
||||
:rowSelection="rowSelection"
|
||||
:indexColumnProps="indexColumnProps"
|
||||
:afterFetch="afterFetch"
|
||||
:beforeFetch="beforeFetch"
|
||||
>
|
||||
<!-- update-begin-author:taoyan date:2022-5-25 for: VUEN-1112一对多 用户选择 未显示选择条数,及清空 -->
|
||||
<template #tableTitle></template>
|
||||
<!-- update-end-author:taoyan date:2022-5-25 for: VUEN-1112一对多 用户选择 未显示选择条数,及清空 -->
|
||||
</BasicTable>
|
||||
</a-col>
|
||||
<a-col :span="showSelected ? 6 : 0">
|
||||
<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>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, unref, ref, watch } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { getUserList } from '/src/api/common/api';
|
||||
import { createAsyncComponent } from '/src/utils/factory/createAsyncComponent';
|
||||
import { useSelectBiz } from '/src/components/Form/src/jeecg/hooks/useSelectBiz';
|
||||
import { useAttrs } from '/src/hooks/core/useAttrs';
|
||||
import { selectProps } from '/src/components/Form/src/jeecg/props/props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SzUserSelectModal',
|
||||
components: {
|
||||
//此处需要异步加载BasicTable
|
||||
BasicModal,
|
||||
BasicTable: createAsyncComponent(() => import('/src/components/Table/src/BasicTable.vue'), {
|
||||
loading: true,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
...selectProps,
|
||||
//选择框标题
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
//排除用户id的集合
|
||||
excludeUserIdList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
userSecurityLevel: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
},
|
||||
emits: ['register', 'getSelectResult', 'close'],
|
||||
setup(props, { emit, refs }) {
|
||||
// update-begin-author:taoyan date:2022-5-24 for: VUEN-1086 【移动端】用户选择 查询按钮 效果不好 列表展示没有滚动条
|
||||
const tableScroll = ref<any>({ x: false });
|
||||
const tableRef = ref();
|
||||
const maxHeight = ref(600);
|
||||
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner(() => {
|
||||
if (window.innerWidth < 900) {
|
||||
tableScroll.value = { x: 900 };
|
||||
} else {
|
||||
tableScroll.value = { x: false };
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-6-2 for: VUEN-1112 一对多 用户选择 未显示选择条数,及清空
|
||||
setTimeout(() => {
|
||||
if (tableRef.value) {
|
||||
tableRef.value.setSelectedRowKeys(selectValues['value'] || []);
|
||||
}
|
||||
}, 800);
|
||||
//update-end-author:taoyan date:2022-6-2 for: VUEN-1112 一对多 用户选择 未显示选择条数,及清空
|
||||
});
|
||||
// update-end-author:taoyan date:2022-5-24 for: VUEN-1086 【移动端】用户选择 查询按钮 效果不好 列表展示没有滚动条
|
||||
const attrs = useAttrs();
|
||||
//表格配置
|
||||
const config = {
|
||||
canResize: false,
|
||||
bordered: true,
|
||||
size: 'small',
|
||||
};
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs), config);
|
||||
const [{ rowSelection, visibleChange, selectValues, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(
|
||||
getUserList,
|
||||
getBindValue,
|
||||
emit
|
||||
);
|
||||
const searchInfo = ref(props.params);
|
||||
// update-begin--author:liaozhiyang---date:20230811---for:【issues/657】右侧选中列表删除无效
|
||||
watch(rowSelection.selectedRowKeys, (newVal) => {
|
||||
//update-begin---author:wangshuai ---date: 20230829 for:null指针异常导致控制台报错页面不显示------------
|
||||
if (tableRef.value) {
|
||||
tableRef.value.setSelectedRowKeys(newVal);
|
||||
}
|
||||
//update-end---author:wangshuai ---date: 20230829 for:null指针异常导致控制台报错页面不显示------------
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20230811---for:【issues/657】右侧选中列表删除无效
|
||||
//查询form
|
||||
const formConfig = {
|
||||
baseColProps: {
|
||||
xs: 24,
|
||||
sm: 8,
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
xxl: 6,
|
||||
},
|
||||
//update-begin-author:taoyan date:2022-5-24 for: VUEN-1086 【移动端】用户选择 查询按钮 效果不好 列表展示没有滚动条---查询表单按钮的栅格布局和表单的保持一致
|
||||
actionColOptions: {
|
||||
xs: 24,
|
||||
sm: 8,
|
||||
md: 8,
|
||||
lg: 8,
|
||||
xl: 8,
|
||||
xxl: 8,
|
||||
},
|
||||
//update-end-author:taoyan date:2022-5-24 for: VUEN-1086 【移动端】用户选择 查询按钮 效果不好 列表展示没有滚动条---查询表单按钮的栅格布局和表单的保持一致
|
||||
schemas: [
|
||||
{
|
||||
label: '账号',
|
||||
field: 'username',
|
||||
component: 'JInput',
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
field: 'realname',
|
||||
component: 'JInput',
|
||||
},
|
||||
{
|
||||
label: '密级',
|
||||
field: 'searchSecurityLevel',
|
||||
component: 'JDictSelectTag',
|
||||
defaultValue: 3,
|
||||
componentProps: {
|
||||
dictCode: 'user_security_level',
|
||||
placeholder: '请选择密级',
|
||||
stringToNumber: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
//定义表格列
|
||||
const columns = [
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'username',
|
||||
width: 120,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '用户姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '密级',
|
||||
dataIndex: 'userSecurityLevel_dictText',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex_dictText',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
// width: 40,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status_dictText',
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
//已选择的table信息
|
||||
const selectedTable = {
|
||||
pagination: false,
|
||||
showIndexColumn: false,
|
||||
scroll: { y: 390 },
|
||||
size: 'small',
|
||||
canResize: false,
|
||||
bordered: true,
|
||||
rowKey: 'id',
|
||||
columns: [
|
||||
{
|
||||
title: '用户姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 40,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
slots: { customRender: 'action' },
|
||||
},
|
||||
],
|
||||
};
|
||||
/**
|
||||
* 确定选择
|
||||
*/
|
||||
function handleOk() {
|
||||
getSelectResult((options, values) => {
|
||||
//回传选项和已选择的值
|
||||
emit('getSelectResult', options, values);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
//update-begin---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
/**
|
||||
* 用户返回结果逻辑查询
|
||||
*/
|
||||
function afterFetch(record) {
|
||||
let excludeList = props.excludeUserIdList;
|
||||
if (!excludeList) {
|
||||
return record;
|
||||
}
|
||||
let arr: any[] = [];
|
||||
//如果存在过滤用户id集合,并且后台返回的数据不为空
|
||||
if (excludeList.length > 0 && record && record.length > 0) {
|
||||
for (let item of record) {
|
||||
if (excludeList.indexOf(item.id) < 0) {
|
||||
arr.push({ ...item });
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
|
||||
const handleCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入
|
||||
//update-end---author:wangshuai ---date:20230703 for:【QQYUN-5685】5、离职人员可以选自己------------
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-305】小屏幕展示10条
|
||||
const clientHeight = document.documentElement.clientHeight * 200;
|
||||
maxHeight.value = clientHeight > 600 ? 600 : clientHeight;
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-305】小屏幕展示10条
|
||||
|
||||
//update-begin---author:wangshuai---date:2024-07-03---for:【TV360X-1629】用户选择组件不是根据创建时间正序排序的---
|
||||
/**
|
||||
* 请求之前根据创建时间排序
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
function beforeFetch(params) {
|
||||
return Object.assign({ column: 'createTime', order: 'desc' }, params);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2024-07-03---for:【TV360X-1629】用户选择组件不是根据创建时间正序排序的---
|
||||
|
||||
return {
|
||||
//config,
|
||||
handleOk,
|
||||
searchInfo,
|
||||
register,
|
||||
indexColumnProps,
|
||||
visibleChange,
|
||||
getBindValue,
|
||||
getUserList,
|
||||
formConfig,
|
||||
columns,
|
||||
rowSelection,
|
||||
selectRows,
|
||||
selectedTable,
|
||||
handleDeleteSelected,
|
||||
tableScroll,
|
||||
tableRef,
|
||||
afterFetch,
|
||||
handleCancel,
|
||||
maxHeight,
|
||||
beforeFetch,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user