--添加测试模块

This commit is contained in:
2025-10-13 11:53:54 +08:00
parent c3c93fe308
commit e1326c7ce8
146 changed files with 11171 additions and 807 deletions

View File

@ -0,0 +1,216 @@
<template>
<SimpleForm
ref="systemFormRef"
:formProps="data.formDataProps"
:formModel="{}"
:isWorkFlow="props.fromPage!=FromPageType.MENU"
/>
</template>
<script lang="ts" setup>
import { reactive, ref,onBeforeMount,onMounted } from 'vue';
import { formProps, formEventConfigs ,formConfig} from './config';
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
import { addTestfrom3, getTestfrom3, updateTestfrom3 } from '/@/api/system/Testfrom3';
import { cloneDeep } from 'lodash-es';
import { FormDataProps } from '/@/components/Designer/src/types';
import { usePermission } from '/@/hooks/web/usePermission';
import { useFormConfig } from '/@/hooks/web/useFormConfig';
import { FromPageType } from '/@/enums/workflowEnum';
import { createFormEvent, getFormDataEvent, loadFormEvent, submitFormEvent,} from '/@/hooks/web/useFormEvent';
import { changeWorkFlowForm, changeSchemaDisabled } from '/@/hooks/web/useWorkFlowForm';
import { WorkFlowFormParams } from '/@/model/workflow/bpmnConfig';
import { useRouter } from 'vue-router';
const { filterFormSchemaAuth } = usePermission();
const { mergeFormSchemas,mergeFormEventConfigs } = useFormConfig();
const { currentRoute } = useRouter();
const RowKey = 'id';
const emits = defineEmits(['changeUploadComponentIds','loadingCompleted', 'form-mounted']);
const props = defineProps({
fromPage: {
type: Number,
default: FromPageType.MENU,
},
});
const systemFormRef = ref();
const data: { formDataProps: FormDataProps } = reactive({
formDataProps: {schemas:[]} as FormDataProps,
});
const state = reactive({
formModel: {},
});
let customFormEventConfigs=[];
onMounted(async () => {
try {
// 合并渲染覆盖配置中的字段配置、表单事件配置
await mergeCustomFormRenderConfig();
if (props.fromPage == FromPageType.MENU) {
setMenuPermission();
await createFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:初始化表单
await loadFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:加载表单
} else if (props.fromPage == FromPageType.FLOW) {
emits('loadingCompleted'); //告诉系统表单已经加载完毕
// loadingCompleted后 工作流页面直接利用Ref调用setWorkFlowForm方法
} else if (props.fromPage == FromPageType.PREVIEW) {
// 预览 无需权限,表单事件也无需执行
} else if (props.fromPage == FromPageType.DESKTOP) {
// 桌面设计 表单事件需要执行
emits('loadingCompleted'); //告诉系统表单已经加载完毕
await createFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:初始化表单
await loadFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:加载表单
}
emits('form-mounted', formProps);
} catch (error) {
}
});
async function mergeCustomFormRenderConfig() {
let cloneProps=cloneDeep(formProps);
let fEventConfigs=cloneDeep(formEventConfigs);
if (formConfig.useCustomConfig) {
if(props.fromPage !== FromPageType.FLOW){
let formPath=currentRoute.value.query.formPath;
//1.合并字段配置
cloneProps.schemas=await mergeFormSchemas({formSchema:cloneProps.schemas!,formPath:formPath});
//2.合并表单事件配置
fEventConfigs=await mergeFormEventConfigs({formEventConfigs:fEventConfigs,formPath:formPath});
}
}
data.formDataProps=cloneProps;
customFormEventConfigs=fEventConfigs;
}
// 根据菜单页面权限,设置表单属性(必填,禁用,显示)
function setMenuPermission() {
data.formDataProps.schemas = filterFormSchemaAuth(data.formDataProps.schemas!);
}
// 校验form 通过返回表单数据
async function validate() {
let values = [];
try {
values = await systemFormRef.value?.validate();
//添加隐藏组件
if (data.formDataProps.hiddenComponent?.length) {
data.formDataProps.hiddenComponent.forEach((component) => {
values[component.bindField] = component.value;
});
}
} finally {
}
return values;
}
// 根据行唯一ID查询行数据并设置表单数据 【编辑】
async function setFormDataFromId(rowId, skipUpdate) {
try {
const record = await getTestfrom3(rowId);
if (skipUpdate) {
return record;
}
setFieldsValue(record);
state.formModel = record;
await getFormDataEvent(customFormEventConfigs, state.formModel, systemFormRef.value, formProps.schemas); //表单事件:获取表单数据
return record;
} catch (error) {
}
}
// 辅助设置表单数据
function setFieldsValue(record) {
systemFormRef.value.setFieldsValue(record);
}
// 重置表单数据
async function resetFields() {
await systemFormRef.value.resetFields();
}
// 设置表单数据全部为Disabled 【查看】
async function setDisabledForm(isDisabled) {
data.formDataProps.schemas = changeSchemaDisabled(cloneDeep(data.formDataProps.schemas),isDisabled);
}
// 获取行键值
function getRowKey() {
return RowKey;
}
// 更新api表单数据
async function update({ values, rowId }) {
try {
values[RowKey] = rowId;
state.formModel = values;
let saveVal = await updateTestfrom3(values);
await submitFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:提交表单
return saveVal;
} catch (error) {}
}
// 新增api表单数据
async function add(values) {
try {
state.formModel = values;
let saveVal = await addTestfrom3(values);
await submitFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:提交表单
return saveVal;
} catch (error) {}
}
// 根据工作流页面权限,设置表单属性(必填,禁用,显示)
async function setWorkFlowForm(obj: WorkFlowFormParams) {
try {
const cloneProps=cloneDeep(formProps);
customFormEventConfigs=cloneDeep(formEventConfigs);
if (formConfig.useCustomConfig) {
const parts = obj.formConfigKey.split('_');
const formId=parts[1];
cloneProps.schemas=await mergeFormSchemas({formSchema:cloneProps.schemas!,formId:formId});
customFormEventConfigs=await mergeFormEventConfigs({formEventConfigs:customFormEventConfigs,formId:formId});
}
let flowData = changeWorkFlowForm(cloneProps, obj);
let { buildOptionJson, uploadComponentIds, formModels, isViewProcess } = flowData;
data.formDataProps = buildOptionJson;
emits('changeUploadComponentIds', uploadComponentIds); //工作流中必须保存上传组件id【附件汇总需要】
if (isViewProcess) {
setDisabledForm(); //查看
}
state.formModel = formModels;
if(formModels[RowKey]) {
setFormDataFromId(formModels[RowKey], false)
} else {
setFieldsValue(formModels)
}
} catch (error) {}
await createFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:初始化表单
await loadFormEvent(customFormEventConfigs, state.formModel,
systemFormRef.value,
formProps.schemas); //表单事件:加载表单
}
defineExpose({
setFieldsValue,
resetFields,
validate,
add,
update,
setFormDataFromId,
setDisabledForm,
setMenuPermission,
setWorkFlowForm,
getRowKey,
});
</script>

View File

@ -0,0 +1,110 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" @cancel="handleClose" :paddingRight="15" :bodyStyle="{ minHeight: '400px !important' }">
<ModalForm ref="formRef" :fromPage="FromPageType.MENU" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, reactive } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { formProps } from './config';
import ModalForm from './Form.vue';
import { FromPageType } from '/@/enums/workflowEnum';
const emit = defineEmits(['success', 'register']);
const { notification } = useMessage();
const formRef = ref();
const state = reactive({
formModel: {},
isUpdate: true,
isView: false,
isCopy: false,
rowId: '',
});
const { t } = useI18n();
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
state.isUpdate = !!data?.isUpdate;
state.isView = !!data?.isView;
state.isCopy = !!data?.isCopy;
setModalProps({
destroyOnClose: true,
maskClosable: false,
showCancelBtn: !state.isView,
showOkBtn: !state.isView,
canFullscreen: true,
width: 900,
});
if (state.isUpdate || state.isView || state.isCopy) {
state.rowId = data.id;
if (state.isView) {
await formRef.value.setDisabledForm();
}
await formRef.value.setFormDataFromId(state.rowId);
} else {
formRef.value.resetFields();
}
});
const getTitle = computed(() => (state.isView ? '查看' : !state.isUpdate ? '新增' : '编辑'));
async function saveModal() {
let saveSuccess = false;
try {
const values = await formRef.value?.validate();
//添加隐藏组件
if (formProps.hiddenComponent?.length) {
formProps.hiddenComponent.forEach((component) => {
values[component.bindField] = component.value;
});
}
if (values !== false) {
try {
if (!state.isUpdate || state.isCopy) {
saveSuccess = await formRef.value.add(values);
} else {
saveSuccess = await formRef.value.update({ values, rowId: state.rowId });
}
return saveSuccess;
} catch (error) {}
}
} catch (error) {
return saveSuccess;
}
}
async function handleSubmit() {
try {
const saveSuccess = await saveModal();
setModalProps({ confirmLoading: true });
if (saveSuccess) {
if (!state.isUpdate || state.isCopy) {
//false 新增
notification.success({
message: 'Tip',
description: t('新增成功!'),
}); //提示消息
} else {
notification.success({
message: 'Tip',
description: t('修改成功!'),
}); //提示消息
}
closeModal();
formRef.value.resetFields();
emit('success');
}
} finally {
setModalProps({ confirmLoading: false });
}
}
function handleClose() {
formRef.value.resetFields();
}
</script>

View File

@ -0,0 +1,231 @@
import { FormProps, FormSchema } from '/@/components/Form';
import { BasicColumn } from '/@/components/Table';
export const formConfig = {
useCustomConfig: false,
};
export const searchFormSchema: FormSchema[] = [
{
field: 'danXingWenBen5518',
label: '单行文本',
component: 'Input',
},
{
field: 'danXingWenBen5841',
label: '单行文本',
component: 'Input',
},
{
field: 'jiShuZuJian6835',
label: '计数组件',
component: 'InputNumber',
componentProps: {
style: { width: '100%' },
},
},
];
export const columns: BasicColumn[] = [
{
dataIndex: 'danXingWenBen5518',
title: '单行文本',
componentType: 'input',
align: 'left',
sorter: true,
},
{
dataIndex: 'danXingWenBen5841',
title: '单行文本',
componentType: 'input',
align: 'left',
sorter: true,
},
{
dataIndex: 'jiShuZuJian6835',
title: '计数组件',
componentType: 'number',
align: 'left',
sorter: true,
},
];
//表单事件
export const formEventConfigs = {
0: [
{
type: 'circle',
color: '#2774ff',
text: '开始节点',
icon: '#icon-kaishi',
bgcColor: '#D8E5FF',
isUserDefined: false,
},
{
color: '#F6AB01',
icon: '#icon-chushihua',
text: '初始化表单',
bgcColor: '#f9f5ea',
isUserDefined: false,
nodeInfo: { processEvent: [] },
},
],
1: [
{
color: '#B36EDB',
icon: '#icon-shujufenxi',
text: '获取表单数据',
detail: '(新增无此操作)',
bgcColor: '#F8F2FC',
isUserDefined: false,
nodeInfo: { processEvent: [] },
},
],
2: [
{
color: '#F8625C',
icon: '#icon-jiazai',
text: '加载表单',
bgcColor: '#FFF1F1',
isUserDefined: false,
nodeInfo: { processEvent: [] },
},
],
3: [
{
color: '#6C6AE0',
icon: '#icon-jsontijiao',
text: '提交表单',
bgcColor: '#F5F4FF',
isUserDefined: false,
nodeInfo: { processEvent: [] },
},
],
4: [
{
type: 'circle',
color: '#F8625C',
text: '结束节点',
icon: '#icon-jieshuzhiliao',
bgcColor: '#FFD6D6',
isLast: true,
isUserDefined: false,
},
],
};
export const formProps: FormProps = {
labelCol: { span: 3, offset: 0 },
labelAlign: 'right',
layout: 'horizontal',
size: 'default',
schemas: [
{
key: '4251bdeb18574f2cb014ca33d650be52',
field: 'danXingWenBen5518',
label: '单行文本',
type: 'input',
component: 'Input',
colProps: { span: 24 },
defaultValue: '',
componentProps: {
width: '100%',
span: '',
defaultValue: '',
labelWidthMode: 'fix',
labelFixWidth: 120,
responsive: false,
respNewRow: false,
placeholder: '请输入单行文本',
maxlength: null,
prefix: '',
suffix: '',
addonBefore: '',
addonAfter: '',
disabled: false,
allowClear: false,
showLabel: true,
required: false,
rules: [],
events: {},
isSave: false,
isShow: true,
scan: false,
style: { width: '100%' },
},
},
{
key: '3cea59512d464fa3b5a81814999718f3',
field: 'danXingWenBen5841',
label: '单行文本',
type: 'input',
component: 'Input',
colProps: { span: 24 },
defaultValue: '',
componentProps: {
width: '100%',
span: '',
defaultValue: '',
labelWidthMode: 'fix',
labelFixWidth: 120,
responsive: false,
respNewRow: false,
placeholder: '请输入单行文本',
maxlength: null,
prefix: '',
suffix: '',
addonBefore: '',
addonAfter: '',
disabled: false,
allowClear: false,
showLabel: true,
required: false,
rules: [],
events: {},
isSave: false,
isShow: true,
scan: false,
style: { width: '100%' },
},
},
{
key: 'c4aaaf58ffbc468bb3f6c4f013b0350b',
field: 'jiShuZuJian6835',
label: '计数组件',
type: 'number',
component: 'InputNumber',
colProps: { span: 24 },
defaultValue: 0,
componentProps: {
labelWidthMode: 'fix',
labelFixWidth: 120,
responsive: false,
width: '100%',
span: '',
defaultValue: 0,
min: 0,
max: 100,
step: 1,
maxlength: null,
disabled: false,
showLabel: true,
controls: true,
required: false,
subTotal: false,
isShow: true,
rules: [],
events: {},
style: { width: '100%' },
},
},
],
showActionButtonGroup: false,
buttonLocation: 'center',
actionColOptions: { span: 24 },
showResetButton: false,
showSubmitButton: false,
hiddenComponent: [],
};

View File

@ -0,0 +1,47 @@
export const permissionList = [
{
required: true,
view: true,
edit: true,
disabled: false,
isSaveTable: false,
tableName: '',
fieldName: '单行文本',
fieldId: 'danXingWenBen5518',
isSubTable: false,
showChildren: true,
type: 'input',
key: '4251bdeb18574f2cb014ca33d650be52',
children: [],
},
{
required: true,
view: true,
edit: true,
disabled: false,
isSaveTable: false,
tableName: '',
fieldName: '单行文本',
fieldId: 'danXingWenBen5841',
isSubTable: false,
showChildren: true,
type: 'input',
key: '3cea59512d464fa3b5a81814999718f3',
children: [],
},
{
required: true,
view: true,
edit: true,
disabled: false,
isSaveTable: false,
tableName: '',
fieldName: '计数组件',
fieldId: 'jiShuZuJian6835',
isSubTable: false,
showChildren: true,
type: 'number',
key: 'c4aaaf58ffbc468bb3f6c4f013b0350b',
children: [],
},
];

View File

@ -0,0 +1,469 @@
<template>
<PageWrapper dense fixedHeight contentFullHeight contentClass="flex">
<BasicTable @register="registerTable" ref="tableRef" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" @row-dbClick="dbClickRow">
<template #toolbar>
<template v-for="button in tableButtonConfig" :key="button.code">
<a-button v-if="button.isDefault" :type="button.type" @click="buttonClick(button.code)">
<template #icon><Icon :icon="button.icon" /></template>
{{ button.name }}
</a-button>
<a-button v-else :type="button.type">
<template #icon><Icon :icon="button.icon" /></template>
{{ button.name }}
</a-button>
</template>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<TableAction :actions="getActions(record)" />
</template>
</template>
</BasicTable>
<PrintPreview
v-if="printData.visible"
:id="printData.id"
:request-params-configs="printData.requestParamsConfigs"
:request-body-configs="printData.requestBodyConfigs"
:request-header-configs="printData.requestHeaderConfigs"
@close="printData.visible = false"
/>
<Testfrom3Modal @register="registerModal" @success="handleSuccess" />
<ImportModal @register="registerImportModal" importUrl="/system/testfrom3/import" @success="handleImportSuccess"/>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onUnmounted, createVNode,
reactive
} from 'vue';
import { Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { BasicTable, useTable, TableAction, ActionItem } from '/@/components/Table';
import { getTestfrom3Page, deleteTestfrom3, exportTestfrom3} from '/@/api/system/Testfrom3';
import { PageWrapper } from '/@/components/Page';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { usePermission } from '/@/hooks/web/usePermission';
import { useFormConfig } from '/@/hooks/web/useFormConfig';
import { useRouter } from 'vue-router';
import { getTestfrom3 } from '/@/api/system/Testfrom3';
import {generateTableJson } from '/@/utils/event/design';
import printJS from 'print-js';
import { getPrintConfigInfo } from '/@/api/system/generator/print';
import PrintPreview from '/@/views/generator/print/Preview.vue';
import { PrintButton } from '/@/enums/printEnum';
import { InputParamItem } from '/@/components/ApiConfig/src/interface';
import { useModal } from '/@/components/Modal';
import Testfrom3Modal from './components/Testfrom3Modal.vue';
import { ImportModal } from '/@/components/Import';
import { downloadByData } from '/@/utils/file/download';
import {formConfig, searchFormSchema, columns } from './components/config';
import Icon from '/@/components/Icon/index';
import useEventBus from '/@/hooks/event/useEventBus';
import { cloneDeep } from 'lodash-es';
const { bus, CREATE_FLOW, FLOW_PROCESSED, FORM_LIST_MODIFIED } = useEventBus();
const { notification } = useMessage();
const { t } = useI18n();
defineEmits(['register']);
const { filterColumnAuth, filterButtonAuth } = usePermission();
const { mergeColumns,mergeSearchFormSchema,mergeButtons } = useFormConfig();
const filterColumns = cloneDeep(filterColumnAuth(columns));
const customConfigColums =ref(filterColumns);
const customSearchFormSchema =ref(searchFormSchema);
const tableRef = ref();
//所有按钮
const buttons = ref([{"isUse":true,"name":"新增","code":"add","icon":"ant-design:plus-outlined","isDefault":true,"type":"primary"},{"isUse":true,"name":"编辑","code":"edit","icon":"ant-design:form-outlined","isDefault":true},{"isUse":true,"name":"刷新","code":"refresh","icon":"ant-design:reload-outlined","isDefault":true},{"isUse":true,"name":"查看","code":"view","icon":"ant-design:eye-outlined","isDefault":true},{"isUse":true,"name":"快速导入","code":"import","icon":"ant-design:import-outlined","isDefault":true},{"isUse":true,"name":"快速导出","code":"export","icon":"ant-design:export-outlined","isDefault":true},{"isUse":true,"name":"打印","code":"print","icon":"ant-design:printer-outlined","isDefault":true},{"isUse":true,"name":"模板打印","code":"templateprint","icon":"ant-design:printer-outlined","isDefault":true},{"isUse":true,"name":"删除","code":"delete","icon":"ant-design:delete-outlined","isDefault":true}]);
//展示在列表内的按钮
const actionButtons = ref<string[]>(['view', 'edit', 'copyData', 'delete', 'startwork','flowRecord']);
const buttonConfigs = computed(()=>{
return filterButtonAuth(buttons.value);
})
const tableButtonConfig = computed(() => {
return buttonConfigs.value?.filter((x) => !actionButtons.value.includes(x.code));
});
const actionButtonConfig = computed(() => {
return buttonConfigs.value?.filter((x) => actionButtons.value.includes(x.code));
});
const btnEvent = {add : handleAdd,edit : handleEdit,refresh : handleRefresh,view : handleView,import : handleImport,export : handleExport,print : handlePrint,templateprint : handleTemplateprint,delete : handleDelete,}
const { currentRoute } = useRouter();
const router = useRouter();
const printMenuId = computed(() => currentRoute.value.meta.menuId as string);
const formIdComputedRef = ref();
formIdComputedRef.value = currentRoute.value.meta.formId
const schemaIdComputedRef = ref();
schemaIdComputedRef.value = currentRoute.value.meta.schemaId
const selectedKeys = ref<string[]>([]);
const selectedRowsData = ref<any[]>([]);
const [registerModal, { openModal }] = useModal();
const [registerImportModal, { openModal: openImportModal }] = useModal();
// 模板打印 入参参数
let printData: {
visible:boolean;
id: string;
code:string;
requestParamsConfigs: Array<InputParamItem>;
requestHeaderConfigs: Array<InputParamItem>;
requestBodyConfigs: Array<InputParamItem>;
} = reactive({
visible:false,
id: '',
code:'',
requestParamsConfigs: [],
requestHeaderConfigs: [],
requestBodyConfigs: [],
});
const formName='测试_极简模式';
const [registerTable, { reload, getRawDataSource, }] = useTable({
title: '' || (formName + '列表'),
api: getTestfrom3Page,
rowKey: 'id',
columns: customConfigColums,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: customSearchFormSchema,
fieldMapToTime: [],
showResetButton: false,
},
beforeFetch: (params) => {
return { ...params, FormId: formIdComputedRef.value, PK: 'id' };
},
afterFetch: (res) => {
tableRef.value.setToolBarWidth();
},
useSearchForm: true,
showTableSetting: true,
striped: false,
actionColumn: {
width: 160,
title: '操作',
dataIndex: 'action',
slots: { customRender: 'action' },
},
tableSetting: {
size: false,
setting: false,
},
customRow,
});
function dbClickRow(record) {
if (!actionButtonConfig?.value.some(element => element.code == 'view')) {
return;
}
const { processId, taskIds, schemaId } = record.workflowData || {};
if (taskIds && taskIds.length) {
router.push({
path: '/flow/' + schemaId + '/' + (processId || '') + '/approveFlow',
query: {
taskId: taskIds[0],
formName: formName,
formId:currentRoute.value.meta.formId
}
});
} else if (schemaId && !taskIds && processId) {
router.push({
path: '/flow/' + schemaId + '/' + processId + '/approveFlow',
query: {
readonly: 1,
taskId: '',
formName: formName,
formId:currentRoute.value.meta.formId
}
});
} else {
router.push({
path: '/form/Testfrom3/' + record.id + '/viewForm',
query: {
formPath: 'system/Testfrom3',
formName: formName,
formId:currentRoute.value.meta.formId
}
});
}
}
function buttonClick(code) {
if (code.includes(PrintButton.CODE)) {
printData.code = code;
}
btnEvent[code]();
}
function handleAdd() {
if (schemaIdComputedRef.value) {
router.push({
path: '/flow/' + schemaIdComputedRef.value + '/0/createFlow'
});
} else {
router.push({
path: '/form/Testfrom3/0/createForm',
query: {
formPath: 'system/Testfrom3',
formName: formName,
formId:currentRoute.value.meta.formId
}
});
}
}
function handleEdit(record: Recordable) {
router.push({
path: '/form/Testfrom3/' + record.id + '/updateForm',
query: {
formPath: 'system/Testfrom3',
formName: formName,
formId:currentRoute.value.meta.formId
}
});
}
function handleDelete(record: Recordable) {
deleteList([record.id]);
}
function deleteList(ids) {
Modal.confirm({
title: '提示信息',
icon: createVNode(ExclamationCircleOutlined),
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',
onOk() {
deleteTestfrom3(ids).then((_) => {
handleSuccess();
notification.success({
message: 'Tip',
description: t('删除成功!'),
});
});
},
onCancel() {},
});
}
async function handlePrint() {
const dataSource = Array.isArray(getRawDataSource())
? getRawDataSource()
: getRawDataSource().list;
const json = generateTableJson(filterColumns, dataSource);
const properties = filterColumns.map((item) => item.title);
printJS({
printable: json,
properties: properties,
type: 'json',
});
}
// 模板打印
async function handleTemplateprint() {
if (!selectedKeys.value.length) {
notification.warning({
message: t('提示'),
description: t('请选择数据'),
});
return;
}
if (selectedKeys.value.length > 1) {
notification.warning({
message: t('提示'),
description: t('只能选择一条数据进行操作'),
});
return;
}
let id = selectedKeys.value[0];
try {
const record = await getTestfrom3(id);
let res = await getPrintConfigInfo(printData.code, printMenuId.value);
if(res.enabledMark==null){
notification.warning({
message: t('提示'),
description: t('当前功能未绑定打印模板,请绑定后再进行模板打印。'),
});
return ;
}
if(res.enabledMark==0){
notification.warning({
message: t('提示'),
description: t('找不到打印模板,请联系管理员。'),
});
return ;
}
printData.id = res.schemaId;
if (res.apiConfig) {
let json = JSON.parse(res.apiConfig);
if (json.requestParamsConfigs && json.requestParamsConfigs.length > 0) {
printData.requestParamsConfigs = json.requestParamsConfigs.map((ele) => {
if (ele.config && record[ele.config] != undefined) {
ele.value = record[ele.config];
}
return ele;
});
}
if (json.requestHeaderConfigs && json.requestHeaderConfigs.length > 0) {
printData.requestHeaderConfigs = json.requestHeaderConfigs.map((ele) => {
if (ele.config && record[ele.config] != undefined) {
ele.value = record[ele.config];
}
return ele;
});
}
if (json.requestBodyConfigs && json.requestBodyConfigs.length > 0) {
printData.requestBodyConfigs = json.requestBodyConfigs.map((ele) => {
if (ele.config && record[ele.config] != undefined) {
ele.value = record[ele.config];
}
return ele;
});
}
printData.visible = true;
}else{
notification.warning({
message: t('提示'),
description: t('当前功能未绑定打印模板,请绑定后再进行模板打印。'),
});
}
} catch (error) {}
}
function onSelectChange(selectedRowKeys: [], selectedRows) {
selectedKeys.value = selectedRowKeys;
selectedRowsData.value = selectedRows;
}
function customRow(record: Recordable) {
return {
onClick: () => {
let selectedRowKeys = [...selectedKeys.value];
if (selectedRowKeys.indexOf(record.id) >= 0) {
let index = selectedRowKeys.indexOf(record.id);
selectedRowKeys.splice(index, 1);
} else {
selectedRowKeys.push(record.id);
}
selectedKeys.value = selectedRowKeys;
},
};
}
function handleRefresh() {
reload();
}
function handleSuccess() {
selectedKeys.value = [];
selectedRowsData.value = [];
reload();
}
function handleView(record: Recordable) {
dbClickRow(record);
}
async function handleExport() {
const res = await exportTestfrom3({ isTemplate: false });
downloadByData(
res.data,
'Testfrom3.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
}
function handleImport() {
openImportModal(true, {
title: '快速导入',
downLoadUrl:'/system/testfrom3/export',
});
}
function handleImportSuccess(){
reload()
}
onMounted(() => {
if (schemaIdComputedRef.value) {
bus.on(FLOW_PROCESSED, handleRefresh);
bus.on(CREATE_FLOW, handleRefresh);
} else {
bus.on(FORM_LIST_MODIFIED, handleRefresh);
}
// 合并渲染覆盖配置中的列表配置,包括展示字段配置、搜索字段配置、按钮配置
mergeCustomListRenderConfig();
});
onUnmounted(() => {
if (schemaIdComputedRef.value) {
bus.off(FLOW_PROCESSED, handleRefresh);
bus.off(CREATE_FLOW, handleRefresh);
} else {
bus.off(FORM_LIST_MODIFIED, handleRefresh);
}
});
function getActions(record: Recordable):ActionItem[] {
const actionsList: ActionItem[] = actionButtonConfig.value?.map((button) => {
if (!record.workflowData?.processId) {
return {
icon: button?.icon,
tooltip: button?.name,
color: button.code === 'delete' ? 'error' : undefined,
onClick: btnEvent[button.code].bind(null, record),
};
} else {
if (button.code === 'view') {
return {
icon: button?.icon,
tooltip: button?.name,
onClick: btnEvent[button.code].bind(null, record),
};
} else {
return {};
}
}
});
return actionsList;
}
async function mergeCustomListRenderConfig(){
if (formConfig.useCustomConfig) {
let formId=currentRoute.value.meta.formId;
//1.合并展示字段配置
let cols= await mergeColumns(customConfigColums.value,formId);
customConfigColums.value=cols;
//2.合并搜索字段配置
let sFormSchema= await mergeSearchFormSchema(customSearchFormSchema.value,formId);
customSearchFormSchema.value=sFormSchema;
//3.合并按钮配置
let btns= await mergeButtons(buttons.value,formId);
buttons.value=btns;
}
};
</script>
<style lang="less" scoped>
:deep(.ant-table-selection-col) {
width: 50px;
}
.show{
display: flex;
}
.hide{
display: none !important;
}
</style>

View File

@ -0,0 +1,77 @@
export const items=[
/* {
code:"租户",
name:'租户',
ref:"tenantRef",
}, */
{
code:'角色',
name:'角色',
ref:"roleRef",
},
{
code:'岗位',
name:'岗位' ,
ref:"postRef",
},
{
code:'用户组',
name:'用户组',
ref:"userGroupRef",
},
{
code:'菜单',
name:'菜单',
ref:"menuRef",
},
{
code:'表单',
name:'表单',
ref:"formRef",
},
{
code:'流程定义',
name:'流程定义',
ref:"workflowRef",
},
{
code:'系统配置',
name:'系统配置',
ref:"systemConfigRef",
},
{
checked:false,
code:'数据字典',
name:'数据字典',
ref:"dictionaryRef",
},
{
checked:false,
code:'自动编码',
name:'自动编码',
ref:"codeRuleRef",
},
{
code:'桌面设计',
name:'桌面设计',
ref:"desktopRef",
},
{
checked:false,
code:'角色-菜单授权',
name:'权限:角色-菜单授权(菜单、按钮、列表、表单)',
ref:"roleMenuAuthRef",
},
{
code:'角色-自定义接口授权',
name:'权限:角色-自定义接口授权',
ref:"roleInterfaceAuthRef",
},
{
code:'租户-菜单授权',
name:'权限:租户-菜单授权',
ref:"tenantMenuAuthRef",
}
]

View File

@ -0,0 +1,239 @@
<template>
<BasicModal @register="registerModal" v-bind="$attrs" wrapClassName="data-migration-modal" >
<template #title>
<a-steps :current="current">
<a-step
v-for="step in steps"
:title="t(step.name)"
/>
</a-steps>
<div class="btn-box">
<a-button type="primary" @click="handleStepPrev" :disabled="current == 0">{{t('上一步')}}</a-button>
<a-button type="primary" @click="handleStepNext" :disabled="current>=steps.length-1">{{t('下一步')}}</a-button>
<a-button type="primary" @click="handleExport" :disabled="current<steps.length-1">{{t('导出')}}</a-button>
<a-button type="primary" danger @click="handleClose">{{ t('关闭') }}</a-button>
</div>
</template>
<div class="step-container">
<Tenant ref="tenantRef" v-show="showIf('租户')"/>
<Role ref="roleRef" v-show="showIf('角色')"/>
<Post ref="postRef" v-show="showIf('岗位')"/>
<UserGroup ref="userGroupRef" v-show="showIf('用户组')"/>
<Menu ref="menuRef" v-show="showIf('菜单')"/>
<Form ref="formRef" v-show="showIf('表单')"/>
<Workflow ref="workflowRef" v-show="showIf('流程定义')"/>
<SystemConfig ref="systemConfigRef" v-show="showIf('系统配置')"/>
<Dictionary ref="dictionaryRef" v-show="showIf('数据字典')"/>
<CodeRule ref="codeRuleRef" v-show="showIf('自动编码')"/>
<Desktop ref="desktopRef" v-show="showIf('桌面设计')"/>
<!-- <span ref="roleMenuAuthRef" v-show="showIf('角色-菜单授权')" >角色-菜单授权 选择列表</span> -->
<RoleMenuAuth ref="roleMenuAuthRef" v-show="showIf('角色-菜单授权')"/>
<InterfaceAuth ref="roleInterfaceAuthRef" v-show="showIf('角色-自定义接口授权')"/>
<TenantAuthorize ref="tenantMenuAuthRef" v-show="showIf('租户-菜单授权')"/>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref,unref,provide,Ref,onMounted} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import {isFunction } from 'lodash-es';
import { isNumber } from '/@/utils/is';
import { downloadByData } from '/@/utils/file/download';
import { dateUtil } from '/@/utils/dateUtil';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { exportDatas,downloadDatas} from '/@/api/system/dataMigration';
import Tenant from './components/Tenant.vue';
import Role from './components/Role.vue';
import Post from './components/Post.vue';
import Menu from './components/Menu.vue';
import Form from './components/Form.vue';
import Workflow from './components/Workflow.vue';
import SystemConfig from './components/SystemConfig.vue';
import Dictionary from './components/Dictionary.vue';
import CodeRule from './components/CodeRule.vue';
import Desktop from './components/Desktop.vue';
import UserGroup from './components/UserGroup.vue';
import RoleMenuAuth from './components/RoleMenuAuth.vue';
import InterfaceAuth from './components/InterfaceAuth.vue';
import TenantAuthorize from './components/TenantAuthorize.vue';
const { t } = useI18n();
const current = ref(0);
const steps=ref([]);
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
steps.value=data.items;
current.value=0;
selectedModel.value={};
initStep(0);
setModalProps({
confirmLoading: false,
canFullscreen: false,
defaultFullscreen: true,
destroyOnClose: true,
draggable: false,
showOkBtn: false,
showCancelBtn: false,
footer: null,
closable: false,
});
});
const tenantRef=ref();
const roleRef=ref();
const postRef= ref();
const userGroupRef= ref();
const menuRef = ref();
const formRef=ref();
const workflowRef=ref();
const systemConfigRef= ref();
const dictionaryRef= ref();
const codeRuleRef= ref();
const desktopRef= ref();
const roleMenuAuthRef= ref();
const roleInterfaceAuthRef= ref();
const tenantMenuAuthRef= ref();
const refs = {
tenantRef,
roleRef,
postRef,
userGroupRef,
menuRef,
formRef,
workflowRef,
systemConfigRef,
dictionaryRef,
codeRuleRef,
desktopRef,
roleMenuAuthRef,
roleInterfaceAuthRef,
tenantMenuAuthRef
};
const selectedModel: Ref<{
menu?:{
menuIds: number[],
buttonIds: number[],
columnIds:number[],
formIds:number[]
},
tenant?:{
tenantIds:number[]
}
}> = ref({});
provide('selectedModel', selectedModel);
function handleClose() {
closeModal();
}
function showIf(itemType:string){
let currentStep=getStep('current');
if(currentStep==null){
return false;
}
return currentStep.code==itemType;
}
//上一步
function handleStepPrev() {
completeStep(current.value);
current.value--;
initStep(current.value);
}
//下一步
async function handleStepNext() {
completeStep(current.value);
current.value++;
initStep(current.value);
}
//导出
async function handleExport(){
completeStep(current.value);
let result= await exportDatas({
exportType:'exportSelected',
...unref(selectedModel)
});
if(result){
const fileName='data-mg-'+dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
const res = await downloadDatas({uuid:result});
downloadByData(
res.data,
fileName+".zip"
);
}
handleClose();
}
function getStep(key:string|number){
let stepList=unref(steps);
if(!stepList||stepList.length==0){
return null;
}
let step=null;
if(key=='current'){
step=unref(stepList[current.value]);
}else if(key=='prev'){
step=unref(stepList[current.value-1]);
}else if(key=='next'){
step=unref(stepList[current.value+1]);
}else if(isNumber(key)&&key>=0&&key<stepList.length){
step=unref(stepList[key]);
}
return step;
}
function initStep(key:string|number){
let stepRef = refs[getStep(key).ref].value;
stepRef.initStep&& stepRef.initStep();
};
function completeStep(key:string|number){
let stepRef = refs[getStep(key).ref].value;
stepRef.completeStep&& stepRef.completeStep();
};
</script>
<style lang="less">
.data-migration-modal{
.step-container {
max-height: 90vh;
overflow-y: auto;
padding-bottom: 16px;
}
.ant-modal-header{
height: 100px;
}
.ant-steps{
display: flex;
flex-wrap: wrap;
}
.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item {
padding-left:16px!important;
margin-bottom: 10px;
flex: 1 1 auto;
}
}
</style>
<style lang="less" scoped>
.btn-box {
height: 100px;
position: absolute;
right: 10px;
top:60px;
:deep(.ant-btn) {
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection"/>
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getCodeList } from '/@/api/system/code';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const columns = ref([
{
title: t('编码编号'),
dataIndex: 'code',
width: 120,
sorter: true,
align: 'left',
},
{
title: t('编码名称'),
dataIndex: 'name',
width: 120,
sorter: true,
align: 'left',
},
{
title: t('当前流水号'),
dataIndex: 'currentNumber',
width: 120,
align: 'left',
sorter: true,
},
{
title: t('编码说明'),
dataIndex: 'description',
width: 180,
align: 'left',
sorter: true,
},
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getCodeList({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
currentNumber: item.currentNumber,
description: item.description
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let codeRule = selectedModel.value.codeRule || (selectedModel.value.codeRule = {});
codeRule.codeRuleIds=selectedKeys.value;
}else{
selectedModel.value.codeRule=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,77 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getPageList } from '/@/api/desktop';
const columns = ref([
{
title: '编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getPageList({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let deskTop = selectedModel.value.deskTop || (selectedModel.value.deskTop = {});
deskTop.desktopIds=selectedKeys.value;
}else{
selectedModel.value.deskTop=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,241 @@
<template>
<PageWrapper v-if="inited" dense contentFullHeight fixedHeight contentClass="flex">
<BasicTable
@register="registerTableItem"
@row-click="handleRowClick"
class="w-2/5 xl:w-2/5 mr-2"
>
<!-- <template #action="{ record }">
<a-checkbox v-model:checked="checkedItems[record.id]"/>
</template> -->
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-checkbox v-model:checked="checkedItems[record.id]"/>
</template>
</template>
</BasicTable>
<BasicTable
@register="registerTableDetail"
class="w-3/5 xl:w-3/5"
:rowSelection="rowSelectionDetail"
>
</BasicTable>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref,inject,reactive, } from 'vue';
import { BasicTable, useTable, FormSchema, BasicColumn } from '/@/components/Table';
import {
getDicItemPageList,
getDicDetailPageList
} from '/@/api/system/dic';
import { PageWrapper } from '/@/components/Page';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const inited=ref(false);
const checkedItems=ref({});
const checkedDetails=ref({});
const selectedModel = inject('selectedModel');
const searchFormSchemaItem: FormSchema[] = [
{
field: 'name',
label: t('项目名'),
component: 'Input',
},
{
field: 'code',
label: t('编码'),
component: 'Input',
},
];
const itemColumns: BasicColumn[] = [
{
title: t('选择'),
dataIndex: 'operation',
width: 50,
align: 'left',
},
{
title: t('项目名'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('编号'),
dataIndex: 'code',
width: 120,
align: 'left',
},
{
title: t('备注'),
dataIndex: 'remark',
align: 'left',
},
];
const searchFormSchemaDetail: FormSchema[] = [
{
field: 'name',
label: t('字典名'),
component: 'Input',
},
{
field: 'code',
label: t('编码'),
component: 'Input',
},
];
const detailColumns: BasicColumn[] = [
{
title: t('字典名'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('编号'),
dataIndex: 'code',
width: 120,
align: 'left',
},
{
title: t('值'),
dataIndex: 'value',
width: 120,
align: 'left',
},
{
title: t('备注'),
align: 'left',
dataIndex: 'remark',
},
];
// Row selection for detail table
const selectedRowKeysDetail = ref<string[]>([]);
const rowSelectionDetail = reactive<any> ({
selectedRowKeys: selectedRowKeysDetail,
onChange: (selectedRowKeys: string[]) => {
selectedRowKeysDetail.value = selectedRowKeys;
syncCheckedDetails();
},
onSelectAll: (selected: boolean, selectedRows: any[], changeRows: any[]) => {
const ids = (changeRows || selectedRows || []).map((r: any) => String(r.id));
if (selected) {
selectedRowKeysDetail.value = Array.from(new Set([...selectedRowKeysDetail.value, ...ids]));
} else {
selectedRowKeysDetail.value = selectedRowKeysDetail.value.filter((k) => !ids.includes(k));
}
syncCheckedDetails();
},
});
const { notification } = useMessage();
const selectItemId = ref('');
const [registerTableItem, { reload: itemReload }] = useTable({
title: t('数据字典项'),
api: getDicItemPageList,
rowKey: 'id',
columns: itemColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaItem,
showResetButton: false,
},
useSearchForm: true,
showTableSetting: true,
striped: false,
/* actionColumn: {
width: 80,
title: t('操作'),
dataIndex: 'action',
slots: { customRender: 'action' },
}, */
tableSetting: {
size: false,
setting: false,
},
});
const [registerTableDetail, { reload: detailReload }] = useTable({
title: t('数据字典详情'),
api: getDicDetailPageList,
rowKey: 'id',
columns: detailColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaDetail,
showResetButton: false,
},
beforeFetch: (params) => {
return { ...params, itemId: selectItemId.value };
},
useSearchForm: true,
showTableSetting: true,
striped: false,
tableSetting: {
size: false,
setting: false,
},
});
function handleRowClick(record: any) {
selectItemId.value = record.id;
detailReload();
}
function onSelectChange(rowKey: string[]) {
selectItemId.value = rowKey[0];
if(selectItemId.value){
detailReload();
}
}
function syncCheckedDetails() {
const map: Record<string, boolean> = {};
selectedRowKeysDetail.value.forEach((key) => {
map[key] = true;
});
checkedDetails.value = map;
}
async function initStep() {
inited.value=true;
}
function completeStep(){
let items=checkedItems.value;
let details=checkedDetails.value;
let checkItemIds = Object.keys(items).filter(key => items[key] === true);
let checkDetailIds = Object.keys(details).filter(key => details[key] === true);
if((checkItemIds&&checkItemIds.length>0)||(checkDetailIds&&checkDetailIds.length>0)){
let dictionary = selectedModel.value.dictionary || (selectedModel.value.dictionary = {});
dictionary.itemIds=checkItemIds;
dictionary.detailIds=checkDetailIds;
}else{
selectedModel.value.dictionary=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,94 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getFormTemplatePage} from '/@/api/form/design';
const columns = ref([
{
title: '表单id',
key:'id',
dataIndex:'id',
align: 'left',
sorter: true,
},
{
title: '表单名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '模板类型',
dataIndex: '',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.formDesignType===0) {
return '数据优先模板';
}else if(record.formDesignType===1){
return '界面优先模板';
}else if(record.formDesignType===2){
return '简易模板';
}
return '';
}
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getFormTemplatePage({limit:1,size:99,type:0,category:''});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
id: item.id,
name: item.name,
formDesignType: item.formDesignType
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let formTemplate = selectedModel.value.formTemplate || (selectedModel.value.formTemplate = {});
formTemplate.formTemplateIds=selectedKeys.value;
}else{
selectedModel.value.formTemplate=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,279 @@
<template>
<PageWrapper v-if="inited" dense contentFullHeight fixedHeight contentClass="flex">
<BasicTable
@register="registerTableRole"
@row-click="handleRowClick"
class="w-2/5 xl:w-2/5 mr-2"
>
<!-- <template #action="{ record }">
<a-checkbox v-model:checked="checkedRoles[record.id]"/>
</template> -->
</BasicTable>
<BasicTable @register="registerInterface" class="w-3/5 xl:w-3/5" ref="interfaceRef">
<!-- <template #action="{ record }">
<a-checkbox v-model:checked="checkedInterfaces[record.roleId+'_'+record.id]"/>
</template> -->
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-checkbox v-model:checked="checkedInterfaces[record.roleId+'_'+record.id]"
@change="onParentCheckChange(record, $event)"/>
</template>
</template>
</BasicTable>
</PageWrapper>
</template>
<script lang="ts" setup>
import {ref,inject,nextTick,unref } from 'vue';
import { BasicTable, useTable, TableAction, FormSchema, BasicColumn } from '/@/components/Table';
import {getRolePageList,getRoleInterfaceList} from '/@/api/system/role';
import {getAllInterface } from '/@/api/system/interface';
import { PageWrapper } from '/@/components/Page';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDrawer } from '/@/components/Drawer';
import { cloneDeep } from 'lodash-es';
const { t } = useI18n();
const inited=ref(false);
const checkedRoles=ref({});
const checkedInterfaces=ref({});
const allInterfacesTree=ref([]);
const selectedModel = inject('selectedModel');
const searchFormSchemaRole: FormSchema[] = [
{
field: 'name',
label: t('角色名称'),
component: 'Input',
},
{
field: 'code',
label: t('角色编码'),
component: 'Input',
},
];
const roleColumns: BasicColumn[] = [
{
title: t('角色名称'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('角色编码'),
dataIndex: 'code',
width: 120,
align: 'left',
},
{
title: '状态',
dataIndex: 'enabledMark',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.enabledMark===1) {
return '启用';
}else{
return '禁用';
}
}
}
];
const searchFormSchemaInterface: FormSchema[] = [
{
field: 'name',
label: t('名称'),
component: 'Input',
}
];
const interfaceColumns: BasicColumn[] = [
{
title: t('选择'),
dataIndex: 'operation',
width: 50,
align: 'left',
},
{
title: t('接口名称'),
dataIndex: 'name',
width: 120,
align: 'left',
}
];
const { notification } = useMessage();
const selectRoleId = ref('');
const [registerTableRole, { reload: roleReload }] = useTable({
title: t('角色'),
api: getRolePageList,
rowKey: 'id',
columns: roleColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaRole,
showResetButton: false,
},
rowSelection: {
type: 'radio',
onChange: onSelectChange,
},
useSearchForm: true,
showTableSetting: true,
striped: false,
/* actionColumn: {
width: 80,
title: t('操作'),
dataIndex: 'action',
slots: { customRender: 'action' },
}, */
tableSetting: {
size: false,
setting: false,
},
});
const [registerInterface, {reload: interfaceReload, expandAll, collapseAll }] = useTable({
title: '自定义接口',
isTreeTable: true,
beforeFetch: (params) => {
return { ...params, roleId: selectRoleId.value };
},
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaInterface,
showResetButton: false,
},
useSearchForm: true,
columns:interfaceColumns,
api:getAuthInterface,
rowKey: 'id',
/* actionColumn: {
width: 80,
title: t('操作'),
dataIndex: 'action',
slots: { customRender: 'action' },
}, */
});
function handleRowClick(record: any) {
if(selectRoleId.value===record.id){
return;
}
selectRoleId.value = record.id;
interfaceReload();
}
function onSelectChange(rowKey: string[]) {
if(selectRoleId.value== rowKey[0]){
return;
}
if(rowKey[0]){
selectRoleId.value =rowKey[0] ;
interfaceReload();
}
}
function setChildrenChecked(record, checked) {
if (record.children && record.children.length > 0) {
record.children.forEach(child => {
checkedInterfaces.value[child.roleId + '_' + child.id] = checked;
setChildrenChecked(child, checked);
});
}
}
function onParentCheckChange(record, e) {
const checked = e.target.checked;
setChildrenChecked(record, checked);
}
function getAuthTree(authTree,allTree,authList,roleId) {
let result=false;
for (let i = 0; i < allTree.length; i++) {
let o = allTree[i];
let include=false;
let childrenInclude=false;
if (authList.includes(o.id)) {
include=true;
}
let children=[];
if (o.children?.length > 0) {
childrenInclude=getAuthTree(children, o.children, authList,roleId);
}
if(include===true||childrenInclude===true){
let node=cloneDeep(o);
node.children=children;
node.roleId=roleId;
authTree.push(node);
result=true;
}
}
return result;
}
async function getAuthInterface(params, mode){
if(!params.roleId){
return [];
}
let allTree=allInterfacesTree.value;
if(!allTree||allTree.length==0){
allTree=allInterfacesTree.value=await getAllInterface();
}
let authList=await getRoleInterfaceList(params,mode);
let authTree=[];
getAuthTree(authTree,allTree,authList,params.roleId);
expandAll();
return authTree;
}
async function initStep() {
inited.value=true;
}
function completeStep(){
let interfaces=checkedInterfaces.value;
let checkIds = Object.keys(interfaces).filter(key => interfaces[key] === true);
if(checkIds&&checkIds.length>0){
let map={};
for(let i=0;i<checkIds.length;i++){
let splits=checkIds[i].split('_');
let roleId=splits[0];
let interfaceId=splits[1];
let interfaceList=map[roleId]||(map[roleId]=[]);
interfaceList.push(interfaceId);
}
let interfaceAuth = selectedModel.value.interfaceAuth || (selectedModel.value.interfaceAuth = {});
interfaceAuth.interfaceAuthIdsMap=map;
}else{
selectedModel.value.interfaceAuth=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,286 @@
<template>
<a-row :gutter="[16, 16]" class="h-full">
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('菜单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="menuData"
checkable
ref="menuRef"
@check="handleTreeSelect"
:fieldNames="{ title: 'title', key: 'id' }"
>
<template #title="{ title, systemName, parentId }">
<div v-if="parentId == 0">{{ title }} {{ systemName }}</div>
<div v-else>{{ title }}</div>
</template>
</BasicTree>
</div>
</a-col>
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('按钮')
}}</div>
<div class="treebox">
<BasicTree
:treeData="buttonSelectData"
checkable
ref="ButtonRef"
@check="handleButtonSelect"
:fieldNames="{ title: 'title', key: 'id' }"
/>
</div>
</a-col>
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('字段')
}}</div>
<div class="treebox">
<BasicTree
:treeData="columnSelectData"
checkable
ref="ColumnRef"
@check="handleColumnSelect"
:fieldNames="{ title: 'title', key: 'id' }"
/>
</div>
</a-col>
<a-col :span="6">
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('表单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="fieldSelectData"
checkable
ref="FieldRef"
@check="handleFieldSelect"
:fieldNames="{ title: 'title', key: 'id' }"
/>
</div>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import {ref,unref,inject} from 'vue';
import { TreeItem,BasicTree,TreeActionType} from '/@/components/Tree';
import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
import { getMenuSimpleTree } from '/@/api/system/menu';
import { getMenuButtonList, getMenuColumnList, getMenuFieldList } from '/@/api/system/menuButton';
const { t } = useI18n();
const menuRef = ref<Nullable<TreeActionType>>(null);
const ButtonRef = ref<Nullable<TreeActionType>>(null);
const ColumnRef = ref<Nullable<TreeActionType>>(null);
const FieldRef = ref<Nullable<TreeActionType>>(null);
const menuData = ref<TreeItem[]>([]);
const buttonData = ref<TreeItem[]>([]);
const columnData = ref<TreeItem[]>([]);
const fieldData = ref<TreeItem[]>([]);
const buttonSelectData = ref<TreeItem[]>([]);
const columnSelectData = ref<TreeItem[]>([]);
const fieldSelectData = ref<TreeItem[]>([]);
const menuKeys = ref<string[]>([]);
const btnKeys = ref<string[]>([]);
const colKeys = ref<string[]>([]);
const fieldKeys = ref<string[]>([]);
const btnFilterKeys = ref<string[]>([]);
const colFilterKeys = ref<string[]>([]);
const fieldFilterKeys = ref<string[]>([]);
const selectedModel = inject('selectedModel');
const inited=ref(false);
async function initStep() {
try {
if(inited.value){
return;
}
menuData.value = await getMenuSimpleTree();
buttonData.value = await getMenuButtonList();
columnData.value = await getMenuColumnList();
fieldData.value = await getMenuFieldList();
inited.value=true;
}catch (e) {
console.error(e);
}
}
function handleTreeSelect(keys, e) {
//keys 所有全选中的key
//e.halfCheckedKeys 所有半选中的
//如果没有半选 就证明全部全选 直接使用keys
//如果有半选需要 使用keys + e.halfCheckedKeys
const menuSelect: string[] = [...e.halfCheckedKeys, ...keys] as String[];
//选择菜单的时候。对应后面的button column field 只进行增量添加
incrementTree(menuSelect);
menuKeys.value = menuSelect;
}
function incrementTree(addMenuSelect) {
/* btnFilterKeys.value = [];
colFilterKeys.value = [];
fieldFilterKeys.value = []; */
// 确保未选中菜单的子节点不会丢失
findMenuTree(buttonSelectData.value, menuData.value, Array.from(addMenuSelect), 'button');
findMenuTree(columnSelectData.value, menuData.value, Array.from(addMenuSelect), 'column');
findMenuTree(fieldSelectData.value, menuData.value, Array.from(addMenuSelect), 'field');
// 更新按钮、字段、表单的选中状态
btnKeys.value = Array.from(new Set([...btnKeys.value, ...addMenuSelect]));
colKeys.value = Array.from(new Set([...colKeys.value, ...addMenuSelect]));
fieldKeys.value = Array.from(new Set([...fieldKeys.value, ...addMenuSelect]));
// 扩展树的展开状态
unref(ButtonRef).setExpandedKeys(Array.from(addMenuSelect));
unref(ColumnRef).setExpandedKeys(Array.from(addMenuSelect));
unref(FieldRef).setExpandedKeys(Array.from(addMenuSelect));
}
function findMenuTree(menuSelectTree, treeData, keys, type) {
for (let i = 0; i < treeData.length; i++) {
let o = cloneDeep(treeData[i]);
if (keys.includes(o.id)) {
let hadData = false;
let index = 0;
for (let j = 0; j < menuSelectTree.length; j++) {
if (menuSelectTree[j].id == o.id) {
hadData = true;
index = j;
break;
}
}
if (!hadData) {
o.children = [];
menuSelectTree.push(o);
} else {
o = menuSelectTree[index];
}
if (treeData[i].children?.length > 0) {
findMenuTree(o.children, treeData[i].children, keys, type);
} else {
let list =
type == 'button'
? buttonData.value
: type == 'column'
? columnData.value
: fieldData.value;
o.children = getAuthData(list, o.id, type);
}
}
}
}
function getAuthData(list, id, type) {
let arr: TreeItem[] =list.filter((o) => {
return o.menuId == id;
});
arr.forEach((o) => {
o.parentId = o.menuId;
if (type == 'button') {
btnFilterKeys.value.push(o.id);
} else if (type == 'column') {
colFilterKeys.value.push(o.id);
} else {
if (o.children && o.children.length > 0) {
o.children?.forEach((k) => {
fieldFilterKeys.value.push(k.id);
});
}
fieldFilterKeys.value.push(o.id);
}
});
return arr;
}
function handleButtonSelect(keys, e) {
btnKeys.value = [...e.halfCheckedKeys, ...keys];
}
function handleColumnSelect(keys, e) {
colKeys.value = [...e.halfCheckedKeys, ...keys];
}
function handleFieldSelect(keys, e) {
fieldKeys.value = [...e.halfCheckedKeys, ...keys];
}
function completeStep(){
let menuIds=menuKeys.value;
let buttonIds=btnKeys.value.filter((o) =>{ return btnFilterKeys.value.includes(o)});
let columnIds=colKeys.value.filter((o) => {return colFilterKeys.value.includes(o)});
let formIds=fieldKeys.value.filter((o) => {return fieldFilterKeys.value.includes(o)});
if((menuIds&&menuIds.length>0)||(buttonIds&&buttonIds.length>0)
||(columnIds&&columnIds.length>0)||(formIds&&formIds.length>0)){
let menu = selectedModel.value.menu || (selectedModel.value.menu = {});
menu.menuIds=menuIds;
menu.buttonIds=buttonIds;
menu.columnIds=columnIds;
menu.formIds=formIds;
}else{
selectedModel.value.menu=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>
<style lang="less">
.autherizeDialog {
.ant-modal {
top: 20px;
height: calc(100% - 40px) !important;
.ant-modal-content {
height: 100%;
.ant-modal-body {
height: calc(100% - 120px) !important;
.scrollbar__view {
height: 100%;
& > div {
height: 100%;
max-height: none !important;
.ant-col {
height: 100%;
.treebox {
height: calc(100% - 42px);
overflow: auto;
padding-right: 15px;
margin-bottom: 10px;
border-right: solid 1px #ccc;
.vben-tree {
height: auto;
}
}
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,236 @@
<template>
<PageWrapper v-if="inited" dense contentFullHeight fixedHeight contentClass="flex">
<div class="w-1/3 xl:w-1/4">
<DeptTree @select="handleSelect" />
</div>
<div class="w-2/3 xl:w-3/4">
<BasicTable @register="registerTable">
</BasicTable>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { defineComponent, h, ref, inject, reactive, watch } from 'vue';
import { BasicTable, useTable, TableAction, FormSchema, BasicColumn } from '/@/components/Table';
import {getTreePostList} from '/@/api/system/post';
import { SelectDepartment } from '/@/components/SelectOrganizational/index';
import { Tag } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useModal } from '/@/components/Modal';
import { PageWrapper } from '/@/components/Page';
import DeptTree from '/@/views/system/user/components/DeptTree.vue';
import { UserInfo } from '/#/store';
import { useUserStore } from '/@/store/modules/user';
import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
const { t } = useI18n();
const inited=ref(false);
const checkedPosts=ref<Record<string, boolean>>({});
const selectedModel = inject('selectedModel');
const columns: BasicColumn[] = [
{
title: t('岗位名称'),
dataIndex: 'name',
align: 'left',
sorter: true,
},
{
title: t('岗位编码'),
dataIndex: 'code',
align: 'left',
sorter: true,
},
{
title: t('状态'),
dataIndex: 'enabledMark',
width: 80,
align: 'left',
sorter: true,
customRender: ({ record }) => {
const status = record.enabledMark;
const enable = ~~status === 1;
const color = enable ? 'green' : 'red';
const text = enable ? t('启用') : t('停用');
return h(Tag, { color: color }, () => text);
},
},
{
title: t('备注'),
dataIndex: 'remark',
sorter: true,
align: 'left',
},
];
const searchFormSchema: FormSchema[] = [
{
field: 'name',
label: t('岗位名称'),
component: 'Input',
colProps: { lg: 8, md: 12, sm: 12 },
},
{
field: 'code',
label: t('岗位编码'),
component: 'Input',
colProps: { lg: 8, md: 12, sm: 12 },
},
{
field: 'enabledMark',
label: t('状态'),
component: 'Select',
componentProps: {
options: [
{ label: t('启用'), value: 1 },
{ label: t('停用'), value: 0 },
],
},
colProps: { lg: 8, md: 12, sm: 12 },
},
];
const { notification } = useMessage();
const selectDeptId = ref('');
const postId = ref('');
const selectedUserIds = ref<string[]>([]);
const visible = ref<boolean>(false);
const [registerModal, { openModal }] = useModal();
// --- 新增:用于树形表格多选(父选中 -> 子也选中) ---
const selectedRowKeys = ref<string[]>([]);
const rowSelection = reactive<any>({
type: 'checkbox',
selectedRowKeys: selectedRowKeys.value,
// onChange 来自 ant table会在任何选择变化时触发
onChange: (keys: (string|number)[]) => {
selectedRowKeys.value = (keys || []).map(k => String(k));
syncCheckedPosts();
},
// onSelect 单条选择(可以用于父级级联选中子级)
onSelect: (record: any, selected: boolean) => {
const data = getDataSource ? getDataSource() : [];
const node = findNode(data, String(record.id));
const ids = node ? collectIds(node) : [String(record.id)];
if (selected) {
selectedRowKeys.value = Array.from(new Set([...selectedRowKeys.value, ...ids]));
} else {
selectedRowKeys.value = selectedRowKeys.value.filter(k => !ids.includes(k));
}
// 把 reactive 对象的 selectedRowKeys 同步ant table 读取该字段)
rowSelection.selectedRowKeys = selectedRowKeys.value;
syncCheckedPosts();
},
// 全选/取消全选
onSelectAll: (selected: boolean, selectedRows: any[], changeRows: any[]) => {
// changeRows 通常是当前页被切换选中状态的行,使用它来只影响当前页
const pageIds = (changeRows || selectedRows || []).map((r: any) => String(r.id));
if (selected) {
// 仅将当前页的 id 加入已选集合(保留跨页已选)
selectedRowKeys.value = Array.from(new Set([...selectedRowKeys.value, ...pageIds]));
} else {
// 仅移除当前页的 id不清空其它页的已选
selectedRowKeys.value = selectedRowKeys.value.filter((k) => !pageIds.includes(k));
}
rowSelection.selectedRowKeys = selectedRowKeys.value;
syncCheckedPosts();
},
});
// helper: 在 tree data 中查找节点
function findNode(list: any[], id: string): any | null {
for (const n of list || []) {
if (String(n.id) === id) return n;
const found = findNode(n.children || [], id);
if (found) return found;
}
return null;
}
// helper: 收集节点以及所有子孙 id字符串形式
function collectIds(node: any, out: string[] = []) {
out.push(String(node.id));
(node.children || []).forEach((c: any) => collectIds(c, out));
return out;
}
// helper: 返回所有节点 id平展
function flattenAllIds(list: any[], out: string[] = []) {
for (const n of list || []) {
out.push(String(n.id));
if (n.children && n.children.length) flattenAllIds(n.children, out);
}
return out;
}
function syncCheckedPosts() {
const map: Record<string, boolean> = {};
(selectedRowKeys.value || []).forEach(k => (map[String(k)] = true));
checkedPosts.value = map;
}
// --- 使用 useTable 时将 rowSelection 传入(支持父选中级联子级) ---
const [registerTable, { reload, getDataSource }] = useTable({
title: t('岗位列表'),
api: getTreePostList,
columns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchema,
actionColOptions: { span: 16 },
showResetButton: false,
},
beforeFetch: (params) => {
//发送请求默认新增 左边树结构所选机构id
return { ...params, deptId: selectDeptId.value };
},
rowKey: 'id',
pagination: {
pageSize: 20,
},
striped: false,
useSearchForm: true,
showTableSetting: true,
showIndexColumn: false,
defaultExpandAllRows: true,
// 把我们准备好的 rowSelection 对象传入
rowSelection,
tableSetting: {
size: false,
setting: false,
},
});
function handleSelect(deptId = '') {
selectDeptId.value = deptId;
reload({ searchInfo: { deptId: deptId } });
}
async function initStep() {
inited.value=true;
}
function completeStep(){
let posts=checkedPosts.value;
let checkPostIds = Object.keys(posts).filter(key => posts[key] === true);
if(checkPostIds&&checkPostIds.length>0){
let post = selectedModel.value.post || (selectedModel.value.post = {});
post.postIds=checkPostIds;
}else{
selectedModel.value.post=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,91 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getRolePageList} from '/@/api/system/role';
const columns = ref([
{
title: '角色名',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '角色编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '状态',
dataIndex: 'enabledMark',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.enabledMark===1) {
return '启用';
}else{
return '禁用';
}
}
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getRolePageList({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
enabledMark: item.enabledMark
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let role = selectedModel.value.role || (selectedModel.value.role = {});
role.roleIds=selectedKeys.value;
}else{
selectedModel.value.role=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,423 @@
<template>
<div class="role-menu-auth">
<a-row :gutter="[16, 16]" class="h-full">
<a-col :span="8" >
<BasicTable
@register="registerTableRole"
@row-click="handleRowClick"
>
<template #action="{ record }">
</template>
</BasicTable>
</a-col>
<a-col :span="4" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('菜单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="menuAuthData"
checkable
ref="menuRef"
@check="handleMenuSelect"
:fieldNames="{ title: 'title', key: 'roleObjectId' }"
>
<template #title="{ title, systemName, parentId }">
<div v-if="parentId == 0">{{ title }} {{ systemName }}</div>
<div v-else>{{ title }}</div>
</template>
</BasicTree>
</div>
</a-col>
<a-col :span="4" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('按钮')
}}</div>
<div class="treebox">
<BasicTree
:treeData="buttonAuthData"
checkable
ref="buttonRef"
@check="handleButtonSelect"
:fieldNames="{ title: 'title', key: 'roleObjectId' }"
/>
</div>
</a-col>
<a-col :span="4" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('字段')
}}</div>
<div class="treebox">
<BasicTree
:treeData="columnAuthData"
checkable
ref="columnRef"
@check="handleColumnSelect"
:fieldNames="{ title: 'title', key: 'roleObjectId' }"
/>
</div>
</a-col>
<a-col :span="4">
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('表单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="fieldAuthData"
checkable
ref="fieldRef"
@check="handleFieldSelect"
:fieldNames="{ title: 'title', key: 'roleObjectId' }"
/>
</div>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import {ref,unref,inject,nextTick} from 'vue';
import { BasicTable, useTable, TableAction, FormSchema, BasicColumn } from '/@/components/Table';
import { TreeItem,BasicTree,TreeActionType} from '/@/components/Tree';
import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
import {getRolePageList,getRoleAuth} from '/@/api/system/role';
import { getMenuSimpleTree } from '/@/api/system/menu';
import { getMenuButtonList, getMenuColumnList, getMenuFieldList } from '/@/api/system/menuButton';
const { t } = useI18n();
const menuRef = ref<Nullable<TreeActionType>>(null);
const buttonRef = ref<Nullable<TreeActionType>>(null);
const columnRef = ref<Nullable<TreeActionType>>(null);
const fieldRef = ref<Nullable<TreeActionType>>(null);
const selectRoleId = ref('');
let menuData =[];
let buttonData =[];
let columnData = [];
let fieldData =[];
const menuAuthData = ref<TreeItem[]>([]);
const buttonAuthData = ref<TreeItem[]>([]);
const columnAuthData = ref<TreeItem[]>([]);
const fieldAuthData = ref<TreeItem[]>([]);
let selectedMenuData={};
let selectedButtonData={};
let selectedColumnData={};
let selectedFieldData={};
const selectedModel = inject('selectedModel');
const inited=ref(false);
const roleColumns: BasicColumn[] = [
{
title: t('角色名称'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('角色编码'),
dataIndex: 'code',
width: 120,
align: 'left',
},
];
async function initStep() {
try {
if(inited.value){
return;
}
menuData = await getMenuSimpleTree();
buttonData = await getMenuButtonList();
columnData = await getMenuColumnList();
fieldData = await getMenuFieldList();
inited.value=true;
}catch (e) {
console.error(e);
}
}
const [registerTableRole, { reload: roleReload }] = useTable({
title: t('角色'),
api: getRolePageList,
rowKey: 'id',
columns: roleColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: [],
showResetButton: false,
},
rowSelection: {
type: 'radio',
onChange: onSelectChange,
},
useSearchForm: true,
showTableSetting: true,
striped: false,
pagination: { pageSize: 100 },
tableSetting: {
size: false,
setting: false,
},
});
function handleRowClick(record: any) {
if(selectRoleId.value===record.id){
return;
}
selectRoleId.value = record.id;
reloadRoleAuth();
}
function onSelectChange(rowKey: string[]) {
if(selectRoleId.value== rowKey[0]){
return;
}
if(rowKey[0]){
selectRoleId.value =rowKey[0] ;
reloadRoleAuth();
}
}
function getAuthMenuTree(authTree,allTree,authList) {
let result=false;
for (let i = 0; i < allTree.length; i++) {
let o = allTree[i];
let include=false;
let childrenInclude=false;
if (authList.includes(o.id)) {
include=true;
}
let children=[];
if (o.children?.length > 0) {
childrenInclude=getAuthMenuTree(children, o.children, authList);
}
if(include===true||childrenInclude===true){
let node=cloneDeep(o);
node.children=children;
node.roleId=selectRoleId.value;
authTree.push(node);
result=true;
}
}
setRoleObjectId(authTree);
return result;
}
function getAuthObjectTree(menuTree,authList,type) {
let cloneMenuTree=cloneDeep(menuTree);
for(let i = 0; i < authList.length; i++){
let object=authList[i];
object.type=type;
let result=hangToMenuTree(object,cloneMenuTree,type);
if(result!==true){
cloneMenuTree.push(object);
}
}
let objectTree=[];
fillToObjectTree(objectTree,cloneMenuTree,type);
setRoleObjectId(objectTree);
return objectTree;
}
function hangToMenuTree(object,menuTree,type) {
for(let i = 0; i< menuTree.length; i++){
let menu=menuTree[i];
if(menu.type&&menu.type===type){
continue;
}
let flag=false;
if(menu.id==object.menuId){
let children = menu.children || (menu.children = []);
children.push(object);
flag=true;
}else{
if (menu.children?.length > 0) {
flag=hangToMenuTree(object,menu.children,type);
}
}
if(flag===true){
menu.hasObjects=true;
return flag;
}
}
return false;
}
function fillToObjectTree(objectTree,menuTree,type){
for(let i = 0; i< menuTree.length; i++){
let menu=menuTree[i];
if(menu.type===type){
objectTree.push(menu);
}else if(menu.hasObjects===true){
let children=[]
if (menu.children?.length > 0) {
fillToObjectTree(children,menu.children,type);
}
menu.children=children;
objectTree.push(menu);
}
}
}
function setRoleObjectId(objectTree){
for(let i = 0; i< objectTree.length; i++){
let object=objectTree[i];
object.roleObjectId=selectRoleId.value+"_"+object.id;
if (object.children?.length > 0) {
setRoleObjectId(object.children);
}
}
}
async function reloadRoleAuth(){
let roleId=selectRoleId.value;
if(!roleId){
return [];
}
const authList = await getRoleAuth(roleId);
let authMenuIds= authList.menuIds || [];
let authButtonIds= authList.buttonIds || [];
let authColumnIds= authList.columnIds || [];
let authFieldIds= authList.formIds || [];
let menuAuthTree=[];
getAuthMenuTree(menuAuthTree,menuData,authMenuIds);
menuAuthData.value=menuAuthTree;
let authButtonData=buttonData.filter((item)=>authButtonIds.includes(item.id));
let buttonAuthTree=getAuthObjectTree(menuData,authButtonData,'button');
buttonAuthData.value=buttonAuthTree;
let authColumnData=columnData.filter((item)=>authColumnIds.includes(item.id));
let columnAuthTree=getAuthObjectTree(menuData,authColumnData,'column');
columnAuthData.value=columnAuthTree;
let authFieldData=fieldData.filter((item)=>authFieldIds.includes(item.id));
let fieldAuthTree=getAuthObjectTree(menuData,authFieldData,'field');
fieldAuthData.value=fieldAuthTree;
nextTick(() => {
unref(menuRef).setCheckedKeys(selectedMenuData[selectRoleId.value]);
unref(buttonRef).setCheckedKeys(selectedButtonData[selectRoleId.value]);
unref(columnRef).setCheckedKeys(selectedColumnData[selectRoleId.value]);
unref(fieldRef).setCheckedKeys(selectedFieldData[selectRoleId.value]);
unref(menuRef)?.expandAll(true);
unref(buttonRef)?.expandAll(true);
unref(columnRef)?.expandAll(true);
unref(fieldRef)?.expandAll(true);
});
}
function handleMenuSelect(keys, e){
selectedMenuData[selectRoleId.value]=keys;
}
function handleButtonSelect(keys, e){
selectedButtonData[selectRoleId.value]=keys;
}
function handleColumnSelect(keys, e){
selectedColumnData[selectRoleId.value]=keys;
}
function handleFieldSelect(keys, e){
selectedFieldData[selectRoleId.value]=keys;
}
function completeStep(){
let map={};
for(let key of Object.keys(selectedMenuData)){
let list=selectedMenuData[key];
if(list&&list.length>0){
let menuIdList=list.map((item)=>{ return item.split("_")[1];});
let objectList=map[key]||(map[key]=[]);
objectList.push(...menuIdList);
}
}
for(let key of Object.keys(selectedButtonData)){
let list=selectedButtonData[key];
if(list&&list.length>0){
let allbuttonList=buttonData.map((item)=>{return item.id});
let buttonList=list.map((item)=>{ return item.split("_")[1];}).filter((item)=>{return allbuttonList.includes(item);});
if(buttonList&&buttonList.length>0){
let objectList=map[key]||(map[key]=[]);
objectList.push(...buttonList);
}
}
}
for(let key of Object.keys(selectedColumnData)){
let list=selectedColumnData[key];
if(list&&list.length>0){
let allColumnList=columnData.map((item)=>{return item.id});
let columnList=list.map((item)=>{ return item.split("_")[1];}).filter((item)=>{return allColumnList.includes(item);});
if(columnList&&columnList.length>0){
let objectList=map[key]||(map[key]=[]);
objectList.push(...columnList);
}
}
}
for(let key of Object.keys(selectedFieldData)){
let list=selectedFieldData[key];
if(list&&list.length>0){
let allFieldList=fieldData.map((item)=>{return item.id});
let fieldList=list.map((item)=>{ return item.split("_")[1];}).filter((item)=>{return allFieldList.includes(item);});
if(fieldList&&fieldList.length>0){
let objectList=map[key]||(map[key]=[]);
objectList.push(...fieldList);
}
}
}
if(Object.keys(map).length>0){
let authorize = selectedModel.value.authorize || (selectedModel.value.authorize = {});
authorize.authorizeMap=map;
}else{
selectedModel.value.authorize=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>
<style lang="less" scoped>
:deep(.ant-table-title){
display:none!important;
}
.treebox {
height: calc(100% - 42px);
overflow: auto;
padding-right: 15px;
margin-bottom: 10px;
border-right: solid 1px #ccc;
.vben-tree {
height: auto;
}
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getXjrSystemConfigPage} from '/@/api/system/systemConfig';
const columns = ref([
{
title: '编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '配置值',
key:'value',
dataIndex:'value',
align: 'left',
sorter: true,
},
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getXjrSystemConfigPage({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
value: item.value
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let systemConfig = selectedModel.value.systemConfig || (selectedModel.value.systemConfig = {});
systemConfig.systemConfigIds=selectedKeys.value;
}else{
selectedModel.value.systemConfig=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,77 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getTenantPageList} from '/@/api/system/tenant';
const columns = ref([
{
title: '租户名',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '租户编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getTenantPageList({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let tenant = selectedModel.value.tenant || (selectedModel.value.tenant = {});
tenant.tenantIds=selectedKeys.value;
}else{
selectedModel.value.tenant=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,141 @@
<template>
<div style="margin-left: 30%">
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">
{{ t('已授权菜单') }}
</div>
<div class="treebox">
<BasicTree
:treeData="treeData"
checkable
ref="treeRef"
@check="handleTreeSelect"
:fieldNames="{ title: 'title', key: 'id' }"
>
<template #title="{ title, systemName, parentId }">
<div v-if="parentId == 0">{{ title }} {{ systemName }}</div>
<div v-else>{{ title }}</div>
</template>
</BasicTree>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, unref,inject,onMounted,nextTick } from 'vue';
import { getTenantMenuSimpleTree } from '/@/api/system/menu';
import { TreeItem, BasicTree, TreeActionType } from '/@/components/Tree';
import { useMessage } from '/@/hooks/web/useMessage';
import { getAuthorize } from '/@/api/system/tenant';
import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
import { useUserStore } from '/@/store/modules/user';
import { storeToRefs } from 'pinia';
const emits = defineEmits(['success', 'register']);
const { notification } = useMessage();
const { t } = useI18n();
const treeRef = ref<Nullable<TreeActionType>>(null);
const treeData = ref<TreeItem[]>([]);
const inited=ref(false);
const selectedKeys = ref<string[]>([]);
const selectedModel = inject('selectedModel');
function getTree(tree) {
return tree || null;
}
function handleTreeSelect(keys, e) {
selectedKeys.value = [...e.halfCheckedKeys, ...keys];
}
function getAuthTree(authTree,allTree,authList) {
let result=false;
for (let i = 0; i < allTree.length; i++) {
let o = allTree[i];
let include=false;
let childrenInclude=false;
if (authList.includes(o.id)) {
include=true;
}
let children=[];
if (o.children?.length > 0) {
childrenInclude=getAuthTree(children, o.children, authList);
}
if(include===true||childrenInclude===true){
let node=cloneDeep(o);
node.children=children;
authTree.push(node);
result=true;
}
}
return result;
}
async function initStep() {
try {
if(inited.value){
return;
}
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
let allTree = await getTenantMenuSimpleTree();
const authList = await getAuthorize(userInfo.value.tenantId);
let authTree=[];
getAuthTree(authTree,allTree,authList);
treeData.value=authTree;
nextTick(() => {
getTree(unref(treeRef)).expandAll(true);
});
inited.value=true;
}catch(e){
console.error(e);
}
}
function builldSelectedTree(tree,selecteds,arr){
if((!tree||tree.length==0)||(!selecteds||selecteds.length==0)){
return;
}
for(let i=0;i<tree.length;i++){
let treeNode=tree[i];
if(selecteds.includes(treeNode.id)){
arr.push(treeNode);
let children=treeNode.children;
let childArr=[];
builldSelectedTree(children,selecteds,childArr);
treeNode.children=childArr;
}
}
}
function completeStep(){
let cloneTreeData= cloneDeep(treeData.value);
let arr=[];
builldSelectedTree(cloneTreeData,selectedKeys.value,arr);
if(arr.length>0){
let tenantAuthorize = selectedModel.value.tenantAuthorize || (selectedModel.value.tenantAuthorize = {});
tenantAuthorize.menuTree=arr;
tenantAuthorize.menuIds=selectedKeys.value;
}else{
selectedModel.value.tenantAuthorize=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,91 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getXjrGroupPage} from '/@/api/system/group';
const columns = ref([
{
title: '用户组名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '用户组编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '状态',
dataIndex: 'enabledMark',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.enabledMark===1) {
return '启用';
}else{
return '禁用';
}
}
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getXjrGroupPage({limit:1,size:99});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
enabledMark: item.enabledMark
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let userGroup = selectedModel.value.userGroup || (selectedModel.value.userGroup = {});
userGroup.userGroupIds=selectedKeys.value;
}else{
selectedModel.value.userGroup=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,77 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getDesignPage } from '/@/api/workflow/design';
const columns = ref([
{
title: '流程编码',
key:'code',
dataIndex:'code',
align: 'left',
sorter: true,
},
{
title: '流程名称',
key:'name',
dataIndex: 'name',
sorter: true,
align: 'left',
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getDesignPage({limit:1,size:99,isAuth:false});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let workflowSchema = selectedModel.value.workflowSchema || (selectedModel.value.workflowSchema = {});
workflowSchema.workflowSchemaIds=selectedKeys.value;
}else{
selectedModel.value.workflowSchema=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -1,6 +1,9 @@
<template>
<div class="export">
<div class="l-export" ><span @click.stop="openConfirmDialog">导出所选项目{{configType}}</span></div>
<div class="l-export" >
<span @click.stop="beginExport('exportSelected')" class="action-btn">选择性导出</span>
<!-- <span @click.stop="beginExport('exportAll')" class="action-btn">导出全部</span> -->
</div>
<div class="list-wrapper">
<a-list size="small" bordered :data-source="state.options">
<template #renderItem="{ item }">
@ -18,155 +21,109 @@
</a-list>
</div>
</div>
<ExportModal
v-if="state.isShowExportModal"
@register="registerExportModal"
@close="handleClose()"
/>
</template>
<script lang="ts" setup>
import { ref,watch,reactive,h} from 'vue';
import { ref,unref,watch,reactive,h} from 'vue';
import {useMessage} from "/@/hooks/web/useMessage";
import { useI18n } from '/@/hooks/web/useI18n';
import { exportDatas,downloadDatas} from '/@/api/system/dataMigration';
import { downloadByData } from '/@/utils/file/download';
import { cloneDeep } from 'lodash-es';
import { dateUtil } from '/@/utils/dateUtil';
import { useModal } from '/@/components/Modal';
import ExportModal from './ExportModal.vue';
import { items } from '../config.ts';
const { t } = useI18n();
const state = reactive({
indeterminate: false,
checkAll: false,
options:[
{
checked:false,
code:"租户",
name:'租户'
},
{
checked:false,
code:'角色',
name:'角色'
},
{
checked:false,
code:'岗位',
name:'岗位'
},
{
checked:false,
code:'用户组',
name:'用户组'
},
{
checked:false,
code:'菜单',
name:'菜单'
},
{
checked:false,
code:'表单',
name:'表单'
},
{
checked:false,
code:'流程定义',
name:'流程定义'
},
{
checked:false,
code:'系统变量',
name:'系统变量'
},
{
checked:false,
code:'数据字典',
name:'数据字典'
},
{
checked:false,
code:'桌面配置',
name:'桌面配置'
},
{
checked:false,
code:'角色-菜单授权',
name:'权限:角色-菜单授权(菜单、按钮、列表、表单)'
},
{
checked:false,
code:'角色-自定义接口授权',
name:'权限:角色-自定义接口授权'
},
{
checked:false,
code:'租户-菜单授权',
name:'权限:租户-菜单授权'
}
]
isShowExportModal: true,
options:cloneDeep(items)
});
function onCheckAllChange(e: any){
if(e.target.checked){
watch(
() => state.options,
val => {
const checkedList=val.filter((item)=>item.checked);
if(checkedList.length>0){
if(checkedList.length< val.length){
state.indeterminate=true;
}else{
state.checkAll=true;
state.indeterminate=false;
}
}else{
state.indeterminate=false;
}
},
{ deep: true },
);
function onCheckAllChange(e: any){
if(e.target.checked){
state.options.forEach((item)=>{
item.checked=true;
});
}else{
}else{
state.options.forEach((item)=>{
item.checked=false;
});
}
state.indeterminate=false;
}
}
state.indeterminate=false;
}
const [registerExportModal, { openModal: openExportModal }] = useModal();
function handleClose() {
state.isShowExportModal = false;
}
async function beginExport(type){
const { notification} = useMessage();
const checkedList=state.options.filter((item)=>item.checked);
if(checkedList.length==0){
notification.warning({
message: '提示',
description: '未选择任何项目'
});
return;
}
if(type=='exportSelected'){
const checkedList=state.options.filter((item)=>item.checked);
openExportModal(true, {
items:cloneDeep(unref(checkedList))
});
}else if(type=='exportAll'){
const {createConfirm} = useMessage();
createConfirm({
iconType: 'warning',
title: () => h('span', t('温馨提示')),
content: () => t('确定导出所选项目中的所有数据吗?'),
width:'500px',
onOk: async () => {
exportAll();
},
okText: () => t('确认'),
cancelText: () => t('取消'),
});
}
}
watch(
() => state.options,
val => {
const checkedList=val.filter((item)=>item.checked);
if(checkedList.length>0){
if(checkedList.length< val.length){
state.indeterminate=true;
}else{
state.checkAll=true;
state.indeterminate=false;
}
}else{
state.indeterminate=false;
}
},
{ deep: true },
);
function openConfirmDialog(){
const { notification,createConfirm} = useMessage();
const checkedList=state.options.filter((item)=>item.checked);
if(checkedList.length==0){
notification.warning({
message: '提示',
description: '未选择任何项目'
});
return;
}
createConfirm({
iconType: 'warning',
title: () => h('span', t('温馨提示')),
content: () => t('确定导出所选项目吗?'),
width:'500px',
onOk: async () => {
handleExport();
},
okText: () => t('确认'),
cancelText: () => t('取消'),
});
}
async function handleExport(){
async function exportAll(){
try {
const checkedCodeList=state.options.filter((item)=>item.checked).map(item => item.code);
let currentTime=dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
let result= await exportDatas({
exportType:'exportAll',
items:checkedCodeList
});
@ -223,8 +180,10 @@
border:0px;
}
}
.action-btn{
margin-right: 30px;
}
</style>

View File

@ -0,0 +1,159 @@
<template>
<span @click.stop="open">
<slot></slot>
<a-modal
v-model:visible="data.visible"
:title="t(`导入数据[${importType=='overrideMode'?'覆盖模式':'新增模式'}]`)"
:maskClosable="false"
@ok="doUpload"
@cancel="close"
@click.stop=""
>
<div class="upload-box">
<a-upload
v-model:file-list="fileList"
class="upload-box"
name="file"
accept=".json,.zip"
:max-count="1"
:before-upload="beforeUpload"
@remove="handleRemove"
>
<img :src="BgImg" />
<div class="a-upload__text">{{ t('将文件拖到此处,或') }}<em>{{ t('点击上传') }}</em></div>
</a-upload>
<div v-if="VITE_GLOB_UPLOAD_ALERT_TIP?.trim()" style="color: red; margin-top: 8px;">
{{VITE_GLOB_UPLOAD_ALERT_TIP}}
</div>
</div>
</a-modal>
</span>
</template>
<script setup lang="ts">
import { reactive,ref} from 'vue';
import BgImg from '../../assets/sysconfig_import.png';
import { useI18n } from '/@/hooks/web/useI18n';
import { defHttp } from '/@/utils/http/axios';
import { useGlobSetting } from '/@/hooks/setting';
import { useMessage } from '/@/hooks/web/useMessage';
import { getAppEnvConfig } from '/@/utils/env';
const { VITE_GLOB_UPLOAD_ALERT_TIP } = getAppEnvConfig();
const props = defineProps({
importType: {
type:String,
},
});
const { t } = useI18n();
const globSetting = useGlobSetting();
const { notification } = useMessage();
const data: {
visible: boolean;
} = reactive({
visible: false
});
const fileList=ref<File[]>([]);
async function open() {
data.visible = true;
}
function close() {
data.visible = false;
fileList.value=[];
}
function handleRemove(file:File){
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
function beforeUpload(file:File){
fileList.value = [...(fileList.value || []), file];
return false;
};
async function doUpload(){
if(fileList==undefined||fileList.value.length==0){
notification.error({
message: '提示',
description: t('请选择文件'),
});
return;
}
const files=[];
fileList.value.forEach(file => {
files.push(file.originFileObj);
});
const url="/system/dataMigration/importFullDatas";
defHttp.uploadFile(
{
baseURL: globSetting.apiUrl,
url:url,
method: 'POST',
},
{
name: 'file',
file: files,
data:{
importType:props.importType
}
}
).then((data) => {
notification.success({
message: '提示',
description: t('导入中...请稍后确认导入结果!'),
});
close();
}).catch((err) => {
console.error(err);
}).finally(()=>{
// close();
});
};
</script>
<style lang="less" scoped>
.tenant-box{
height:100px;
display: flex;
justify-content: center;
align-items: center;
}
.upload-box {
display: inline-block;
}
.upload-demo {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.a-upload-dragger {
width: 615px;
height: 370px;
border: none;
}
.a-upload__text {
position: absolute;
bottom: 100px;
right: 100px;
font-weight: bold;
color: #1d2027;
}
em {
font-style: normal;
color: #4f83fd;
}
</style>

View File

@ -0,0 +1,271 @@
<template>
<BasicModal @register="registerModal" v-bind="$attrs" wrapClassName="data-migration-modal">
<template #title>
<a-steps :current="current">
<a-step
v-for="step in steps"
:title="t(step.name)"
/>
</a-steps>
<div class="btn-box">
<a-button type="primary" @click="handleStepPrev" :disabled="current == 0">{{t('上一步')}}</a-button>
<a-button type="primary" @click="handleStepNext" :disabled="current>=steps.length-1">{{t('下一步')}}</a-button>
<a-button type="primary" @click="handleImport" :disabled="current<steps.length-1">{{t('导入')}}</a-button>
<a-button type="primary" danger @click="handleClose">{{ t('关闭') }}</a-button>
</div>
</template>
<div class="step-container">
<Tenant ref="tenantRef" v-show="showIf('租户')"/>
<Role ref="roleRef" v-show="showIf('角色')"/>
<Post ref="postRef" v-show="showIf('岗位')"/>
<UserGroup ref="userGroupRef" v-show="showIf('用户组')"/>
<Menu ref="menuRef" v-show="showIf('菜单')"/>
<Form ref="formRef" v-show="showIf('表单')"/>
<Workflow ref="workflowRef" v-show="showIf('流程定义')"/>
<SystemConfig ref="systemConfigRef" v-show="showIf('系统配置')"/>
<Dictionary ref="dictionaryRef" v-show="showIf('数据字典')"/>
<CodeRule ref="codeRuleRef" v-show="showIf('自动编码')"/>
<Desktop ref="desktopRef" v-show="showIf('桌面设计')"/>
<RoleMenuAuth ref="roleMenuAuthRef" v-show="showIf('角色-菜单授权')"/>
<InterfaceAuth ref="roleInterfaceAuthRef" v-show="showIf('角色-自定义接口授权')"/>
<TenantAuthorize ref="tenantMenuAuthRef" v-show="showIf('租户-菜单授权')"/>
<ImportTypeSelect ref="importTypeSelectRef" v-show="showIf('导入模式选择')"/>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref,unref,provide,Ref,onMounted} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useMessage } from '/@/hooks/web/useMessage';
import {isFunction } from 'lodash-es';
import { isNumber } from '/@/utils/is';
import { dateUtil } from '/@/utils/dateUtil';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { importDatas} from '/@/api/system/dataMigration';
import Tenant from './components/Tenant.vue';
import Role from './components/Role.vue';
import Post from './components/Post.vue';
import Menu from './components/Menu.vue';
import Form from './components/Form.vue';
import Workflow from './components/Workflow.vue';
import SystemConfig from './components/SystemConfig.vue';
import Dictionary from './components/Dictionary.vue';
import Desktop from './components/Desktop.vue';
import UserGroup from './components/UserGroup.vue';
import RoleMenuAuth from './components/RoleMenuAuth.vue';
import InterfaceAuth from './components/InterfaceAuth.vue';
import TenantAuthorize from './components/TenantAuthorize.vue';
import ImportTypeSelect from './components/ImportTypeSelect.vue';
import CodeRule from './components/CodeRule.vue';
const { t } = useI18n();
const { notification } = useMessage();
const current = ref(0);
const steps=ref([]);
let dirName=ref('');
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
let items=data.items;
items.push({
code:'导入模式选择',
name:'导入模式选择',
ref:"importTypeSelectRef",
});
steps.value=items;
dirName.value=data.dirName;
current.value=0;
selectedModel.value={};
initStep(0);
setModalProps({
confirmLoading: false,
canFullscreen: false,
defaultFullscreen: true,
destroyOnClose: true,
draggable: false,
showOkBtn: false,
showCancelBtn: false,
footer: null,
closable: false,
});
});
const tenantRef=ref();
const roleRef=ref();
const postRef= ref();
const userGroupRef= ref();
const menuRef = ref();
const formRef=ref();
const workflowRef=ref();
const systemConfigRef= ref();
const dictionaryRef= ref();
const desktopRef= ref();
const roleMenuAuthRef= ref();
const roleInterfaceAuthRef= ref();
const tenantMenuAuthRef= ref();
const importTypeSelectRef=ref();
const codeRuleRef=ref();
const refs = {
tenantRef,
roleRef,
postRef,
userGroupRef,
menuRef,
formRef,
workflowRef,
systemConfigRef,
dictionaryRef,
desktopRef,
roleMenuAuthRef,
roleInterfaceAuthRef,
tenantMenuAuthRef,
importTypeSelectRef,
codeRuleRef,
};
const selectedModel: Ref<{
menu?:{
menuIds: number[],
buttonIds: number[],
columnIds:number[],
formIds:number[]
},
tenant?:{
tenantIds:number[]
}
}> = ref({});
provide('selectedModel', selectedModel);
provide('dirName', dirName);
function handleClose() {
closeModal();
}
function showIf(itemType:string){
let currentStep=getStep('current');
if(currentStep==null){
return false;
}
return currentStep.code==itemType;
}
//上一步
function handleStepPrev() {
completeStep(current.value);
current.value--;
initStep(current.value);
}
//下一步
async function handleStepNext() {
completeStep(current.value);
current.value++;
initStep(current.value);
}
//导出
async function handleImport(){
completeStep(current.value);
let validateResult=false;
let selectedModelVal=selectedModel.value;
for(let key of Object.keys(selectedModelVal)){
let item=selectedModelVal[key];
if(item!=null){
validateResult=true;
break;
}
}
if(!validateResult){
notification.warning({
message: '提示',
description: '没有选择任何项目'
});
return;
}
await importDatas({
dirName:dirName.value,
data:{
...unref(selectedModel)
},
});
notification.success({
message: t('提示'),
description: t('导入完成!'),
});
handleClose();
}
function getStep(key:string|number){
let stepList=unref(steps);
if(!stepList||stepList.length==0){
return null;
}
let step=null;
if(key=='current'){
step=unref(stepList[current.value]);
}else if(key=='prev'){
step=unref(stepList[current.value-1]);
}else if(key=='next'){
step=unref(stepList[current.value+1]);
}else if(isNumber(key)&&key>=0&&key<stepList.length){
step=unref(stepList[key]);
}
return step;
}
function initStep(key:string|number){
let stepRef = refs[getStep(key).ref].value;
stepRef.initStep&& stepRef.initStep();
};
function completeStep(key:string|number){
let stepRef = refs[getStep(key).ref].value;
stepRef.completeStep&& stepRef.completeStep();
};
</script>
<style lang="less">
.data-migration-modal{
.step-container {
max-height: 90vh;
overflow-y: auto;
padding-bottom: 16px;
}
.ant-modal-header{
height: 100px;
}
.ant-steps{
display: flex;
flex-wrap: wrap;
}
.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item {
padding-left:16px!important;
margin-bottom: 10px;
flex: 1 1 auto;
}
}
</style>
<style lang="less" scoped>
.btn-box {
height: 100px;
position: absolute;
right: 10px;
top:60px;
:deep(.ant-btn) {
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<span @click.stop="open">
<slot></slot>
<a-modal
v-model:visible="data.visible"
:title="t('导入数据')"
:maskClosable="false"
@ok="doUpload"
@cancel="close"
@click.stop=""
>
<div class="upload-box">
<a-upload
v-model:file-list="fileList"
class="upload-box"
name="file"
accept=".json,.zip"
:max-count="1"
:before-upload="beforeUpload"
@remove="handleRemove"
>
<img :src="BgImg" />
<div class="a-upload__text">{{ t('将文件拖到此处,或') }}<em>{{ t('点击上传') }}</em></div>
</a-upload>
<div v-if="VITE_GLOB_UPLOAD_ALERT_TIP?.trim()" style="color: red; margin-top: 8px;">
{{VITE_GLOB_UPLOAD_ALERT_TIP}}
</div>
</div>
</a-modal>
</span>
</template>
<script setup lang="ts">
import { reactive,ref} from 'vue';
import BgImg from '../../assets/sysconfig_import.png';
import { useI18n } from '/@/hooks/web/useI18n';
import { defHttp } from '/@/utils/http/axios';
import { useGlobSetting } from '/@/hooks/setting';
import { useMessage } from '/@/hooks/web/useMessage';
import { getAppEnvConfig } from '/@/utils/env';
const emits=defineEmits(['fileUploaded']);
const { VITE_GLOB_UPLOAD_ALERT_TIP } = getAppEnvConfig();
const props = defineProps({
importType: {
type:String,
},
});
const { t } = useI18n();
const globSetting = useGlobSetting();
const { notification } = useMessage();
const data: {
visible: boolean;
} = reactive({
visible: false
});
const fileList=ref<File[]>([]);
async function open() {
data.visible = true;
}
function close() {
data.visible = false;
fileList.value=[];
}
function handleRemove(file:File){
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
function beforeUpload(file:File){
fileList.value = [...(fileList.value || []), file];
return false;
};
async function doUpload(){
if(fileList==undefined||fileList.value.length==0){
notification.error({
message: '提示',
description: t('请选择文件'),
});
return;
}
const files=[];
fileList.value.forEach(file => {
files.push(file.originFileObj);
});
const url="/system/dataMigration/analyseDatas";
defHttp.uploadFile(
{
baseURL: globSetting.apiUrl,
url:url,
method: 'POST',
},
{
name: 'file',
file: files
}
).then((data) => {
emits('fileUploaded', data);
close();
}).catch((err) => {
console.error(err);
}).finally(()=>{
});
};
</script>
<style lang="less" scoped>
.upload-box {
display: inline-block;
}
.upload-demo {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.a-upload-dragger {
width: 615px;
height: 370px;
border: none;
}
.a-upload__text {
position: absolute;
bottom: 100px;
right: 100px;
font-weight: bold;
color: #1d2027;
}
em {
font-style: normal;
color: #4f83fd;
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getUploadedData} from '/@/api/system/dataMigration';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const columns = ref([
{
title: t('编码编号'),
dataIndex: 'code',
width: 120,
sorter: true,
align: 'left',
},
{
title: t('编码名称'),
dataIndex: 'name',
width: 120,
sorter: true,
align: 'left',
},
{
title: t('当前流水号'),
dataIndex: 'currentNumber',
width: 120,
align: 'left',
sorter: true,
},
{
title: t('创建用户'),
dataIndex: 'createUserName',
width: 120,
sorter: true,
align: 'left',
},
{
title: t('创建时间'),
dataIndex: 'createDate',
width: 180,
sorter: true,
align: 'left',
},
{
title: t('编码说明'),
dataIndex: 'description',
width: 180,
align: 'left',
sorter: true,
},
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'自动编码',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
currentNumber: item.currentNumber,
createUserName: item.createUserName,
createDate: item.createDate,
description: item.description
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let codeRule = selectedModel.value.codeRule || (selectedModel.value.codeRule = {});
codeRule.itemName='自动编码';
codeRule.importType=codeRule.importType?codeRule.importType:'overrideMode';
codeRule.codeRuleIds=selectedKeys.value;
}else{
selectedModel.value.codeRule=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,80 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'桌面设计',dirName:dirName.value});
let resList=rtObject.list;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let deskTop = selectedModel.value.deskTop || (selectedModel.value.deskTop = {});
deskTop.itemName='桌面设计';
deskTop.importType=deskTop.importType?deskTop.importType:'overrideMode';
deskTop.desktopIds=selectedKeys.value;
}else{
selectedModel.value.deskTop=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,287 @@
<template>
<PageWrapper v-if="inited" dense contentFullHeight fixedHeight contentClass="flex">
<BasicTable
@register="registerTableItem"
@row-click="handleRowClick"
class="w-2/5 xl:w-2/5 mr-2"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-checkbox v-model:checked="checkedItems[record.id]"/>
</template>
</template>
</BasicTable>
<BasicTable @register="registerTableDetail" class="w-3/5 xl:w-3/5">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-checkbox v-model:checked="checkedDetails[record.id]"/>
</template>
</template>
</BasicTable>
</PageWrapper>
</template>
<script lang="ts" setup>
import {ref,inject,nextTick } from 'vue';
import { BasicTable, useTable, TableAction, FormSchema, BasicColumn } from '/@/components/Table';
import {
getDicItemPageList,
getDicDetailPageList
} from '/@/api/system/dic';
import {getUploadedData} from '/@/api/system/dataMigration';
import { PageWrapper } from '/@/components/Page';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDrawer } from '/@/components/Drawer';
const { t } = useI18n();
const inited=ref(false);
const checkedItems=ref({});
const checkedDetails=ref({});
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
let data=null;
const searchFormSchemaItem: FormSchema[] = [
{
field: 'name',
label: t('项目名'),
component: 'Input',
},
{
field: 'code',
label: t('编码'),
component: 'Input',
},
];
const itemColumns: BasicColumn[] = [
{
title: t('选择'),
dataIndex: 'operation',
width: 50,
align: 'left',
},
{
title: t('项目名'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('编号'),
dataIndex: 'code',
width: 120,
align: 'left',
},
{
title: t('备注'),
dataIndex: 'remark',
align: 'left',
},
];
const searchFormSchemaDetail: FormSchema[] = [
{
field: 'name',
label: t('字典名'),
component: 'Input',
},
{
field: 'code',
label: t('编码'),
component: 'Input',
},
];
const detailColumns: BasicColumn[] = [
{
title: t('选择'),
dataIndex: 'operation',
width: 50,
align: 'left',
},
{
title: t('字典名'),
dataIndex: 'name',
width: 120,
align: 'left',
},
{
title: t('编号'),
dataIndex: 'code',
width: 120,
align: 'left',
},
{
title: t('值'),
dataIndex: 'value',
width: 120,
align: 'left',
},
{
title: t('所属项目'),
dataIndex: 'itemName',
width: 120,
align: 'left',
},
{
title: t('备注'),
align: 'left',
dataIndex: 'remark',
},
];
const { notification } = useMessage();
const selectItemId = ref('');
const [registerTableItem, { reload: itemReload }] = useTable({
title: t('数据字典项'),
api: loadDictionaryItem,
rowKey: 'id',
columns: itemColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaItem,
showResetButton: false,
},
useSearchForm: true,
showTableSetting: true,
striped: false,
tableSetting: {
size: false,
setting: false,
},
});
const [registerTableDetail, { reload: detailReload }] = useTable({
title: t('数据字典详情'),
api: loadDictionaryDetails,
rowKey: 'id',
columns: detailColumns,
formConfig: {
rowProps: {
gutter: 16,
},
schemas: searchFormSchemaDetail,
showResetButton: false,
},
beforeFetch: (params) => {
return { ...params, itemId: selectItemId.value };
},
useSearchForm: true,
showTableSetting: true,
striped: false,
tableSetting: {
size: false,
setting: false,
},
});
function handleRowClick(record: any) {
selectItemId.value = record.id;
detailReload();
}
function onSelectChange(rowKey: string[]) {
selectItemId.value = rowKey[0];
detailReload();
}
async function loadDictionaryItem(params){
if(!inited.value){
return [];
}
return data?.dicItems||[];
}
async function loadDictionaryDetails(params){
if(!inited.value){
return [];
}
let itemId=params?.itemId;
let details=data?.dicDetails||[];
let rtList=[];
if(itemId){
rtList=details.filter((detail)=>{return detail.itemId===itemId});
}else{
rtList=details;
}
let items=data?.dicItems||[];
const itemMap = items.reduce((acc, item) => {
acc[item.id] = item.name;
return acc;
}, {});
for(let i=0;i<rtList.length;i++){
let detail=rtList[i];
detail.itemName=itemMap[detail.itemId]||'--';
}
return rtList;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({ itemName:'数据字典',dirName:dirName.value});
data=rtObject.data;
inited.value=true;
//默认选择全部
let dicItems=data?.dicItems||[];
dicItems.forEach(item=>{
checkedItems.value[item.id]=true;
});
let dicDetails=data?.dicDetails||[];
dicDetails.forEach(item=>{
checkedDetails.value[item.id]=true;
});
nextTick(() => {
itemReload();
loadDictionaryDetails();
});
}catch (e) {
console.error(e);
}
}
function completeStep(){
let items=checkedItems.value;
let details=checkedDetails.value;
let checkItemIds = Object.keys(items).filter(key => items[key] === true);
let checkDetailIds = Object.keys(details).filter(key => details[key] === true);
if((checkItemIds&&checkItemIds.length>0)||(checkDetailIds&&checkDetailIds.length>0)){
let dictionary = selectedModel.value.dictionary || (selectedModel.value.dictionary = {});
dictionary.itemName='数据字典';
dictionary.importType=dictionary.importType?dictionary.importType:'overrideMode';
dictionary.itemIds=checkItemIds;
dictionary.detailIds=checkDetailIds;
}else{
selectedModel.value.dictionary=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,83 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getFormTemplatePage} from '/@/api/form/design';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([,
{
title: '表单ID',
key:'id',
dataIndex:'id',
align: 'left',
width:'40%',
sorter: true,
},
{
title: '表单名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'表单',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
id: item.id,
name: item.name
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.log(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let formTemplate = selectedModel.value.formTemplate || (selectedModel.value.formTemplate = {});
formTemplate.itemName='表单模板';
formTemplate.importType=formTemplate.importType?formTemplate.importType:'overrideMode';
formTemplate.formTemplateIds=selectedKeys.value;
}else{
selectedModel.value.formTemplate=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,128 @@
<template>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<div class="import-type-select">
<a-table :dataSource="tableConfig.dataSource" :columns="tableConfig.columns" :pagination="false" >
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'importType'">
<a-radio-group v-if="isForceOverrideMode(record.key)" v-model:value="record.importType">
<a-radio value="overrideMode">覆盖模式</a-radio>
</a-radio-group>
<a-radio-group v-else v-model:value="record.importType">
<a-radio value="addMode">新增模式</a-radio>
<a-radio value="overrideMode">覆盖模式</a-radio>
</a-radio-group>
</template>
</template>
</a-table>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, unref,inject,onMounted,nextTick } from 'vue';
import { PageWrapper } from '/@/components/Page';
const inited=ref(false);
const selectedModel = inject('selectedModel');
const tableConfig=ref({
dataSource:[],
columns:[
{
title: '序号',
dataIndex: 'sortNum',
key: 'sortNum',
width:'100px'
},
{
title: '项目名',
dataIndex: 'itemName',
key: 'itemName',
width:'200px'
},
{
title: '导入模式',
dataIndex: 'importType',
key: 'importType',
width:'400px'
},
]
})
function isForceOverrideMode(itemKey){
let arr=['menu','interfaceAuth','authorize','formTemplate'];
if(arr.includes(itemKey)){
return true;
}
return false;
}
async function initStep() {
try {
tableConfig.value.dataSource=[];
let arr=[];
let selectedModelVal=selectedModel.value;
let sortNum=0;
for(let key of Object.keys(selectedModelVal)){
let item=selectedModelVal[key];
if(item){
arr.push(
{
key:key,
sortNum:++sortNum,
itemName:item.itemName,
importType:item.importType
}
);
}
}
tableConfig.value.dataSource=arr;
inited.value=true;
}catch(e){
console.error(e);
}
}
function completeStep(){
let dataSource=tableConfig.value.dataSource;
if(dataSource&&dataSource.length>0){
const map = dataSource.reduce((map, item) => {
map.set(item.key, item);
return map;
}, new Map());
console.log(map);
let selectedModelVal=selectedModel.value;
for(let key of Object.keys(selectedModelVal)){
let item=selectedModelVal[key];
let i=map.get(key);
if(i){
item.importType=i.importType;
}
}
}
}
defineExpose({
initStep,
completeStep
})
</script>
<style lang="less" scoped>
.import-type-select{
width:100%;
display:flex;
flex-direction: column;
align-items:center;
}
:deep(.ant-table-tbody > tr > td){
padding-left: 8px!important;
}
</style>

View File

@ -0,0 +1,111 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
// import {getTenantPageList} from '/@/api/system/tenant';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '角色id',
key:'roleId',
dataIndex:'roleId',
align: 'left',
sorter: true,
},
{
title: '角色',
key:'roleName',
dataIndex:'roleName',
align: 'left',
sorter: true,
},
{
title: '接口id',
key:'interfaceId',
dataIndex:'interfaceId',
sorter: true,
align: 'left',
},
{
title: '接口名称',
key:'interfaceName',
dataIndex:'interfaceName',
sorter: true,
align: 'left',
},
{
title: '接口类型',
dataIndex: 'groupId',
sorter: true,
align: 'left',
customRender: ({ record }) => {
const groupId = record.groupId;
if(groupId){
return '接口'
} else {
return '目录';
}
},
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'角色-自定义接口授权',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
roleId: item.roleId,
roleName: item.roleName,
interfaceId: item.interfaceId,
interfaceName: item.interfaceName,
groupId: item.groupId
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let interfaceAuth = selectedModel.value.interfaceAuth || (selectedModel.value.interfaceAuth = {});
interfaceAuth.itemName='角色-自定义接口授权';
interfaceAuth.importType='overrideMode';
interfaceAuth.interfaceAuthIds=selectedKeys.value;
}else{
selectedModel.value.interfaceAuth=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,257 @@
<template>
<a-row :gutter="[16, 16]" class="h-full">
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('菜单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="menuData"
checkable
ref="menuRef"
@check="handleTreeSelect"
:fieldNames="{ title: 'title', key: 'id' }"
>
<template #title="{ title, systemName, parentId }">
<div v-if="parentId == 0">{{ title }} {{ systemName }}</div>
<div v-else>{{ title }}</div>
</template>
</BasicTree>
</div>
</a-col>
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('按钮')
}}</div>
<div class="treebox">
<BasicTree
:treeData="buttonData"
checkable
ref="ButtonRef"
@check="handleButtonSelect"
:fieldNames="{ title: 'name', key: 'id' }"
/>
</div>
</a-col>
<a-col :span="6" >
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('字段')
}}</div>
<div class="treebox">
<BasicTree
:treeData="columnData"
checkable
ref="ColumnRef"
@check="handleColumnSelect"
:fieldNames="{ title: 'name', key: 'id' }"
/>
</div>
</a-col>
<a-col :span="6">
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">{{
t('表单')
}}</div>
<div class="treebox">
<BasicTree
:treeData="fieldData"
checkable
ref="FieldRef"
@check="handleFieldSelect"
:fieldNames="{ title: 'name', key: 'id' }"
/>
</div>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import {ref,unref,inject,nextTick} from 'vue';
import { TreeItem,BasicTree,TreeActionType} from '/@/components/Tree';
import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es';
import { getMenuSimpleTree } from '/@/api/system/menu';
import { getMenuButtonList, getMenuColumnList, getMenuFieldList } from '/@/api/system/menuButton';
import {getUploadedData} from '/@/api/system/dataMigration';
const { t } = useI18n();
const MenuRef = ref<Nullable<TreeActionType>>(null);
const ButtonRef = ref<Nullable<TreeActionType>>(null);
const ColumnRef = ref<Nullable<TreeActionType>>(null);
const FieldRef = ref<Nullable<TreeActionType>>(null);
const menuData = ref<TreeItem[]>([]);
const buttonData = ref<TreeItem[]>([]);
const columnData = ref<TreeItem[]>([]);
const fieldData = ref<TreeItem[]>([]);
let menuSelectData =[];
let buttonSelectData =[];
let columnSelectData =[];
let fieldSelectData =[];
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
const inited=ref(false);
async function initStep() {
try {
if(inited.value){
return;
}
let rtObject= await getUploadedData({itemName:'菜单',dirName:dirName.value});
let data=rtObject.data;
menuData.value=data.menus;
let allMenus=data.allMenus;
buttonData.value=buildObjectTree(allMenus,data.menuButtons);
columnData.value=buildObjectTree(allMenus,data.menuColumns);
fieldData.value=buildObjectTree(allMenus,data.menuForms);
nextTick(() => {
unref(MenuRef)?.expandAll(true);
unref(ButtonRef)?.expandAll(true);
unref(ColumnRef)?.expandAll(true);
unref(FieldRef)?.expandAll(true);
});
inited.value=true;
}catch (e) {
console.error(e);
}
}
function buildObjectTree(menus,objects){
if(!objects||objects.length==0){
return [];
}
let cloneMenus=cloneDeep(menus);
let rootMenus=cloneMenus.filter(i => i.parentId === '0');
let arr=[];
let bObjects=[];
buildObjectChildren(rootMenus,cloneMenus,objects,bObjects);
for(let i=0;i<rootMenus.length;i++){
let rootMenu=rootMenus[i];
if(rootMenu.flag===true){
arr.push(rootMenu);
}
}
let otherObjects=objects.filter(i=>!bObjects.includes(i));
arr.push(...otherObjects);
return arr;
}
function buildObjectChildren(menus,allMenus,objects,bObjects){
let rt=false;
for(let i=0;i<menus.length;i++){
let menu=menus[i];
let childMenus=allMenus.filter(i => i.parentId === menu.id);
let objectArr=[];
menu.flag=false;
for(let j=0;j<objects.length;j++){
let object=objects[j];
if(object.menuId===menu.id){
objectArr.push(object);
menu.flag=true;
rt=true;
}
}
menu.children=childMenus?childMenus:[];
if(childMenus&&childMenus.length>0){
let result=buildObjectChildren(menu.children,allMenus,objects,bObjects);
if(result===true){
menu.flag=true;
rt=true;
}
}
let filteredChildren=menu.children.filter(i => i.flag === true);
menu.children=[...filteredChildren,...objectArr];
bObjects.push(...objectArr);
}
return rt;
}
function handleTreeSelect(keys, e) {
menuSelectData= [...e.halfCheckedKeys, ...keys];
}
function handleButtonSelect(keys, e) {
buttonSelectData = [...e.halfCheckedKeys, ...keys];
}
function handleColumnSelect(keys, e) {
columnSelectData = [...e.halfCheckedKeys, ...keys];
}
function handleFieldSelect(keys, e) {
fieldSelectData = [...e.halfCheckedKeys, ...keys];
}
function completeStep(){
if((menuSelectData.length>0)||(buttonSelectData.length>0)
||(columnSelectData.length>0)||(fieldSelectData.length>0)){
let menu = selectedModel.value.menu || (selectedModel.value.menu = {});
menu.itemName='菜单';
menu.importType='overrideMode';
menu.menuIds=menuSelectData;
menu.buttonIds=buttonSelectData;
menu.columnIds=columnSelectData;
menu.formIds=fieldSelectData;
}else{
selectedModel.value.menu=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>
<style lang="less">
.autherizeDialog {
.ant-modal {
top: 20px;
height: calc(100% - 40px) !important;
.ant-modal-content {
height: 100%;
.ant-modal-body {
height: calc(100% - 120px) !important;
.scrollbar__view {
height: 100%;
& > div {
height: 100%;
max-height: none !important;
.ant-col {
height: 100%;
.treebox {
height: calc(100% - 42px);
overflow: auto;
padding-right: 15px;
margin-bottom: 10px;
border-right: solid 1px #ccc;
.vben-tree {
height: auto;
}
}
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject,h} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { Tag } from 'ant-design-vue';
import {getFormTemplatePage} from '/@/api/form/design';
import {getUploadedData} from '/@/api/system/dataMigration';
const { t } = useI18n();
const columns = ref([
{
title: t('选择'),
dataIndex: 'operation',
width: 50,
align: 'left',
},
{
title: '部门名称',
key:'deptName',
dataIndex:'deptName',
align: 'left',
width:'40%',
sorter: true,
},
{
title: '岗位名称',
key:'name',
dataIndex:'name',
align: 'left',
width:'40%',
sorter: true,
},
{
title: '岗位编码',
key:'code',
dataIndex:'code',
align: 'left',
sorter: true,
},
{
title: t('状态'),
dataIndex: 'enabledMark',
width: 80,
align: 'left',
sorter: true,
customRender: ({ record }) => {
const status = record.enabledMark;
const enable = ~~status === 1;
const color = enable ? 'green' : 'red';
const text = enable ? t('启用') : t('停用');
return h(Tag, { color: color }, () => text);
},
},
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'岗位',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
id: item.id,
name: item.name,
code: item.code,
enabledMark:item.enabledMark,
deptName: item.deptName,
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let post = selectedModel.value.post || (selectedModel.value.post = {});
post.itemName='职位';
post.importType='overrideMode';
post.postIds=selectedKeys.value;
}else{
selectedModel.value.post=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,95 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getRolePageList} from '/@/api/system/role';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '角色名',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '角色编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '状态',
dataIndex: 'enabledMark',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.enabledMark===1) {
return '启用';
}else{
return '禁用';
}
}
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'角色',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
enabledMark: item.enabledMark
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let role = selectedModel.value.role || (selectedModel.value.role = {});
role.itemName='角色';
role.importType=role.importType?role.importType:'overrideMode';
role.roleIds=selectedKeys.value;
}else{
selectedModel.value.role=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,124 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '角色id',
key:'roleId',
dataIndex:'roleId',
align: 'left',
sorter: true,
},
{
title: '角色',
key:'roleName',
dataIndex:'roleName',
align: 'left',
sorter: true,
},
{
title: '菜单名称',
key:'menuName',
dataIndex: 'menuName',
sorter: true,
align: 'left',
},
{
title: '对象id',
key:'objectId',
dataIndex: 'objectId',
sorter: true,
align: 'left',
},
{
title: '对象名称',
key:'objectName',
dataIndex: 'objectName',
sorter: true,
align: 'left',
},
{
title: '对象类型',
dataIndex: 'authorizeType',
sorter: true,
align: 'left',
customRender: ({ record }) => {
const authorizeType = record.authorizeType;
if(authorizeType===0){
return '菜单'
}else if(authorizeType===1){
return '按钮'
}else if(authorizeType===2){
return '列表字段'
}else if(authorizeType===3){
return '表单字段'
}else{
return '';
}
},
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'角色-菜单授权',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
roleId: item.roleId,
roleName: item.roleName,
menuName: item.menuName,
objectId: item.objectId,
objectName: item.objectName,
authorizeType:item.authorizeType
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let authorize = selectedModel.value.authorize || (selectedModel.value.authorize = {});
authorize.itemName='角色-菜单授权';
authorize.importType='overrideMode';
authorize.authorizeIds=selectedKeys.value;
}else{
selectedModel.value.authorize=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,88 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import { getXjrSystemConfigPage} from '/@/api/system/systemConfig';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '配置值',
key:'value',
dataIndex:'value',
align: 'left',
sorter: true,
},
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'系统配置',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
value: item.value
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let systemConfig = selectedModel.value.systemConfig || (selectedModel.value.systemConfig = {});
systemConfig.itemName='系统配置';
systemConfig.importType=systemConfig.importType?systemConfig.importType:'overrideMode';
systemConfig.systemConfigIds=selectedKeys.value;
}else{
selectedModel.value.systemConfig=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,81 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="rowSelection" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
// import {getTenantPageList} from '/@/api/system/tenant';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '租户名',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '租户编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
const rowSelection = ref({
onChange: (selectedRowKeys, selectedRows) => {
selectedKeys.value=selectedRowKeys;
},
});
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'租户',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code
}
);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let tenant = selectedModel.value.tenant || (selectedModel.value.tenant = {});
tenant.itemName='租户';
tenant.importType='overrideMode';
tenant.tenantIds=selectedKeys.value;
}else{
selectedModel.value.tenant=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,78 @@
<template>
<div style="margin-left: 30%">
<div class="text-base border-l-6 border-[#5e95ff] pl-3 h-5 leading-5 mb-3 ml-2">
{{ t('租户菜单授权') }}
</div>
<div class="treebox">
<BasicTree
:treeData="treeData"
checkable
ref="treeRef"
@check="handleTreeSelect"
:fieldNames="{ title: 'title', key: 'id' }"
>
<template #title="{ title, systemName, parentId }">
<div v-if="parentId == 0">{{ title }} {{ systemName }}</div>
<div v-else>{{ title }}</div>
</template>
</BasicTree>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref,unref,inject,nextTick} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { TreeItem, BasicTree, TreeActionType } from '/@/components/Tree';
import {getUploadedData} from '/@/api/system/dataMigration';
const { t } = useI18n();
const treeRef = ref<Nullable<TreeActionType>>(null);
const treeData = ref<TreeItem[]>([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function getTree(tree) {
return tree || null;
}
function handleTreeSelect(keys, e) {
selectedKeys.value = [...e.halfCheckedKeys, ...keys];
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'租户-菜单授权',dirName:dirName.value});
let resList=rtObject.data;
treeData.value=resList;
nextTick(() => {
getTree(unref(treeRef)).expandAll(true);
});
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let tenantAuthorize = selectedModel.value.tenantAuthorize || (selectedModel.value.tenantAuthorize = {});
tenantAuthorize.itemName='租户-菜单授权';
tenantAuthorize.importType=tenantAuthorize.importType?tenantAuthorize.importType:'overrideMode';
tenantAuthorize.tenantAuthorizeIds=selectedKeys.value;
}else{
selectedModel.value.tenantAuthorize=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,93 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '用户组名称',
key:'name',
dataIndex:'name',
align: 'left',
sorter: true,
},
{
title: '用户组编码',
key:'code',
dataIndex:'code',
sorter: true,
align: 'left',
},
{
title: '状态',
dataIndex: 'enabledMark',
sorter: true,
align: 'left',
customRender: ({ record }) => {
if (record.enabledMark===1) {
return '启用';
}else{
return '禁用';
}
}
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'用户组',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code,
enabledMark: item.enabledMark
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let userGroup = selectedModel.value.userGroup || (selectedModel.value.userGroup = {});
userGroup.itemName='用户组';
userGroup.importType='overrideMode';
userGroup.userGroupIds=selectedKeys.value;
}else{
selectedModel.value.userGroup=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -0,0 +1,79 @@
<template>
<a-table :columns="columns" :data-source="datas" :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }" />
</template>
<script lang="ts" setup>
import {ref,inject} from 'vue';
import {getUploadedData} from '/@/api/system/dataMigration';
const columns = ref([
{
title: '流程编码',
key:'code',
dataIndex:'code',
align: 'left',
sorter: true,
},
{
title: '流程名称',
key:'name',
dataIndex: 'name',
sorter: true,
align: 'left',
}
]);
const datas=ref([]);
const inited=ref(false);
const selectedKeys = ref([]);
const selectedModel = inject('selectedModel');
const dirName= inject('dirName');
function onSelectChange(selectedRowKeys, selectedRows) {
selectedKeys.value = selectedRowKeys;
}
async function initStep() {
try {
if(inited.value){
return;
}
const rtObject=await getUploadedData({itemName:'流程定义',dirName:dirName.value});
let resList=rtObject.data;
let arr=[];
if(resList) {
let index = 0;
resList.forEach(item => {
arr.push(
{
key: item.id,
name: item.name,
code: item.code
}
);
selectedKeys.value.push(item.id);
});
}
datas.value=arr;
inited.value=true;
}catch (e) {
console.error(e);
}
}
function completeStep(){
if(selectedKeys.value.length>0){
let workflowSchema = selectedModel.value.workflowSchema || (selectedModel.value.workflowSchema = {});
workflowSchema.itemName='流程定义';
workflowSchema.importType=workflowSchema.importType?workflowSchema.importType:'overrideMode';
workflowSchema.workflowSchemaIds=selectedKeys.value;
}else{
selectedModel.value.workflowSchema=null;
}
}
defineExpose({
initStep,
completeStep
})
</script>

View File

@ -1,13 +1,16 @@
<template>
<div class="import">
<div class="import-log"><span @click.stop="showLogsDialog">查看导入日志</span></div>
<!-- <div class="import-log"><span @click.stop="showLogsDialog">查看导入日志</span></div> -->
<div class="import-type-wrapper">
<ImportSystemConfig importType="tenantMode" v-if="getAppEnvConfig().VITE_GLOB_TENANT_ENABLED">
<span class="item-action">租户模式导入</span>
</ImportSystemConfig>
<ImportSystemConfig importType="overrideMode">
<!-- <FullImportSelectFile importType="addMode" v-if="getAppEnvConfig().VITE_GLOB_TENANT_ENABLED">
<span class="item-action">新增模式导入</span>
</FullImportSelectFile>
<FullImportSelectFile importType="overrideMode">
<span class="item-action">覆盖模式导入</span>
</ImportSystemConfig>
</FullImportSelectFile> -->
<PickingImportSelectFile @fileUploaded="openImportWindow">
<span class="item-action">选择文件</span>
</PickingImportSelectFile>
</div>
</div>
<a-modal
@ -21,11 +24,11 @@
<template v-for="item in logs" :key="item.index">
<div class="log">
<span class="col-item">日志时间<span class="content">{{item.time}}</span></span>
<span class="col-item">导入模式<span class="content">{{item.importType=='overrideMode'?'覆盖模式':'租户模式'}}</span></span>
<span class="col-item">导入模式<span class="content">{{item.importType=='overrideMode'?'覆盖模式':'新增模式'}}</span></span>
<span class="col-item">执行结果
<span :class="{'success':item.result=='success','fail':item.result!=='success'}">{{item.result=='success'?'成功':'失败'}}</span>
</span>
<span class="col-item" v-if="item.importType=='tenantMode'">租户编码:<span class="content">{{item.tenantCode}}</span></span>
<!--<span class="col-item" v-if="item.importType=='tenantMode'">租户编码:<span class="content">{{item.tenantCode}}</span></span>-->
<span class="col-item">
<span class="content view-log-detail" v-if="item.result!=='success'" @click.stop="viewLogDetails(item.fileName)" >查看日志详情 </span>
</span>
@ -44,17 +47,40 @@
<a-textarea v-model:value="logDetails" :style="{whiteSpace:'pre',overflowX: 'auto',color:'red'}" :rows="20" />
</div>
</a-modal>
<ImportModal
v-if="state.isShowImportModal"
@register="registerImportModal"
@close="handleClose()"
/>
</template>
<script lang="ts" setup>
import { reactive,ref} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import ImportSystemConfig from './ImportSystemConfig.vue';
import { getLogList,getLogDetails} from '/@/api/system/dataMigration';
import { cloneDeep } from 'lodash-es';
import {getAppEnvConfig} from "/@/utils/env";
import FullImportSelectFile from './FullImportSelectFile.vue';
import PickingImportSelectFile from './PickingImportSelectFile.vue';
import { getLogList,getLogDetails} from '/@/api/system/dataMigration';
import { useModal } from '/@/components/Modal';
import ImportModal from './ImportModal.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { items } from '../config.ts';
const { t } = useI18n();
const { notification } = useMessage();
const state = reactive({
isShowImportModal: true,
options:cloneDeep(items)
});
const [registerImportModal, { openModal: openImportModal }] = useModal();
const logsDialog: {
visible: boolean;
} = reactive({
@ -70,6 +96,21 @@
const logs=ref([]);
const logDetails=ref([]);
function openImportWindow(data){
let items=state.options.filter((item)=>{return data.items.includes(item.code)});
if(items&&items.length>0){
openImportModal(true, {
items:cloneDeep(items),
dirName:data.dirName
});
}else{
notification.error({
message: '提示',
description: '没有从文件中识别到可导入项目,请确认文件内容是否正确'
});
}
}
async function showLogsDialog(){
logs.value=[];
logsDialog.visible=true;
@ -83,6 +124,7 @@
logDetailsDialog.visible=true;
logDetails.value=logContent;
}
</script>
<style lang="less" scoped>
@ -125,6 +167,9 @@
padding:6px 8px;
border-radius:8px;
color:rgba(0, 0, 0, 0.8);
display: block;
min-width:100px;
text-align: center;
}
.item-action:hover {
cursor: pointer;

View File

@ -11,7 +11,7 @@
@finish="onFinish"
@finish-failed="onFinishFailed"
>
<a-form-item :label="t('多端登录')" name="mulLogin">
<!-- <a-form-item :label="t('多端登录')" name="mulLogin">
<div class="flex">
<div
v-for="item in loginType"
@ -41,9 +41,9 @@
"
/>
</a-tooltip>
</a-form-item>
</a-form-item> -->
<a-form-item :label="t('同端互斥')" name="mutualExclusion">
<!-- <a-form-item :label="t('同端互斥')" name="mutualExclusion">
<a-switch
v-model:checked="formState.mutualExclusion"
:checkedValue="1"
@ -67,7 +67,7 @@
"
/>
</a-tooltip>
</a-form-item>
</a-form-item> -->
<a-form-item :label="t('只登出当前会话')" name="checkLogoutCurrentSession">
<a-switch
@ -95,6 +95,27 @@
v-model:checked="formState.withoutLogin"
:checkedValue="1"
:unCheckedValue="0"
@change="handleWithoutChange"
/>
</a-form-item>
<a-form-item name="withoutLoginTime">
<template #label>
<span style="display: inline-flex; align-items: center;">
{{ t('登录有效时长') }}
<a-tooltip placement="right">
<template #title>
<p style="max-width: 336px">单位为小时,大于等于零的整数,0或者空使用配置文件配置, 七天免登优先于免登时长配置</p>
</template>
<QuestionCircleFilled style="font-size: 16px; color: #ccc; margin-left: 6px;" />
</a-tooltip>
</span>
</template>
<a-input-number
v-model:value="formState.withoutLoginTime"
:placeholder="t('登录有效时长')"
style="width: 50%"
@change="handleWithoutTimeChange"
/>
</a-form-item>
<a-form-item :label="t('密码策略')" name="passwordStrategy">
@ -232,6 +253,29 @@
/>
</a-form-item>
<a-form-item name="lockPageReLogin">
<template #label>
<span style="display: inline-flex; align-items: center;">
{{ t('登录失效后回到锁屏页') }}
<a-tooltip placement="right">
<template #title>
<p style="max-width: 336px">
锁屏重登:用户信息超时开启锁屏重登后跳转到锁屏界面解锁后可以继续提交当前页面
</p>
</template>
<QuestionCircleFilled
style="font-size: 16px; color: #ccc; margin-left: 6px;"
/>
</a-tooltip>
</span>
</template>
<a-switch
v-model:checked="formState.lockPageReLogin"
:checkedValue="true"
:unCheckedValue="false"
/>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 6, span: 24 }">
<a-button type="primary" html-type="submit" class="ml-4">{{ t('提交') }}</a-button>
</a-form-item>
@ -266,6 +310,8 @@
checkErrorLoginBlockIpCount?: number | null;
checkErrorLoginBlockIpRestoreTime?: number | null;
checkLogoutCurrentSession?: boolean;
withoutLoginTime?: number;
lockPageReLogin?: boolean;
}
let formState = ref<FormState>({});
@ -288,7 +334,9 @@
checkErrorLoginBlockIp: res.checkErrorLoginBlockIp,
checkErrorLoginBlockIpCount: res.checkErrorLoginBlockIpCount,
checkErrorLoginBlockIpRestoreTime: res.checkErrorLoginBlockIpRestoreTime,
checkLogoutCurrentSession: res.checkLogoutCurrentSession
checkLogoutCurrentSession: res.checkLogoutCurrentSession,
withoutLoginTime: res.withoutLoginTime,
lockPageReLogin: res.lockPageReLogin,
};
id.value = res.id;
});
@ -358,6 +406,16 @@
content: h('div', {}, [h('p', '修改同端互斥状态需重启后端!')]),
});
};
function handleWithoutChange() {
if (formState.value.withoutLogin == 1) {
formState.value.withoutLoginTime = 0;
}
};
function handleWithoutTimeChange() {
if (formState.value.withoutLoginTime && formState.value.withoutLoginTime > 0) {
formState.value.withoutLogin = 0;
}
};
</script>
<style lang="less">
.gouBox {

View File

@ -0,0 +1,139 @@
<template>
<span @click.stop="open">
<slot></slot>
<a-modal
v-model:visible="data.visible"
:title="t(`导入租户`)"
:maskClosable="false"
@ok="doUpload"
@cancel="close"
@click.stop=""
>
<div class="upload-box">
<a-upload
v-model:file-list="fileList"
class="upload-box"
name="file"
accept=".json,.zip"
:max-count="1"
:before-upload="beforeUpload"
@remove="handleRemove"
>
<div class="a-upload__text">{{ t('将文件拖到此处,或') }}<em>{{ t('点击上传') }}</em></div>
</a-upload>
<div v-if="VITE_GLOB_UPLOAD_ALERT_TIP?.trim()" style="color: red; margin-top: 8px;">
{{VITE_GLOB_UPLOAD_ALERT_TIP}}
</div>
</div>
</a-modal>
</span>
</template>
<script setup lang="ts">
import { reactive,ref} from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { defHttp } from '/@/utils/http/axios';
import { useGlobSetting } from '/@/hooks/setting';
import { useMessage } from '/@/hooks/web/useMessage';
import { getAppEnvConfig } from '/@/utils/env';
const { VITE_GLOB_UPLOAD_ALERT_TIP } = getAppEnvConfig();
const props = defineProps({
importType: {
type:String,
},
});
const { t } = useI18n();
const globSetting = useGlobSetting();
const { notification } = useMessage();
const data: {
visible: boolean;
} = reactive({
visible: false
});
const fileList=ref<File[]>([]);
async function open() {
data.visible = true;
}
function close() {
data.visible = false;
fileList.value=[];
}
function handleRemove(file:File){
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
function beforeUpload(file:File){
fileList.value = [...(fileList.value || []), file];
return false;
};
async function doUpload(){
if(fileList==undefined||fileList.value.length==0){
notification.error({
message: '提示',
description: t('请选择文件'),
});
return;
}
const files=[];
fileList.value.forEach(file => {
files.push(file.originFileObj);
});
const url="/system/tenant/import";
defHttp.uploadFile(
{
baseURL: globSetting.apiUrl,
url:url,
method: 'POST',
},
{
name: 'file',
file: files,
}
).then((data) => {
notification.success({
message: '提示',
description: t('导入成功!'),
});
close();
}).catch((err) => {
console.error(err);
}).finally(()=>{
});
};
</script>
<style lang="less" scoped>
.upload-box {
display: flex;
flex-direction: column;
height: 120px;
align-items: center;
justify-content: center;
}
.a-upload__text {
bottom: 100px;
right: 100px;
font-weight: bold;
color: #1d2027;
}
em {
font-style: normal;
color: #4f83fd;
}
</style>

View File

@ -8,6 +8,14 @@
<a-button type="primary" @click="handleAuth">
{{ t('功能授权') }}
</a-button>
<a-button type="primary" @click="handleExport">
{{ t('导出租户') }}
</a-button>
<a-button type="primary">
<ImportModal importType="overrideMode">
{{ t('导入租户') }}
</ImportModal>
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex == 'action'">
@ -43,10 +51,14 @@
import { PageWrapper } from '/@/components/Page';
import TenantDrawer from './components/TenantDrawer.vue';
import AuthModal from './components/AuthModal.vue';
import ImportModal from './components/ImportModal.vue';
import { Switch } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { deleteTenant, getTenantPageList, updateTenantStatus } from '/@/api/system/tenant';
import { deleteTenant, getTenantPageList, updateTenantStatus,exportTenant } from '/@/api/system/tenant';
import { useI18n } from '/@/hooks/web/useI18n';
import { downloadByData } from '/@/utils/file/download';
import { dateUtil } from '/@/utils/dateUtil';
const { t } = useI18n();
const columns: BasicColumn[] = [
{
@ -144,7 +156,7 @@
const { notification } = useMessage();
const [registerDrawer, { openModal }] = useModal();
const [registerModal, { openModal: openAuthModal }] = useModal();
const [registerTable, { reload, getSelectRowKeys }] = useTable({
const [registerTable, { reload, getSelectRowKeys,getSelectRows }] = useTable({
title: t('租户列表'),
api: getTenantPageList,
rowKey: 'id',
@ -199,6 +211,23 @@
});
}
async function handleExport(){
if (getSelectRows().length === 0) {
notification.warning({
message: t('警告'),
description: t('必须选中一行!'),
});
return;
}
let selectedRow=getSelectRows()[0];
let fileName="tenant_"+selectedRow.code+'_'+dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
const res = await exportTenant({id:selectedRow.id});
downloadByData(
res.data,
fileName+".json"
);
}
function handleEdit(record: Recordable) {
openModal(true, {
record,

View File

@ -0,0 +1,288 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:width="800"
title="新增角色"
:fixedHeight="true"
@ok="handleSubmit"
>
<a-tabs v-model:activeKey="activeKey" @change="handleTabChange">
<a-tab-pane key="1" tab="全部">
<a-table
:columns="columns"
:dataSource="roleData"
:rowSelection="rowSelectionAll"
:pagination="pagination"
:loading="loading"
rowKey="id"
size="middle"
:scroll="{ y: 400 }"
@change="handleTableChange"
/>
</a-tab-pane>
<a-tab-pane key="2" tab="已选" force-render>
<a-table
:columns="columns"
:dataSource="selectedList"
:rowSelection="rowSelectionSelected"
rowKey="id"
size="middle"
:scroll="{ y: 400 }"
/>
</a-tab-pane>
<template #rightExtra>
<div class="left-extra">
<a-input
v-model:value="searchText"
placeholder="请输入查询关键字"
@keypress.enter="handleSearch"
style="width: 200px"
>
<template #prefix>
<SearchOutlined style="color: #ccc" />
</template>
</a-input>
<span class="left-text">已选中{{ selectedRowKeys.length }}</span>
</div>
<a-button type="primary" danger :icon="h(DeleteOutlined)" @click="handleClear">
清空
</a-button>
</template>
</a-tabs>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, h, computed, nextTick } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getRolePageList } from '/@/api/system/role';
import { useI18n } from '/@/hooks/web/useI18n';
import { DeleteOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { cloneDeep } from 'lodash-es';
const { t } = useI18n();
// 表格列配置
const columns = [
{
title: t('角色名称'),
dataIndex: 'name',
key: 'name',
align: 'center',
},
{
title: t('角色编码'),
dataIndex: 'code',
key: 'code',
align: 'center',
},
];
// 响应式数据
const activeKey = ref('1');
const roleData = ref<any[]>([]);
const selectedList = ref<any[]>([]);
const selectedRowKeys = ref<string[]>([]);
const searchText = ref('');
const loading = ref(false);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(15);
// 使用Map存储所有已选择的角色数据key为角色idvalue为角色对象
const selectedMap = ref<Map<string, any>>(new Map());
// 分页配置
const pagination = computed(() => ({
total: total.value,
current: currentPage.value,
pageSize: pageSize.value,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
}));
// 定义事件
const emits = defineEmits(['success']);
// 模态框注册
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false });
// 初始化选择状态
const roles = data.roles || [];
selectedMap.value.clear();
roles.forEach(role => {
selectedMap.value.set(role.id, role);
});
// 更新选择状态
updateSelectionState();
await loadRoleData();
});
// 获取角色数据
const loadRoleData = async (params = {}) => {
loading.value = true;
try {
const res = await getRolePageList({
limit: currentPage.value,
size: pageSize.value,
name: searchText.value,
...params,
});
roleData.value = res.records || res.list || res;
total.value = res.total || res.length || 0;
} catch (error) {
console.error('获取角色列表失败:', error);
} finally {
loading.value = false;
}
};
// 表格翻页事件处理
const handleTableChange = (pagination: any) => {
currentPage.value = pagination.current;
pageSize.value = pagination.pageSize;
loadRoleData();
};
// 更新选择状态
const updateSelectionState = () => {
selectedRowKeys.value = Array.from(selectedMap.value.keys());
selectedList.value = Array.from(selectedMap.value.values());
};
// 全部表格的行选择配置
const rowSelectionAll = computed(() => ({
type: 'checkbox',
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: string[], selectedRows: any[]) => {
// 处理单个选择/取消选择
const currentPageIds = roleData.value.map(item => item.id);
// 移除当前页所有已选择的项
currentPageIds.forEach(id => {
selectedMap.value.delete(id);
});
// 添加新选择的项
selectedRows.forEach(row => {
selectedMap.value.set(row.id, row);
});
updateSelectionState();
},
onSelect: (record: any, selected: boolean) => {
if (selected) {
selectedMap.value.set(record.id, record);
} else {
selectedMap.value.delete(record.id);
}
updateSelectionState();
},
onSelectAll: (selected: boolean, selectedRows: any[], changeRows: any[]) => {
if (selected) {
selectedRows.forEach(row => {
selectedMap.value.set(row.id, row);
});
} else {
changeRows.forEach(row => {
selectedMap.value.delete(row.id);
});
}
updateSelectionState();
},
}));
// 已选表格的行选择配置
const rowSelectionSelected = computed(() => ({
type: 'checkbox',
selectedRowKeys: selectedList.value.map(item => item.id),
onChange: (keys: string[], selectedRows: any[]) => {
// 清空所有选择
selectedMap.value.clear();
// 添加新选择的项
selectedRows.forEach(row => {
selectedMap.value.set(row.id, row);
});
updateSelectionState();
},
}));
// 标签页切换
const handleTabChange = (key: string) => {
activeKey.value = key;
// 已选标签页不需要额外操作selectedList已经是最新的
};
// 搜索
const handleSearch = () => {
currentPage.value = 1; // 搜索时重置到第一页
loadRoleData({ name: searchText.value });
};
// 清空选择
const handleClear = () => {
selectedMap.value.clear();
updateSelectionState();
};
// 提交
const handleSubmit = () => {
emits('success', Array.from(selectedMap.value.values()));
closeModal();
};
</script>
<style lang="less" scoped>
.left-extra {
display: flex;
align-items: center;
gap: 10px;
.left-text {
color: #5e95ff;
font-weight: 500;
}
}
:deep(.ant-tabs-extra-content) {
display: flex;
align-items: center;
justify-content: space-between;
width: 80%;
margin-left: 60px;
.left-extra {
display: flex;
align-items: center;
.left-text {
width: 90px;
margin-left: 10px;
color: #5e95ff;
}
}
}
:deep(.ant-input-affix-wrapper) {
border-radius: 10px;
background: #f8f8f8;
border: none;
}
:deep(.ant-input) {
background: #f8f8f8;
}
:deep(.ant-table) {
.ant-table-thead > tr > th {
background: #fafafa;
font-weight: 600;
}
}
</style>

View File

@ -107,26 +107,43 @@
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { getDepartmentTree } from '/@/api/system/department';
import { addUser, getUser, updateUser } from '/@/api/system/user';
import { addUser, getUser, updateUser, getUserNamePrefix } from '/@/api/system/user';
import Icon from '/@/components/Icon/index';
import { testPwdState } from '/@/utils/event/design';
import { PlusOutlined } from '@ant-design/icons-vue';
import RoleModal from './RoleModal.vue';
import RoleModal from './RoleModalV2.vue';
import PostModal from './PostModal.vue';
import OrganizationModal from './OrganizationModal.vue';
import { debounce } from 'lodash-es';
const { t } = useI18n();
const accountFormSchema: FormSchema[] = [
{
field: 'userName',
label: '账号',
component: 'Input',
required: true,
colProps: { span: 12 },
componentProps: {
placeholder: '请输入账号',
{
field: 'userName',
label: '账号',
component: 'Input',
colProps: { span: 12 },
rules: [
{
required: true,
validator: async (_, value) => {
let values = getFieldsValue1();
let prefix = await getUserNamePrefix();
if (!value) {
return Promise.reject('请输入账号');
}
if (values?.isSelfBuild === 'Y' && !value.startsWith(prefix)) {
return Promise.reject('自建用户账号必须带有前缀:' + prefix);
}
return Promise.resolve();
},
},
],
required: true,
componentProps: {
placeholder: '请输入账号',
},
},
},
{
field: 'name',
label: '用户姓名',
@ -285,6 +302,51 @@
unCheckedValue: 'N',
}
},
{
field: 'isSelfBuild',
label: '自建用户',
component: 'Switch',
colProps: { span: 12 },
helpComponentProps: { maxWidth: '400px' },
componentProps: ({ formModel, formActionType }) => {
return {
checkedValue: 'Y',
unCheckedValue: 'N',
onChange: debounce((e: ChangeEvent) => {
//fieldInt组件会根据fieldString的组件值 变化 +2 //参考view/code/demo2/data/index.ts
if (formModel.isSelfBuild === 'N') {
formModel.isPushMQ = 'N';
}
}, 500),
}
}
},
{
field: 'isPushMQ',
label: '推送mq',
component: 'Switch',
colProps: { span: 12 },
helpMessage: '开启推送mq系统会将自建用户推送到移动办公',
helpComponentProps: { maxWidth: '400px' },
componentProps: {
checkedValue: 'Y',
unCheckedValue: 'N',
},
},
{
field: 'validityTime',
label: '有效期',
colProps: {
span: 12,
},
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm',
style: { width: '100%' },
getPopupContainer: () => document.body,
}
},
{
label: t('备注'),
field: 'remark',
@ -536,6 +598,7 @@
registerForm1,
{
setFieldsValue: setFieldsValue1,
getFieldsValue: getFieldsValue1,
updateSchema: updateSchema1,
resetFields: resetFields1,
validate: validate1,
@ -624,6 +687,16 @@
rowId.value = data.id;
const record = await getUser(data.id);
const departmentIds = record.departmentIds?.split(',')||[];
let validityTime = []
if (record.validityStartTime) {
validityTime[0] = record.validityStartTime;
}
if (record.validityEndTime) {
validityTime[1] = record.validityEndTime;
}
if (validityTime.length > 0) {
record.validityTime = validityTime;
}
setFieldsValue1({
...record,
departmentIds,
@ -640,6 +713,15 @@
setTableData2(postDatasource.value);
setTableData3(orgDatasource.value);
setTableData4(deptDatasource.value);
} else {
//新增
setFieldsValue1({
isSync: 'N',
isMain: 'N',
isSelfBuild: 'N',
isPushMQ: 'N',
});
getUserNamePrefixName();
}
const treeData = await getDepartmentTree();
@ -702,6 +784,19 @@
orgs: deptDatasource.value,
});
};
async function getUserNamePrefixName() {
let userNamePrefix = await getUserNamePrefix();
setFieldsValue1({
userName: userNamePrefix
});
// updateSchema1([{
// field: 'userName',
// defaultValue: userNamePrefix?.value,
// componentProps: { addonBefore: userNamePrefix?.value }
// }])
}
const handleDelete = (index, type, record) => {
// 映射不同类型对应的数据源
const dataSources = {
@ -765,7 +860,10 @@
departments
};
setModalProps({ confirmLoading: true });
if (data.validityTime) {
data.validityStartTime = data.validityTime[0];
data.validityEndTime = data.validityTime[1];
}
// TODO custom api
if (!unref(isUpdate)) {
//false 新增