--添加测试模块

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

@ -10,7 +10,7 @@ VITE_PUBLIC_PATH = /
# 如果接口地址匹配到则会转发到http://localhost:3000防止本地出现跨域问题
# 可以有多个,注意多个不能换行,否则代理将会失效
#VITE_PROXY = [["/api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
VITE_PROXY=[["/api","http://back.ges.bjgastx.com"]]
VITE_PROXY=[["/api","http://127.0.0.1:8090"]]
# 是否删除Console.log
VITE_DROP_CONSOLE = false

View File

@ -12,6 +12,6 @@ VOLUME ["/etc/nginx/nginx.conf", "/usr/share/nginx/html"]
CMD ["nginx","-g","daemon off;"]
# docker build -t itc-web-arm64:1.0.0 .
# docker build -f Dockerfile-arm64 -t itc-web-arm64:1.0.0 .

View File

@ -1,6 +1,6 @@
{
"name": "itc-framework",
"version": "2.0.4",
"version": "2.0.7",
"author": {
"name": "ITC",
"email": "gdyditc@geg.com.cn",

View File

@ -55,7 +55,7 @@
return '关闭提示';
};
if(!getAppEnvConfig().VITE_GLOB_CLOSE_ALERT_DISABLED){
if(!import.meta.env.VITE_GLOB_CLOSE_ALERT_DISABLED){
window.addEventListener('beforeunload', beforeUnloadHandler);
}
@ -79,3 +79,12 @@
line-height: 35px;
}
</style>
<style>
.vben-default-layout {
height: 100%;
}
.vben-layout-content {
flex: 1!important;
background-color: #fff;
}
</style>

View File

@ -0,0 +1,110 @@
import { Testfrom3PageModel, Testfrom3PageParams, Testfrom3PageResult } from './model/Testfrom3Model';
import { defHttp } from '/@/utils/http/axios';
import { ErrorMessageMode } from '/#/axios';
enum Api {
Page = '/system/testfrom3/page',
List = '/system/testfrom3/list',
Info = '/system/testfrom3/info',
Testfrom3 = '/system/testfrom3',
Export = '/system/testfrom3/export',
}
/**
* @description: 查询Testfrom3分页列表
*/
export async function getTestfrom3Page(params: Testfrom3PageParams, mode: ErrorMessageMode = 'modal') {
return defHttp.get<Testfrom3PageResult>(
{
url: Api.Page,
params,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 获取Testfrom3信息
*/
export async function getTestfrom3(id: String, mode: ErrorMessageMode = 'modal') {
return defHttp.get<Testfrom3PageModel>(
{
url: Api.Info,
params: { id },
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 新增Testfrom3
*/
export async function addTestfrom3(testfrom3: Recordable, mode: ErrorMessageMode = 'modal') {
return defHttp.post<boolean>(
{
url: Api.Testfrom3,
params: testfrom3,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 更新Testfrom3
*/
export async function updateTestfrom3(testfrom3: Recordable, mode: ErrorMessageMode = 'modal') {
return defHttp.put<boolean>(
{
url: Api.Testfrom3,
params: testfrom3,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 删除Testfrom3批量删除
*/
export async function deleteTestfrom3(ids: string[], mode: ErrorMessageMode = 'modal') {
return defHttp.delete<boolean>(
{
url: Api.Testfrom3,
data: ids,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 导出Testfrom3
*/
export async function exportTestfrom3(
params?: object,
mode: ErrorMessageMode = 'modal'
) {
return defHttp.download(
{
url: Api.Export,
method: 'GET',
params,
responseType: 'blob',
},
{
errorMessageMode: mode,
},
);
}

View File

@ -0,0 +1,32 @@
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
/**
* @description: Testfrom3分页参数 模型
*/
export interface Testfrom3PageParams extends BasicPageParams {
danXingWenBen5518: string;
danXingWenBen5841: string;
jiShuZuJian6835: string;
}
/**
* @description: Testfrom3分页返回值模型
*/
export interface Testfrom3PageModel {
id: string;
danXingWenBen5518: string;
danXingWenBen5841: string;
jiShuZuJian6835: string;
}
0;
/**
* @description: Testfrom3分页返回值结构
*/
export type Testfrom3PageResult = BasicFetchResult<Testfrom3PageModel>;

View File

@ -3,9 +3,11 @@ import { ErrorMessageMode } from '/#/axios';
enum Api {
ExportDatas= '/system/dataMigration/exportDatas',
ImportDatas= '/system/dataMigration/importDatas',
DownloadDatas='/system/dataMigration/downloadDatas',
LogList='/system/dataMigration/logList',
LogDetails='/system/dataMigration/logDetails',
GetUploadedData='/system/dataMigration/getUploadedData'
}
/**
@ -24,6 +26,38 @@ export async function exportDatas(params, mode: ErrorMessageMode = 'modal') {
}
/**
* @description: 系统配置迁移-导入资源
*/
export async function importDatas(params, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{
url: Api.ImportDatas+"/"+params.dirName,
data:params.data,
timeout: 5 * 60 * 1000,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 获取上传的数据
*/
export async function getUploadedData(params,mode: ErrorMessageMode = 'modal') {
return defHttp.get<[]>(
{
url: Api.GetUploadedData,
params
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 根据uuid(目录名称)下载数据
*/

View File

@ -1,13 +1,27 @@
import { defHttp } from '/@/utils/http/axios';
import { ErrorMessageMode } from '/#/axios';
import { FilePageListParams, FilePageListSearchModel, FilePageListResultModel } from './model';
import { FilePageListParams, FilePageListSearchModel, FilePageListResultModel, ZipFilesModel, FileModel } from './model';
enum Api {
File = '/system/file',
Info = '/system/file/info',
InfoByDownloadUrl = '/system/file/info-byDownloadUrl',
List = '/system/file',
Page = '/system/file/page',
DeleteFile = '/system/file/delete-single',
ZipFiles = '/system/file/package-files',
}
export async function getInfoByDownloadUrl(params: {id: string}, mode: ErrorMessageMode = 'modal') {
return defHttp.get<FileModel>(
{
url: Api.InfoByDownloadUrl,
params,
},
{
errorMessageMode: mode,
},
);
}
/**
@ -73,6 +87,18 @@ export async function getFileList(params: FilePageListParams, mode: ErrorMessage
);
}
export async function getZipFiles(params, mode: ErrorMessageMode = 'modal') {
return defHttp.get<ZipFilesModel>(
{
url: Api.ZipFiles,
params,
},
{
errorMessageMode: mode,
},
);
}
export async function getAppToken(params, mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{

View File

@ -34,6 +34,17 @@ export interface FileModel {
fileType: string;
downloadCount: number;
remark: string;
fileUrlFixed: string; //加签后的url
}
/**
* 文件上传打包 返回模型
*/
export interface ZipFilesModel {
url: string;
type: string;
msg: string;
name: string;
}
/**

View File

@ -8,7 +8,7 @@ export interface LoginParams {
password: string;
tenantCode: string;
deviceType?: number;
captchaCode: string;
captchaCode?: string;
}
export interface RoleInfo {

View File

@ -7,8 +7,6 @@ enum Api {
List = '/system/systemConfig/list',
Info = '/system/systemConfig/info',
XjrSystemConfig = '/system/systemConfig',
Export = '/system/systemConfig/export',
}
@ -108,3 +106,4 @@ export async function exportXjrSystemConfig(
},
);
}

View File

@ -1,14 +1,15 @@
import { XjrNoticePageModel, XjrNoticePageParams, XjrNoticePageResult } from './model/SystemNoticeModel';
import { defHttp } from '/@/utils/http/axios';
import { ErrorMessageMode } from '/#/axios';
import { BasicPageParams } from "/@/api/model/baseModel";
enum Api {
Page = '/system/systemNotice/page',
List = '/system/systemNotice/list',
Info = '/system/systemNotice/info',
XjrNotice = '/system/systemNotice',
QueryLoginUserNotices = '/system/systemNotice/queryLoginUserNotices',
SetNoticeUserIsRead = '/system/systemNotice/setNoticeUserIsRead'
}
/**
@ -85,3 +86,35 @@ export async function deleteXjrNotice(ids: string[], mode: ErrorMessageMode = 'm
},
);
}
/**
* 查询登录用户系统通知
* @param params
* @param mode
*/
export async function queryLoginUserNotices(params: BasicPageParams, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{
url: Api.QueryLoginUserNotices,
params: params,
},
{
errorMessageMode: mode,
showLoading: false
},
);
}
/**
* 设置用户消息为已读状态
* @param ids
* @param mode
*/
export async function setSystemNoticeRead(ids, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{ url: Api.SetNoticeUserIsRead, params: {noticeIds: ids} },
{
errorMessageMode: mode,
},
);
}

View File

@ -11,6 +11,7 @@ enum Api {
Status = '/system/tenant/status',
Authorize = '/system/tenant/authorize',
Switch = '/system/switch-tenant',
Export = '/system/tenant/export',
}
/**
@ -191,3 +192,25 @@ export async function changeTenant(tenantCode: String, mode: ErrorMessageMode =
},
);
}
/**
* @description:导出租户
*/
export async function exportTenant(
params?: object,
mode: ErrorMessageMode = 'modal'
) {
return defHttp.download(
{
url: Api.Export,
params:{
id:params.id
},
method: 'GET',
responseType: 'blob',
},
{
errorMessageMode: mode,
},
);
}

View File

@ -30,6 +30,8 @@ enum Api {
Online = '/organization/user/online-users/page',
OffOnline = '/organization/user/offline-users',
Profile = '/organization/user/get-user-organization-info',
GetUserNamePrefix = '/organization/user/getUserNamePrefix',
getUserByDepartTree = '/organization/user/get-user-department-tree'
}
/**
@ -307,3 +309,33 @@ export async function getUserProfile(mode: ErrorMessageMode = 'modal') {
},
);
}
/**
* @description: 获取XjrSystemConfig用户名前缀
*/
export async function getUserNamePrefix(mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{
url: Api.GetUserNamePrefix
},
{
errorMessageMode: mode
}
);
}
/**
* @description: 将用户转化为departTree形式
*/
export async function getUserByDepartTree(ids, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{
url: Api.getUserByDepartTree,
params: {
ids: ids
},
},
{
errorMessageMode: mode,
},
);
}

View File

@ -38,6 +38,8 @@ enum Api {
Withdraw = '/workflow/execute/my-task/withdraw',
SetAssignee = '/workflow/execute/set-assignee',
withdrawNode = '/workflow/execute/withdraw-node',
AddTask = '/workflow/adminOperation/add-task',
SubTask = '/workflow/adminOperation/sub-task',
}
/**
@ -272,6 +274,24 @@ export async function postSetSign(
},
);
}
/**
* @description: 加签减签
*/
export async function postSetSignV2(
data,
mode: ErrorMessageMode = 'modal',
) {
return defHttp.post<SubmittingProcessData>(
{
url: Api.SetSign,
params: data,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 转办
*/
@ -439,3 +459,33 @@ export async function getDrawNode(processId, mode: ErrorMessageMode = 'modal') {
},
);
}
/**
* @description: 加签
*/
export async function postAddTask(params, mode: ErrorMessageMode = 'modal') {
return defHttp.post<boolean>(
{
url: Api.AddTask,
params: params,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description: 减签
*/
export async function postSubTask(params, mode: ErrorMessageMode = 'modal') {
return defHttp.post<boolean>(
{
url: Api.SubTask,
params: params,
},
{
errorMessageMode: mode,
},
);
}

View File

@ -0,0 +1,4 @@
import { withInstall } from '/@/utils';
import customComponent from './src/CustomComponent.vue';
export const CustomComponent = withInstall(customComponent);

View File

@ -0,0 +1,60 @@
<template>
<component :is="component" v-model:value="modelValue" v-bind="props"/>
</template>
<script lang="ts" setup>
import { computed, ref, watch, defineAsyncComponent, onMounted} from 'vue';
const props = defineProps({
defaultValue: {
type: Object
},
value: {
type: Object
},
component: Object,
formData: Object, //主表
row: Object, //子表
});
const modelValue = ref(null);
const component = ref(
defineAsyncComponent({
loader: () => props.component
})
);
const emit = defineEmits(['update:value', 'change', 'blur']);
onMounted(() => {
})
watch(
() => props.defaultValue,
(val) => {
if(val){
emit('update:value', val);
}
},
{
immediate: true,
},
);
watch(
() => props.value,
(val) => {
modelValue.value = val;
},
{
immediate: true,
},
);
watch(
modelValue,
(val) => {
emit('update:value', val);
},
{
deep: true
}
)
</script>

View File

@ -37,7 +37,7 @@
</a-select>
</a-form-item>
<a-form-item :label="t('绑定字段')" required>
<a-select v-model:value="item.bindField" :placeholder="t('请选择表字段')" size="mini">
<a-select v-model:value="item.bindField" showSearch :placeholder="t('请选择表字段')" size="mini">
<a-select-option v-for="(field, idx) in tableField" :value="field.name" :key="idx" />
</a-select>
</a-form-item>

View File

@ -18,7 +18,7 @@
</a-select>
</a-form-item>
<a-form-item v-if="(data.type == 'input' && !data.options.isSave) || (!noHaveField.includes(data.type) && data.type !== 'input')" :label="t('绑定字段')">
<a-select v-model:value="data.bindField" :placeholder="t('请选择表字段')" size="mini">
<a-select v-model:value="data.bindField" showSearch :placeholder="t('请选择表字段')" size="mini">
<a-select-option v-for="(field, idx) in fieldsInfo" :key="idx" :value="field.name">
{{ field.name }}
<span>
@ -467,6 +467,15 @@
<a-form-item v-if="hasKey('span') && (!data.isSubFormChild || !data.isSingleFormChild)" label="标签宽度">
<a-input-number v-model:value="data.options.span" :max="24" :min="0" addonAfter="/ 24" @change="handleSpanChange" />
</a-form-item>
<a-form-item v-if="hasKey('mode')" label="模式选择">
<a-select
ref="select"
v-model:value="data.options.mode"
>
<a-select-option value="">单选</a-select-option>
<a-select-option value="multiple">多选</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="hasKey('range')" :label="t('双滑块模式')">
<a-switch v-model:checked="data.options.range" @change="handleSliderModeChange" />

View File

@ -247,6 +247,7 @@ export const advanceComponents = [
showSearch: false,
clearable: false,
disabled: false,
mode: 'multiple',
staticOptions: [
{
key: 1,

View File

@ -24,6 +24,7 @@ import SelectDepartment from './components/SelectDepartment.vue';
import SelectDepartmentV2 from './components/SelectDepartmentV2.vue';
import SelectUser from './components/SelectUser.vue';
import SelectUserV2 from './components/SelectUserV2.vue';
import SelectUserShowTree from './components/SelectUserShowTree.vue';
import CommonInfo from './components/CommonInfo.vue';
import SelectArea from './components/SelectArea.vue';
import AutoCodeRule from './components/AutoCodeRule.vue';
@ -66,6 +67,7 @@ import { XjrDatePicker } from '/@/components/DatePicker';
import { Slider } from '/@/components/Slider';
import { CodeTextArea } from '/@/components/Input';
import { OneForOne } from '/@/components/OneForOne';
import { CustomComponent } from '/@/components/CustomComponent';
import SubForm from './components/SubFormV2.vue';
import ErpApply from './components/ErpApply.vue';
import ErpUpload from './components/ErpUpload.vue';
@ -102,6 +104,7 @@ componentMap.set('Rate', Rate);
componentMap.set('DeptTree', SelectDepartment);
componentMap.set('Dept', SelectDepartmentV2);
componentMap.set('User', SelectUserV2);
componentMap.set('UserTree', SelectUserShowTree);
componentMap.set('Info', CommonInfo);
componentMap.set('Area', SelectArea);
componentMap.set('SubForm', SubForm);
@ -145,6 +148,7 @@ componentMap.set('ErpApply', ErpApply);
componentMap.set('ErpUpload', ErpUpload);
componentMap.set('ErpCheck', ErpCheck);
componentMap.set('AutoComplete', AutoComplete);
componentMap.set('CustomComponent', CustomComponent);
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);

View File

@ -1,12 +1,12 @@
<template>
<div class="field-readonly">
<div :class="isWordWrap ? '' : 'field-readonly'" :title="fieldValue">
<div v-if="schema.component === 'RichTextEditor'" v-html="htmlValue"> </div>
{{ fieldValue }}
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ref, watch, computed } from 'vue';
const props = defineProps({
value: String,
@ -17,6 +17,14 @@
const textComponents = ['Input', 'AutoCodeRule', 'DatePicker', 'TimePicker', 'Text', 'InputTextArea', 'InputNumber'];
const fieldValue = ref(genFieldValue(props.value));
const htmlValue = ref(getHtmlValue(props.value));
const isWordWrap = computed(() => {
const schema = props.schema;
const { componentProps } = schema;
if (componentProps?.wordWrap) {
return componentProps.wordWrap;
}
return false;
})
function parseRangeVal(val, component) {
if (component !== 'RangePicker' || !val) {
@ -75,8 +83,10 @@
);
</script>
<style>
<style lang="less" scoped>
.field-readonly {
white-space: pre-wrap;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis
}
</style>

View File

@ -1,6 +1,9 @@
<template>
<div class="depart-select" style="width: 100%" @click="show">
<a-input v-model:value="departNames" :bordered="bordered" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
<div v-if="disabled && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="departNames">
{{departNames}}
</div>
<a-input v-model:value="departNames" v-if="disabledShowBorder || !disabled" :bordered="bordered" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
<template v-if="prefix" #prefix>
<Icon :icon="prefix" />
</template>
@ -81,7 +84,16 @@
justCompany: {
type: Boolean,
default: false
}
},
wordWrap:{
type: Boolean,
default: false
},
disabledShowBorder: {
type: Boolean,
default: false
},
formData:Object
});
const selectedNodes = ref([]);
const loading = ref(true);
@ -181,7 +193,11 @@
visible.value = true;
loading.value = true;
if(props.defaultDeptField) {
defaultDepts.value = props.row[props.defaultDeptField]
if(props.row) {
defaultDepts.value = props.row[props.defaultDeptField];
}else{
defaultDepts.value = props.formData[props.defaultDeptField];
}
}
if (props.value) {
if(props.isArray && !props.value.length) {
@ -227,6 +243,11 @@
}
</style>
<style lang="less" scoped>
.field-readonly {
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis
}
.choose-dep-box {
display: flex;
height: 95%;

View File

@ -1,13 +1,14 @@
<template>
<div class="user-select-list">
<div class="user-select-item" v-for="item in data" @click="selectItem(item)">
<template v-for="item in data">
<div class="user-select-item" @click="selectItem(item)" :class="isDisabledItem(item) ? 'disabled-item' : ''">
<div class="user-select-item-left" v-if="showDep && item.departmentPathName">
{{ `${item.name}${item.departmentPathName}` }}
</div>
<div class="user-select-item-left" v-else>
{{ `${item.name}` }}
</div>
<div class="user-select-item-right">
<div class="user-select-item-right" v-if="!isDisabledItem(item)">
<div class="select-circle" :class="item.selected ? 'selected' : ''" v-if="!viewList">
<check-outlined v-if="item.selected" />
</div>
@ -16,6 +17,8 @@
</div>
</div>
</div>
</template>
<div class="empty-box" v-if="!data.length">
<a-empty :image="simpleImage">
<template #description>
@ -58,8 +61,18 @@ const props = defineProps({
type: Boolean,
default: false
},
disabledSelectList: {
type: Array,
default: () => []
},
})
function isDisabledItem(item) {
return props.disabledSelectList.includes(item.id)
}
function selectItem(item) {
if(isDisabledItem(item)) {
return
}
emits('selectId', item.id)
}
function delItem(item) {
@ -83,6 +96,9 @@ function delItem(item) {
// margin: auto;
}
}
.disabled-item {
background: #e3e3e3;
}
@ -102,11 +118,14 @@ function delItem(item) {
overflow: hidden; //超出隐藏
text-overflow: ellipsis; //文本超出时显示省略号
white-space: nowrap; //设置文本不换行
flex: 1;
}
.user-select-item-right {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
.select-circle {
width: 16px;

View File

@ -0,0 +1,143 @@
<template>
<div class="selectUser-show-tree">
<div class="content">
<div class="user-list" v-if="treeData.length" :style="{height: height, width: width}">
<a-tree v-model:expandedKeys="expandedKeys" :treeData="treeData" :key="treeKey" :fieldNames="{
children: 'children',
title: 'name',
key: 'id'
}">
</a-tree>
</div>
<a-button style="margin-top: 10px" @click="showDialog" v-if="!disabled">选择用户</a-button>
</div>
<SelectUserV2
ref="selectUser"
v-model:value="selected"
v-bind="filteredProps"
justDialog
@change="changeSelect"
/>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import SelectUserV2 from './SelectUserV2.vue'
import { getUserByDepartTree } from '/@/api/system/user';
import { cloneDeep } from 'lodash-es';
const emits = defineEmits(['update:value', 'change']);
const props = defineProps({
value: {
type: String,
default: ''
},
prefix: String,
suffix: String,
placeholder: String,
readonly: Boolean,
disabled: Boolean,
size: String,
justDialog: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: true
},
row: Object, // 行数据,在表格里用的到
sepTextField: String, // 将文本表示存储在独立字段
onlyUserCompany: { // 仅在登录用户公司范围内筛选
type: Boolean,
default: false
},
buttonShow: {
type: Boolean,
default: false
},
defaultDeptField: { // 默认选择公司范围key值
type: String,
default: ''
},
justCompany: {
type: Boolean,
default: false
},
isOnlyCurrentDepartment: { // 仅当前部门,无下级部门
type: Boolean,
default: true
},
height: {
type: String,
default: '500px'
},
width: {
type: String,
default: '100%'
}
});
// 过滤掉value属性避免冲突
const filteredProps = computed(() => {
const { value, ...rest } = props;
return rest;
});
const treeKey = ref(1)
const selected = ref(props.value || [])
const selectUser = ref()
const expandedKeys = ref([])
const treeData = ref([])
const changeSelect = async (ids, options) => {
emits('update:value', ids)
emits('change', ids, options)
}
async function getTreeData(list) {
let tree = await getUserByDepartTree(list)
expandedKeys.value = []
setTreeData(tree)
treeData.value = tree
treeKey.value = Date.now()
}
const setTreeData = (list, indexList = []) => {
list.forEach((item, index) => {
let list = cloneDeep(indexList)
list.push(index)
item.id = list.join('-')
expandedKeys.value.push(item.id)
if(item.children && item.children.length){
setTreeData(item.children, cloneDeep(list))
} else if (item.users.length) {
item.children = item.users
}
})
}
const showDialog = () => {
selectUser.value.show()
}
// 监听props.value变化
watch(() => props.value, (newVal) => {
selected.value = newVal
if(selected.value) {
getTreeData(selected.value)
}
})
</script>
<style lang="less" scoped>
.selectUser-show-tree {
.content{
.user-list {
overflow: auto;
border: 1px solid #e5e5e5;
}
}
}
</style>

View File

@ -1,7 +1,10 @@
<template>
<div :class="{ disabled }" class="form-select-user" @click="show">
<a-button type="primary" v-if="buttonShow">{{ t('添加人员') }}</a-button>
<a-input v-model:value="userNames" v-if="!buttonShow" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
<a-button type="primary" v-if="buttonShow && !justDialog">{{ t('添加人员') }}</a-button>
<div v-if="disabled && !buttonShow && !justDialog && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="userNames">
{{userNames}}
</div>
<a-input v-model:value="userNames" v-if="!buttonShow && !justDialog && (disabledShowBorder || !disabled)" :placeholder="placeholder" :size="size" readonly>
<template v-if="prefix" #prefix>
<Icon :icon="prefix" />
</template>
@ -9,7 +12,7 @@
<Icon :icon="suffix" />
</template>
</a-input>
<ModalPanel :title="t('选择人员')" :visible="visible" :width="900" class="select-user-model" @close="close" @submit="submit">
<ModalPanel :title="t('选择人员')" :visible="visible" :width="900" class="select-user-model" @close="close" @submit="submit" :confirmLoading="confirmLoading">
<div class="select-user">
<div class="select-user-left">
<a-tabs v-model:activeKey="activeKey" :tabBarGutter="20">
@ -29,20 +32,20 @@
</div>
</div>
<div class="user-select-box">
<div class="select-depart-all" @click="selectAll">
<div class="select-depart-all" @click="selectAll" v-if="multiple">
<span>全选</span>
<div class="select-circle" :class="departAllSelected ? 'selected' : ''" v-if="!viewList">
<check-outlined v-if="departAllSelected" />
</div>
</div>
<SelectUserListV2 :multiple="multiple" :data="searchDepartMemberList" emptyDescription="暂无人员" @selectId="changeDepMemberSelect"></SelectUserListV2>
<SelectUserListV2 :multiple="multiple" :data="searchDepartMemberList" emptyDescription="暂无人员" @selectId="changeDepMemberSelect" :disabledSelectList="disabledSelectList"></SelectUserListV2>
<div v-if="false" class="user-select-pagination">
<a-pagination v-model:current="searchDepartMemberParams.limit" :pageSize="searchDepartMemberParams.size" :total="searchDepartMemberTotal" />
</div>
</div>
</div>
<div v-show="activeKey === 'allPerson'" class="all-user-select-box">
<SelectUserListV2 :multiple="multiple" :data="searchAllMemberList" @selectId="changeMemberSelect" show-dep></SelectUserListV2>
<SelectUserListV2 :multiple="multiple" :data="searchAllMemberList" @selectId="changeMemberSelect" show-dep :disabledSelectList="disabledSelectList"></SelectUserListV2>
<div v-if="searchAllMemberTotal > 25" class="all-user-select-pagination">
<a-form-item label="" name="pagination">
<a-pagination
@ -64,14 +67,14 @@
<!-- <div class="selected-user-title sub-title">
已选列表
</div> -->
<SelectUserListV2 :data="selectedMemberList" canDel emptyDescription="暂无已选择人员<br> 请从左侧添加人员" viewList @delId="delMember"></SelectUserListV2>
<SelectUserListV2 :data="selectedMemberList" canDel emptyDescription="暂无已选择人员<br> 请从左侧添加人员" viewList @delId="delMember" :disabledSelectList="disabledSelectList"></SelectUserListV2>
</div>
</div>
</ModalPanel>
</div>
</template>
<script setup>
import { watch, ref } from 'vue';
import { watch, ref, defineExpose } from 'vue';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
import { ModalPanel } from '/@/components/ModalPanel/index';
@ -104,6 +107,10 @@
readonly: Boolean,
disabled: Boolean,
size: String,
justDialog: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: true
@ -129,6 +136,26 @@
isOnlyCurrentDepartment: { // 仅当前部门,无下级部门
type: Boolean,
default: true
},
submitClose: {
type: Boolean,
default: true
},
disabledShowBorder: {
type: Boolean,
default: false
},
wordWrap: {
type: Boolean,
default: false
},
lastSelectedDisabled: {
type: Boolean,
default: false
},
confirmLoading: {
type: Boolean,
default: false
}
});
let timeoutId = null;
@ -136,6 +163,7 @@
const searchPlaceholder = '请输入姓名或工号搜索';
const defaultDepts = ref('')
const departAllSelected = ref(false)
const disabledSelectList = ref([])
if(props.onlyUserCompany) {
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
@ -180,6 +208,9 @@
watch(
props,
async () => {
if(props.lastSelectedDisabled) {
disabledSelectList.value = props.value.split(',')
}
if (props.value && !valChanged.value && props.sepTextField) {
const idArr = props.value.split(',');
let valStr = props.row[camelCaseString(props.sepTextField)];
@ -196,7 +227,7 @@
resetMemberList = cloneDeep(initValue);
return;
}
if (props.value && !resetMemberList.length) {
if (props.value && !Array.isArray(props.value) && !resetMemberList.length) {
const list = await getUserMulti(props.value);
selectedMemberList.value = list;
resetMemberList = cloneDeep(list);
@ -282,11 +313,17 @@
async function getUserList(params) {
return await getUserPageListNew(params);
}
function isDisabledItem(item) {
return disabledSelectList.value.includes(item.id)
}
function selectAll() {
departAllSelected.value = !departAllSelected.value
if(departAllSelected.value) {
searchDepartMemberList.value.forEach(item => {
if(isDisabledItem(item)) {
return
}
if(!item.selected) {
selectedMemberList.value.push(item)
}
@ -294,6 +331,9 @@
})
} else {
searchDepartMemberList.value.forEach(item => {
if(isDisabledItem(item)) {
return
}
if(item.selected) {
selectedMemberList.value = selectedMemberList.value.filter(m => m.id !== item.id)
}
@ -380,8 +420,10 @@
emits('selectedId', ids);
emits('change', ids, selectedMemberList.value);
valChanged.value = false;
if(props.submitClose) {
close();
}
}
function close() {
selectedMemberList.value = cloneDeep(resetMemberList);
@ -390,6 +432,10 @@
searchDepartMemberList.value = [];
searchAllMemberList.value = [];
}
defineExpose({
show,
close
})
</script>
<style lang="less">
.select-user-model {
@ -407,6 +453,11 @@
justify-content: space-between;
color: rgba(144, 147, 153, 0.7);
}
.field-readonly {
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis
}
.select-user {
display: flex;

View File

@ -52,24 +52,35 @@
<template v-else-if="column.key !== 'index'">
<component
v-show="column.width!==0"
:is="componentMap.get(column.componentType)"
:key="column.dataIndex + record['_key_']"
v-model:value="record[column.dataIndex]"
:title="readonlySupport(column.componentType) ? record[column.dataIndex] : ''"
:bordered="showComponentBorder"
:index="index"
:mainKey="mainKey"
:row="record"
v-bind="getComponentsProps(column.componentProps, column.dataIndex, record, index)"
v-bind="{...getComponentsProps(column.componentProps, column.dataIndex, record, index), placeholder: getComponentsProps(column.componentProps, column.dataIndex, record, index).disabled ? '' : getComponentsProps(column.componentProps, column.dataIndex, record, index).placeholder}"
@blur="onFieldBlur(column, record, index)"
@change="onFieldChange(column, record, index)"
/>
</template>
</FormItem>
</template>
<template v-if="column.key === 'action' && !disabled">
<template v-if="column.key === 'action'">
<template v-if="column.actions?.length > 0">
<div class="tag-wrapper">
<template v-for="action in column.actions">
<a-tag class="custom-tag" v-if="showButton(action.show,{record,records:data,formModel,disabled})" @click="action.type === 'delete' ? remove(record) : action.click({record,records:data,formModel,disabled})" :color="action?.color ? action.color : 'blue'">{{action.label}}</a-tag>
</template>
</div>
</template>
<template v-else-if="!disabled">
<MinusCircleOutlined style="padding-bottom: 20px" @click="remove(record)" />
</template>
</template>
</template>
</a-table>
<div class="tbl-toolbar">
@ -92,6 +103,7 @@
:isSubFormUse="true"
:params="{ itemId }"
popupType="preload"
:backPagination="backPagination"
@submit="renderSubFormList"
/>
</FormItemRest>
@ -182,6 +194,8 @@
},
// 是否开启分页
showPagination: Boolean,
//后端分页
backPagination: Boolean,
/**
* 选数据按钮名称
*/
@ -197,7 +211,8 @@
//add after hooks
addAfter: Function,
//表头合并数据
multipleHeads: { type: Array as PropType<MutipleHeadInfo[]> }
multipleHeads: { type: Array as PropType<MutipleHeadInfo[]> },
backPagination: { type: Boolean }
});
const data = ref<Recordable[]>([]);
@ -207,14 +222,18 @@
const headColums = ref<MutipleHeadInfo[]>([]); // 多表头
const originHeads = ref<MutipleHeadInfo[]>([]); // 多表头源数据
const columns = ref<SubFormColumn[]>(props.columns);
const allColumns = ref([])
// 注入表单数据
const formModel = inject<any>('formModel', null);
function readonlySupport(name) {
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker)$/.test(name);
}
const onFieldChange = (column, row, rowIndex) => {
const evt = column?.componentProps?.events?.change;
if (evt) {
evt({ column, row, rowIndex, formModel });
evt({ column, row, rowIndex, formModel, columns: headColums.length > 0 ? headColums : columns });
}
};
@ -235,7 +254,7 @@
}
function addDataKey(rows) {
rows.forEach((row) => {
rows?.forEach((row) => {
if (!row['_key_']) {
row['_key_'] = buildUUID();
}
@ -255,6 +274,7 @@
});
}
setColWidth(columns);
allColumns.value = cloneDeep(columns.value)
columns.value = filterColum(columns.value);
nextTick(() => {
//处理多表头
@ -271,6 +291,7 @@
watch(
() => props.columns,
(val) => {
allColumns.value = cloneDeep(val)
columns.value = filterColum(val);
setColWidth(columns);
}
@ -382,11 +403,20 @@
emit('update:value', unref(data));
};
const showButton=(show,obj)=>{
if(show===undefined||show===null||show===''||show===true||show==='true'){
return true;
}else if(isFunction(show)){
return show(obj);
}
return false;
}
const renderSubFormList = async (list) => {
list?.forEach((x) => {
const dataObj = {};
columns.value?.map((item) => {
if (!item?.dataIndex) return;
allColumns.value?.map((item) => {
if (!item?.dataIndex || !item.componentProps.isShow) return;
dataObj[item.dataIndex as string] = item.componentProps?.prestrainField ? x[item.componentProps.prestrainField] : null;
});
@ -652,4 +682,14 @@
.tbl-toolbar {
margin-top: 10px;
}
.tag-wrapper{
display: flex;
gap: 5px;
}
.custom-tag{
cursor: pointer;
display: inline-block;
}
</style>

View File

@ -168,6 +168,10 @@
type: Boolean,
default: true
},
/**
* 按钮组 [{name: '按钮', click: function(){}}]
*/
buttons: Array,
// 是否开启分页
showPagination: Boolean,
/**
@ -197,6 +201,7 @@
const headColums = ref<MutipleHeadInfo[]>([]); // 多表头
const originHeads = ref<MutipleHeadInfo[]>([]); // 多表头源数据
const columns = ref<SubFormColumn[]>(props.columns);
const allColumns = ref([])
// 注入表单数据
const formModel = inject<any>('formModel', null);
@ -224,6 +229,9 @@
}
function addDataKey(rows) {
if (!rows) {
return [];
}
rows.forEach((row) => {
if (!row['_key_']) {
row['_key_'] = Math.random();
@ -244,6 +252,7 @@
});
}
setColWidth(columns);
allColumns.value = cloneDeep(columns.value)
columns.value = filterColum(columns.value);
nextTick(() => {
//处理多表头
@ -260,6 +269,7 @@
watch(
() => props.columns,
(val) => {
allColumns.value = cloneDeep(val)
columns.value = filterColum(val);
setColWidth(columns);
}
@ -327,6 +337,29 @@
}
});
}
const removeById = (id) => {
data.value = deleteNodeById(data.value, id);
};
function deleteNodeById(tree, idToDelete) {
function deepCloneAndFilter(nodes) {
return nodes.map((node) => {
if (node.id === idToDelete) {
return null; // 当前节点要删除
}
// 递归处理 children
const newChildren = node.children ? deepCloneAndFilter(node.children) : [];
return {
...node,
children: newChildren.length ? newChildren : undefined
};
}).filter(Boolean);
}
return deepCloneAndFilter(tree);
}
const add = () => {
//给各个组件赋默认值
@ -364,17 +397,25 @@
}
};
const remove = (index) => {
const remove = (record) => {
let index;
if (typeof record === 'number' || typeof record === 'string') {
index = Number(record);
} else {
index = data.value.findIndex((r) => r._key_ === record._key_);
}
if (index !== -1 && index < data.value.length) {
data.value.splice(index, 1);
emit('change', unref(data));
emit('update:value', unref(data));
}
};
const renderSubFormList = async (list) => {
list?.forEach((x) => {
const dataObj = {};
columns.value?.map((item) => {
if (!item?.dataIndex) return;
allColumns.value?.map((item) => {
if (!item?.dataIndex || !item.componentProps.isShow) return;
dataObj[item.dataIndex as string] = item.componentProps?.prestrainField ? x[item.componentProps.prestrainField] : null;
});
@ -562,7 +603,8 @@
data,
headColums,
columns,
renderSubFormList
renderSubFormList,
removeById
}
}
}

View File

@ -48,7 +48,7 @@
</a-upload>
</div>
<a-upload
:file-list="fileList"
:file-list="fileListWithHeader"
:maxCount="maxNumber"
:accept="accept"
:name="name"
@ -75,16 +75,46 @@
</div>
<template #itemRender="{ file, actions }">
<template v-if="file.__header&&showDownloadIcon">
<div class="file-list-header" style="display: flex; align-items: center; padding: 4px 0;">
<input
type="checkbox"
:checked="isAllSelected"
@change="toggleSelectAll"
style="margin-right: 8px;"
/>全选
<a-button
type="primary"
size="small"
:disabled="!selectedIds.length"
@click="handleBatchDownload"
style="margin-left: 8px;"
>批量打包下载</a-button>
</div>
</template>
<template v-else>
<a-space class="file-space">
<input
v-if="showDownloadIcon"
type="checkbox"
:checked="selectedIds.includes(file.id)"
@change="e => toggleSelectOne(file.id, e)"
style="margin-right: 8px;"
/>
<PaperClipOutlined/>
<span class="file-name-span" @click="actions.preview">{{ file.name }}</span>
<a-tooltip v-if="showDownloadIcon" title="下载"><span @click="actions.download" class="file-outlined-span"><DownloadOutlined /></span></a-tooltip>
<a-tooltip v-if="!disabled && showRemoveIcon" title="删除"><span @click="actions.remove" class="file-outlined-span"><DeleteOutlined /></span></a-tooltip>
<a-tooltip v-if="showDownloadIcon" title="下载">
<span @click="actions.download" class="file-outlined-span"><DownloadOutlined /></span>
</a-tooltip>
<a-tooltip v-if="!disabled && showRemoveIcon" title="删除">
<span @click="actions.remove" class="file-outlined-span"><DeleteOutlined /></span>
</a-tooltip>
<a-tooltip v-if="'.doc,.docx,.xls,.xlsx,.pdf'.includes(file.fileType)" title="编辑文档">
<span @click="editFile(file)" class="file-outlined-span"><EditOutlined /></span>
</a-tooltip>
</a-space>
</template>
</template>
</a-upload>
<a-modal
@ -110,11 +140,11 @@
</div>
</template>
<script lang="ts" setup>
import { nextTick, ref, watch } from 'vue';
import { nextTick, ref, watch, computed } from 'vue';
import { Upload } from 'ant-design-vue';
import { UploadOutlined, PlusOutlined, DownloadOutlined, DeleteOutlined, EditOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import {deleteSingleFile, getAppToken, getFileList, getOnlineEditUrl} from '/@/api/system/file';
import {deleteSingleFile, getAppToken, getFileList, getOnlineEditUrl, getZipFiles} from '/@/api/system/file';
import { downloadByUrl } from '/@/utils/file/download';
import { uploadMultiApi } from '/@/api/sys/upload';
import Icon from '/@/components/Icon/index';
@ -122,8 +152,12 @@
import { getAppEnvConfig } from '/@/utils/env';
import WebOfficeSDK from "/@/assets/libs/open-jssdk-v0.1.3.es.js";
import {getToken} from "/@/utils/auth";
import { useRoute } from 'vue-router';
const route = useRoute();
const { VITE_GLOB_UPLOAD_ALERT_TIP } = getAppEnvConfig();
const { createSuccessModal, } = useMessage();
const props = defineProps({
value: String,
maxNumber: Number,
@ -346,6 +380,52 @@
previewVisible.value = false;
previewTitle.value = '';
};
const selectedIds = ref<string[]>([]);
const isAllSelected = computed(() => fileList.value.length > 0 && selectedIds.value.length === fileList.value.length);
const fileListWithHeader = computed(() => {
// 只在有文件时插入头部
if (fileList.value.length) {
return [{ __header: true, uid: '__header__' }, ...fileList.value];
}
return fileList.value;
});
function toggleSelectAll(e: Event) {
const checked = (e.target as HTMLInputElement).checked;
selectedIds.value = checked ? fileList.value.map(f => f.id) : [];
}
function toggleSelectOne(id: string, e: Event) {
const checked = (e.target as HTMLInputElement).checked;
if (checked) {
selectedIds.value = [...selectedIds.value, id];
} else {
selectedIds.value = selectedIds.value.filter(item => item !== id);
}
}
async function handleBatchDownload() {
if (!selectedIds.value.length) return;
// getZipFiles 返回下载url
let formName = '';
try {
formName = route.query.formName as string || '';
// 获取当前页面得form name
} catch (error) {
console.warn(error);
}
const res = await getZipFiles({ fileIds: selectedIds.value.join(',') , insertionFileName: formName});
if (!res) {
notification.error({
message: 'Tip',
description: '批量下载失败,请稍后重试!',
});
return;
} else if (res.type === 'async') {
createSuccessModal({ title: 'Tip', content: res.msg });
return;
} else if (res.type === 'synced') {
downloadByUrl({ url: res.url, fileName: res.name || 'files.zip' });
}
}
</script>
<style lang="less" scoped>
.list-upload {

View File

@ -127,6 +127,7 @@ export type ComponentType =
| 'DeptTree'
| 'Dept'
| 'User'
| 'UserTree'
| 'Info'
| 'Area'
| 'SubForm'
@ -158,6 +159,7 @@ export type ComponentType =
| 'ErpCheck'
| 'FormView'
| 'XjrIframe'
| 'CustomComponent'
| 'TableLayout';
/**

View File

@ -1,15 +1,34 @@
<template>
<a-modal
ref="modalRef"
:visible="visible"
:title="title"
:maskClosable="false"
:width="hasLeftSlot ? 1200 : width || 600"
:okText="t('确定')"
:cancelText="t('取消')"
@ok="$emit('submit')"
@cancel="$emit('close')"
:confirmLoading="confirmLoading"
@ok="handleOk"
@cancel="handleCancel"
:getContainer="draggable ? undefined : 'body'"
:wrap-style="draggable ? { overflow: 'hidden' } : {}"
>
<slot name="header"></slot>
<template #title>
<div
ref="modalTitleRef"
:style="{ width: '100%', cursor: draggable ? 'move' : 'default' }"
>
{{ title }}
</div>
</template>
<template #modalRender="{ originVNode }">
<div :style="draggable ? transformStyle : {}">
<component :is="originVNode" />
</div>
</template>
<div class="content">
<div :class="['left', isDeptSelect ? 'left-box' : '']" v-if="hasLeftSlot">
<slot name="left"></slot>
@ -21,19 +40,99 @@
</a-modal>
</template>
<script setup lang="ts">
import { computed, useSlots } from 'vue';
import { computed, useSlots, ref, watch } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDraggable } from '@vueuse/core';
const { t } = useI18n();
defineEmits(['submit', 'close']);
defineProps({
const emit = defineEmits(['submit', 'close']);
const props = defineProps({
title: String,
width: Number,
visible: { type: Boolean, default: false },
isDeptSelect: { type: Boolean, default: false },
confirmLoading: { type: Boolean, default: false },
draggable: { type: Boolean, default: false }
});
const modalRef = ref(null);
const modalTitleRef = ref(null);
const hasLeftSlot = computed(() => {
return !!useSlots().left;
});
const startX = ref(0);
const startY = ref(0);
const startedDrag = ref(false);
const transformX = ref(0);
const transformY = ref(0);
const preTransformX = ref(0);
const preTransformY = ref(0);
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 });
const { x, y, isDragging } = useDraggable(modalTitleRef, {
enabled: computed(() => props.draggable)
});
const handleOk = () => {
emit('submit');
};
const handleCancel = () => {
emit('close');
};
watch([x, y], () => {
if (!props.draggable || !startedDrag.value) {
startX.value = x.value;
startY.value = y.value;
const bodyRect = document.body.getBoundingClientRect();
if (modalTitleRef.value) {
const titleRect = modalTitleRef.value.getBoundingClientRect();
dragRect.value.right = bodyRect.width - titleRect.width;
dragRect.value.bottom = bodyRect.height - titleRect.height;
}
preTransformX.value = transformX.value;
preTransformY.value = transformY.value;
}
startedDrag.value = true;
});
// 监听拖拽状态变化
watch(isDragging, (newVal) => {
if (!newVal) {
startedDrag.value = false;
}
});
watch(
() => props.visible,
(newVisible) => {
if (!newVisible) {
transformX.value = 0;
transformY.value = 0;
}
}
);
const transformStyle = computed(() => {
if (!props.draggable) return {};
if (startedDrag.value) {
transformX.value =
preTransformX.value +
Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
startX.value;
transformY.value =
preTransformY.value +
Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
startY.value;
}
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
};
});
</script>
<style lang="less" scoped>
.content {
@ -49,6 +148,7 @@
.right {
flex: 1;
width: 100%;
}
}

View File

@ -1,7 +1,11 @@
<template>
<div :class="{ disabled }" class="multiple-popup">
<div v-if="disabled && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="popupValue">
{{popupValue}}
</div>
<a-input
v-model:value="popupValue"
v-if="disabledShowBorder || !disabled"
:addonAfter="addonAfter"
:addonBefore="addonBefore"
:bordered="bordered"
@ -35,6 +39,7 @@
:mainKey="mainKey"
:params="params"
:popupType="popupType"
:popupTitle="popupTitle"
:subTableIndex="index"
:valueField="valueField"
:backPagination="backPagination"
@ -68,6 +73,7 @@
const props = defineProps({
popupType: { type: String },
popupTitle: { type: String },
value: { type: String },
labelField: { type: String, default: 'label' },
valueField: { type: String, default: 'value' },
@ -247,3 +253,11 @@
}
};
</script>
<style lang="less" scoped>
.field-readonly {
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<a-modal centered :width="1250" :visible="props.multipleDialog" :title="title" :destroyOnClose="true"
@ok="submitDialog" @cancel="closeDialog" :okText="t('确认')" :cancelText="t('取消')" :bodyStyle="{ padding: '20px' }">
@ok="submitDialog" @cancel="closeDialog" :okText="t('确认')" :cancelText="t('取消')" :bodyStyle="{ padding: '20px' }" v-loading="loading">
<a-row :gutter="12" style="margin-bottom: 10px">
<a-col :span="8">
<a-input v-model:value="state.searchText" :placeholder="t('请输入要查询的关键字')" />
@ -28,10 +28,10 @@
<a-table :dataSource="state.dataSourceList" :columns="state.sourceColumns" :row-selection="{
selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange,
}" :pagination="paginationProps" :scroll="{ y: '420px' }" />
}" :pagination="paginationProps" :scroll="{ y: '420px' }" :loading="loading"/>
</a-tab-pane>
<a-tab-pane key="2" :tab="t('已选记录')" force-render>
<a-table :dataSource="selectedList" :columns="state.selectedColumns" :scroll="{ y: '400px' }">
<a-table :dataSource="selectedList" :columns="state.selectedColumns" :scroll="{ y: '400px' }" :loading="loading">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'delete'">
<Icon icon="ant-design:delete-outlined" color="#f56c6c" @click="deleteSelected(record, index)"
@ -41,7 +41,7 @@
</a-table>
</a-tab-pane>
</a-tabs>
<a-table v-else-if="popupType === 'associate'" :dataSource="state.dataSourceList" :columns="state.sourceColumns"
<a-table v-else-if="popupType === 'associate'" :dataSource="state.dataSourceList" :columns="state.sourceColumns" :loading="loading"
:pagination="paginationProps" :row-selection="{
selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange,
@ -64,6 +64,7 @@ const { t } = useI18n();
const props = defineProps({
multipleDialog: { type: Boolean },
popupType: { type: String },
popupTitle: { type: String },
dataSourceOptions: { type: Array },
params: {
type: [Array, Object, String, Number],
@ -105,6 +106,7 @@ onMounted(async () => {
await getDatasourceList(1);
}
});
const loading = ref(false)
const state = reactive({
selectedRowKeys: [] as any[],
@ -127,6 +129,9 @@ const paginationProps = reactive({
const formModel = inject<any>('formModel', null);
const isCamelCase = inject<boolean>('isCamelCase', false);
const title = computed(() => {
if(props.popupTitle){
return t(props.popupTitle);
};
switch (props.popupType) {
case 'multiple':
return t('多选弹层-选择记录');
@ -214,6 +219,9 @@ const resetSearch = () => {
};
const closeDialog = () => {
emit('update:multipleDialog', false);
selectedList.value = [];
state.selectedRowKeys = [];
state.dataSourceList = [];
};
const submitDialog = () => {
@ -291,12 +299,18 @@ const setFormModel = (isNull?) => {
let bindField = !isCamelCase ? item.bindField : camelCaseString(item.bindField);
dataObj[bindField as string] = item.prestrainField ? x[item.prestrainField] : null;
});
if (formModel[table]) formModel[table].push(dataObj);
if (formModel[table]) {
formModel[table].push(dataObj)
} else {
formModel[table] = []
formModel[table].push(dataObj)
};
});
}
};
const getDatasourceList = async (limit = 1) => {
loading.value = true;
paginationProps.current = limit;
let api;
if (props.datasourceType) {
@ -342,8 +356,8 @@ const getDatasourceList = async (limit = 1) => {
}
}
}
loading.value = false;
if (!api || !isFunction(api)) return;
state.dataSourceList = [];
try {
let dataParams = {};
const pageParams = { order: 'desc', size: 10, limit, keyword: state.searchText };
@ -359,9 +373,11 @@ const getDatasourceList = async (limit = 1) => {
pageParams,
);
}
loading.value = true;
const res = await api(dataParams);
state.dataSourceList = res.list;
paginationProps.total = Number(res.total);
loading.value = false;
} catch (error) {
console.warn(error);
}

View File

@ -15,7 +15,7 @@
<span class="node-name">{{node.activityName}}</span>
<a-switch :checked="node.chooseNode" v-if="!node.hiddenNode" style="margin-left: 10px;" @change="agreeNodeChange(node)"></a-switch>
</div>
<a-form-item :required="(flowNextNodes.length === 1 || node.chooseNode)" v-if="_action === 'agree' && !isEnd" :label="'审批人'">
<a-form-item :required="(flowNextNodes.length === 1 || node.chooseNode)" v-if="(_action === 'agree' || _action == 'disagree') && !isEnd" :label="'审批人'">
<a-select v-show="node.chooseAssign" v-model:value="node.assignees" :options="node.nextAssignees" :disabled="loading"
:placeholder="'审批人'" max-tag-count="responsive"
:mode="node.isChooseMulti? 'multiple' : ''"
@ -30,10 +30,12 @@
<a-select-option v-for="(item, index) in rejectNodeList" :key="index" :value="item.activityId">{{ item.activityName }}</a-select-option>
</a-select>
</a-form-item>
<template v-for="node in rejectNodeList">
<a-form-item v-if="_action === 'reject'&&rejectNodeId===node.activityId" label="审批人">
<a-select v-show="node.chooseAssign" v-model:value="node.assignees" :options="node.nextAssignees" :disabled="loading"
:placeholder="'请选择' + node.activityName + '的审批人'" max-tag-count="responsive" mode="multiple"
<template v-for="node in rejectNodeList" :key="node.activityId">
<a-form-item required v-if="_action === 'reject'&&rejectNodeId===node.activityId" label="审批人">
<a-select v-show="node.chooseAssign" v-model:value="node.assignees" :options="node.nextAssignees"
:placeholder="'请选择' + node.activityName + '的审批人'" max-tag-count="responsive"
:disabled="loading"
:mode="node.rejectIsChooseMulti? 'multiple' : ''"
:filterOption="search"
></a-select>
<span v-show="!node.chooseAssign">{{ getAssigneeText(node) }}</span>
@ -81,7 +83,7 @@
}
function getNextNodesName() {
return flowNextNodes.value.length > 1 ? '多个节点,请选择流向节点' : flowNextNodes?.value[0]?.activityName;
return flowNextNodes.value.length > 1 ? '多个节点,请确认流向节点' : flowNextNodes?.value[0]?.activityName;
}
function toggleDialog({ isClose, action, callback, rejectCancel, processId, taskId, nextNodes } = {}) {
@ -251,7 +253,7 @@
}
rejectNodeList.value.forEach((nNode) => {
if(nNode.activityId==rejectNodeId.value){
nextTaskUser[nNode.activityId] = isEnd.value ? '' : nNode.assignees.join(',');
nextTaskUser[nNode.activityId] = isEnd.value ? '' : (typeof(nNode.assignees) == 'string' ? nNode.assignees : nNode.assignees.join(','));
}
});
}

View File

@ -1,5 +1,5 @@
<template>
<a-modal :mask-closable="false" :title="dialogTitle" :visible="isOpen" :width="500" class="geg" centered>
<a-modal :mask-closable="false" :title="dialogTitle" :visible="isOpen" :width="500" class="geg" centered @cancel="onClickCancel">
<template #footer>
<a-button :disabled="loading" @click="onClickCancel">取消</a-button>
<a-button :loading="loading" type="primary" @click="onClickOK">确定</a-button>

View File

@ -1,5 +1,9 @@
<template>
<a-select v-model:value="selectedValue" :filter-option="handleFilterOption" :mode="mode" :options="getOptions" :placeholder="placeholder" allowClear v-bind="$attrs" @change="handleChange" @dropdown-visible-change="handleFetch">
<div>
<div v-if="disabled && !disabledShowBorder" :class="wordWrap ? '' : 'field-readonly'" :title="departNames">
{{departNames}}
</div>
<a-select v-model:value="selectedValue" :filter-option="handleFilterOption" :mode="mode" :options="getOptions" :placeholder="placeholder" allowClear v-bind="$attrs" @change="handleChange" @dropdown-visible-change="handleFetch" v-else>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}"></slot>
</template>
@ -13,6 +17,7 @@
</span>
</template>
</a-select>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, computed, unref, watch, inject, onMounted, watchEffect } from 'vue';
@ -63,7 +68,15 @@
mainKey: String,
index: Number,
sepTextField: String, // 独立存储文本部分
row: Object // 行数据,在明细表里生效
row: Object, // 行数据,在明细表里生效
wordWrap: {
type: Boolean,
default: false
},
disabledShowBorder: {
type: Boolean,
default: false
}
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit }) {
@ -80,6 +93,7 @@
const valChanged = ref(false);
// label分开存储时第一次懒加载会同时处罚两次fetch所以这里延迟执行
const fetch = debounce(_fetch, 200, { leading: false, trailing: true });
const selectName = ref('')
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
@ -156,6 +170,7 @@
() => props.value,
() => {
selectedValue.value = ((typeof props.value === 'string' && !!props.value ? props.value?.split(',') : props.value) || undefined) as any;
updateSepTextField(Array.isArray(selectedValue.value) ? selectedValue.value : [selectedValue.value]);
},
{
immediate: true
@ -163,15 +178,16 @@
);
function updateSepTextField(arr) {
if (!props.sepTextField || !props.row) {
return;
}
const options = unref(getOptions);
const txtArr = options
.filter((opt) => {
return arr.includes(opt.value);
})
.map((item) => item.label);
selectName.value = txtArr.join(',')
if (!props.sepTextField || !props.row) {
return;
}
props.row[camelCaseString(props.sepTextField)] = txtArr.join(',');
}
@ -260,7 +276,6 @@
emit('update:value', val);
emit('change', val, args);
selectedValue.value = props.value === undefined ? val : (((typeof props.value === 'string' && !!props.value ? props.value?.split(',') : props.value) || undefined) as any);
updateSepTextField(Array.isArray(value) ? value : [value]);
}
return {
@ -276,3 +291,11 @@
}
});
</script>
<style lang="less" scoped>
.field-readonly {
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis
}
</style>

View File

@ -52,12 +52,13 @@
import { PostInfo } from '/@/api/system/post/model';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const emits = defineEmits(['change','selectedNodes']);
const props = withDefaults(
defineProps<{
selectedIds: Array<string>;
multiple?: Boolean;
needNode?: Boolean;
}>(),
{
selectedIds: () => {
@ -66,6 +67,9 @@
disabledIds: () => {
return [];
},
needNode: () => {
return false;
},
},
);
let data: {
@ -106,7 +110,10 @@
data.visible = true;
}
function submit() {
emits('change', data.selectedIds);
emits('change', data.selectedIds, data.selectedList);
if(props.needNode && props.needNode === true) {
emits('selectedNodes', data.selectedList);
}
close();
}
function close() {
@ -136,6 +143,7 @@
data.selectedList = [];
} else {
data.selectedIds = [item.id];
data.selectedList = [item]
}
}
}

View File

@ -86,7 +86,9 @@
});
const getSchemas = computed<FormSchema[]>(() => {
return (unref(getProps).schemas as any) || unref(schemaRef);
let schemas = (unref(getProps).schemas as any) || unref(schemaRef)
getComponent(schemas);
return schemas;
});
// Get the basic configuration of the form
@ -110,7 +112,7 @@
}
function getColWidth(schema: any) {
const compProps = schema.componentProps;
const compProps = schema?.componentProps;
if (compProps?.responsive) {
if (compProps.respNewRow) {
return 24; // 响应式布局下独立成行
@ -125,7 +127,7 @@
}
}
}
return schema.colProps?.span;
return schema?.colProps?.span;
}
const debGetWrapSize = debounce(getWrapSize, 300);
@ -148,7 +150,7 @@
});
function showComponent(schema) {
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema.type);
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema?.type) : !noShowGenerateComponents.includes(schema?.type);
}
function getIsShow(schema: FormSchema, itemValue: any): boolean {
@ -175,22 +177,24 @@
}
function getIfShow(schema: FormSchema, itemValue: any): boolean {
const { ifShow } = schema;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
const { componentProps, show } = schema;
let isShow = true;
if (isBoolean(componentProps?.isShow)) {
isShow = componentProps?.isShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow({
// if (isBoolean(show)) {
// isShow = show;
// }
if (isFunction(componentProps?.isShow)) {
isShow = componentProps.isShow({
values: itemValue,
model: formModel!,
schema: schema,
field: schema.field
});
}
return isIfShow;
return isShow;
}
const formModel = reactive<Recordable>(props.formModel);
@ -218,8 +222,6 @@
const refreshFieldObj = ref<object>({});
getComponent(getSchemas.value);
function getComponent(component) {
const layoutComponents = ['tab', 'grid', 'card'];
component?.map((info) => {
@ -253,8 +255,10 @@
function setComponentDefault(item) {
if ((staticDataComponents.includes(item.component) && (item.componentProps as any)?.datasourceType === 'staticData') || (needDicDefaultValue.includes(item.type) && (item.componentProps as any)?.datasourceType === 'dic')) {
if(formModel[item.field]==undefined) {
let { defaultSelect } = item.componentProps as any;
formModel[item.field] = defaultSelect;
}
return;
}
let { defaultValue } = item;
@ -277,7 +281,9 @@
}
return;
} else {
if(formModel[item.field]==undefined){
formModel[item.field] = item.component === 'SubForm' ? [] : defaultValue;
}
return;
}
}
@ -413,7 +419,8 @@
* 修改bug #5090
* 为了保证表单赋值触发所有组件的change事件
*/
const executeEvent = (allSchemas: FormSchema[]) => {
const executeEvent = (allSchemas: FormSchema[] = []) => {
if(!allSchemas) return;
for (const schema of allSchemas) {
//如果是这几个组件 需要查询子级
if (['Card', 'Tab', 'Grid'].includes(schema.component)) {
@ -442,9 +449,9 @@
try {
if (typeof handler === 'string') {
const event = new Function('schema', 'formModel', 'formActionType', 'extParams', handler);
event(schema, formModel, formApi, { formData, allSchemas });
event(schema, formModel, formApi, { formData, allSchemas, isPersonChange: false });
} else if (typeof handler === 'function') {
handler(schema, formModel, formApi, { formData, allSchemas });
handler(schema, formModel, formApi, { formData, allSchemas, isPersonChange: false });
}
} catch (error) {
console.log('error', error);
@ -675,7 +682,7 @@
schema.componentProps.style = { ...schema.componentProps.style, ...style };
};
const formApi: FormActionType = {
const formApi = {
submit,
validate,
clearValidate,
@ -694,9 +701,10 @@
httpRequest,
refreshAPI,
changeStyle,
setDefaultValue
setDefaultValue,
formModel
};
//将表单方法 导出 给父组件使用。
defineExpose<FormActionType>(formApi);
defineExpose(formApi);
</script>

View File

@ -110,7 +110,7 @@
}
function getColWidth(schema: any) {
const compProps = schema.componentProps;
const compProps = schema?.componentProps;
if (compProps?.responsive) {
if (compProps.respNewRow) {
return 24; // 响应式布局下独立成行
@ -125,7 +125,7 @@
}
}
}
return schema.colProps?.span;
return schema?.colProps?.span;
}
const debGetWrapSize = debounce(getWrapSize, 300);
@ -148,7 +148,7 @@
});
function showComponent(schema) {
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema.type);
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema?.type);
}
function getIsShow(schema: FormSchema, itemValue: any): boolean {
@ -175,22 +175,15 @@
}
function getIfShow(schema: FormSchema, itemValue: any): boolean {
const { ifShow } = schema;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
const { componentProps, show } = schema;
let isShow = true;
if (isBoolean(componentProps?.isShow)) {
isShow = componentProps?.isShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow({
values: itemValue,
model: formModel!,
schema: schema,
field: schema.field
});
}
return isIfShow;
// if (isBoolean(show)) {
// isShow = show;
// }
return isShow;
}
const formModel = reactive<Recordable>(props.formModel);
@ -413,7 +406,8 @@
* 修改bug #5090
* 为了保证表单赋值触发所有组件的change事件
*/
const executeEvent = (allSchemas: FormSchema[]) => {
const executeEvent = (allSchemas: FormSchema[] = []) => {
if(!allSchemas) return;
for (const schema of allSchemas) {
//如果是这几个组件 需要查询子级
if (['Card', 'Tab', 'Grid'].includes(schema.component)) {
@ -442,9 +436,9 @@
try {
if (typeof handler === 'string') {
const event = new Function('schema', 'formModel', 'formActionType', 'extParams', handler);
event(schema, formModel, formApi, { formData, allSchemas });
event(schema, formModel, formApi, { formData, allSchemas, isPersonChange: false });
} else if (typeof handler === 'function') {
handler(schema, formModel, formApi, { formData, allSchemas });
handler(schema, formModel, formApi, { formData, allSchemas, isPersonChange: false });
}
} catch (error) {
console.log('error', error);
@ -672,12 +666,15 @@
httpRequest,
refreshAPI,
changeStyle,
setDefaultValue
setDefaultValue,
formModel
};
//将表单方法 导出 给父组件使用。
expose({
...formApi
...formApi,
formModel,
getSchemas
});
return {

View File

@ -31,6 +31,9 @@
:labelAlign="formProps?.labelAlign"
:name="schema.field"
:wrapperCol="itemLabelWidthProp.wrapperCol"
:style="{
overflow:'hidden'
}"
>
<component :is="formComponent(schema)" v-model:value="formModel![schema.field]" :disabled="getDisable" :size="formProps?.size" v-bind="schema.componentProps" />
</FormItem>
@ -84,7 +87,7 @@
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<template v-if="getDisable && readonlySupport(schema.component)">
<readonly :model="formModel" :schema="schema" />
<readonly :schema="schema" :model="formModel"/>
<component
:is="componentMap.get(schema.component)"
v-show="false"
@ -164,11 +167,11 @@
:validateTrigger="['blur', 'change']"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<template v-if="getDisable && readonlySupport(schema.component)">
<template v-if="getDisable && readonlySupport(schema.component) && !getDisabledShowBorder">
<readonly :schema="schema" :value="formModel![schema.field]" />
</template>
<template v-else>
<component :is="defaultComponent(schema)" :key="refreshFieldObj[schema.field]" v-model:value="formModel![schema.field]" :disabled="getDisable" :formData="formModel" :size="formProps?.size" v-bind="getComponentsProps" />
<component :is="defaultComponent(schema)" :key="refreshFieldObj[schema.field]" v-model:value="formModel![schema.field]" :disabled="getDisable" :formData="formModel" :size="formProps?.size" v-bind="{...getComponentsProps, placeholder: getDisable ? '' : getComponentsProps.placeholder, disabledShowBorder: getDisabledShowBorder}" :title="readonlySupport(schema.component) ? formModel![schema.field] : ''"/>
</template>
</FormItem>
</template>
@ -218,7 +221,8 @@
const tabActiveKey = inject<Ref<number>>('tabActiveKey', ref(0));
const activeKey = ref<number>(0);
const isCamelCase = inject<boolean>('isCamelCase', false);
// 注入整个表单的配置formProps是个计算属性不能修改formData则来自每个业务的表单页面。
const formData = inject('formData', { noInject: true });
watch(
() => tabActiveKey?.value,
(val) => {
@ -262,6 +266,17 @@
return disabled;
});
const getDisabledShowBorder = computed(() => {
let disabledShowBorder = false
if (getComponentsProps.value?.disabledShowBorder) {
disabledShowBorder = true;
}
if(import.meta.env?.VITE_GLOB_READ_ONLY_BORDER_DISABLED) {
disabledShowBorder = import.meta.env?.VITE_GLOB_READ_ONLY_BORDER_DISABLED == 'true'
}
return disabledShowBorder
})
const getComponentsProps = computed(() => {
let { componentProps = {} } = props.schema;
@ -356,9 +371,9 @@
// console.log('formitem watch!!!!!!!!');
//填值以后需要手动校验的组件
const validateComponents = ['User', 'RichTextEditor', 'Upload', 'SelectMap'];
if (validateComponents.includes(props.schema.component) && formModel![props.schema.field]) {
if (validateComponents.includes(props.schema?.component) && formModel![props.schema?.field]) {
setTimeout(() => {
props.formApi?.validateFields([props.schema.field]);
props.formApi?.validateFields([props.schema?.field]);
}, 100);
}
},
@ -394,32 +409,53 @@
};
function showComponent(schema) {
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema.type);
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema?.type) : !noShowGenerateComponents.includes(schema?.type);
}
function readonlySupport(name) {
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker|InputTextArea)$/.test(name);
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker)$/.test(name);
}
function getShow(schema: FormSchema): boolean {
const { show } = schema;
let isIfShow = true;
if (isBoolean(show)) {
isIfShow = show;
}
return isIfShow;
}
function getIsShow(schema: FormSchema): boolean {
const { componentProps, show } = schema as any;
const { componentProps, show } = schema;
let isShow = true;
if (isBoolean(componentProps?.isShow)) {
isShow = componentProps?.isShow;
}
// if (isBoolean(show)) {
// isShow = show;
// }
if (isFunction(componentProps?.isShow)) {
isShow = componentProps.isShow({
values:formModel![schema.field],
model: formModel!,
schema: schema,
field: schema.field
});
}
return isShow;
}
function getIsShow(schema: FormSchema): boolean {
const { show } = schema;
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show({
values:formModel![schema.field],
model: formModel!,
schema: schema,
field: schema.field
});
}
isShow = isShow;
return isShow;
}
</script>

View File

@ -53,6 +53,8 @@
const tabActiveKey = inject<Ref<number>>('tabActiveKey', ref(0));
const activeKey = ref<number>(0);
const isCamelCase = inject<boolean>('isCamelCase', false);
// 注入整个表单的配置formProps是个计算属性不能修改formData则来自每个业务的表单页面。
const formData = inject('formData', { noInject: true });
watch(
() => tabActiveKey?.value,
(val) => {
@ -122,7 +124,7 @@
let field = camelCaseString(item);
if (field) cloneFormModel[field] = cloneFormModel[item];
}
event(props.schema, isCamelCase ? cloneFormModel : formModel, props.formApi, {});
event(props.schema, isCamelCase ? cloneFormModel : formModel, props.formApi, { formData });
if (isCamelCase) {
for (let item in formModel) {
@ -188,7 +190,7 @@
// console.log('formitem watch!!!!!!!!');
//填值以后需要手动校验的组件
const validateComponents = ['User', 'RichTextEditor', 'Upload', 'SelectMap'];
if (validateComponents.includes(props.schema.component) && formModel![props.schema.field]) {
if (validateComponents.includes(props.schema?.component) && formModel![props.schema.field]) {
setTimeout(() => {
props.formApi?.validateFields([props.schema.field]);
}, 100);
@ -209,7 +211,7 @@
};
function showComponent(schema) {
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema.type);
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema?.type) : !noShowGenerateComponents.includes(schema?.type);
}
function readonlySupport(name) {
@ -217,24 +219,35 @@
}
function getShow(schema: FormSchema): boolean {
const { show } = schema;
let isIfShow = true;
if (isBoolean(show)) {
isIfShow = show;
}
return isIfShow;
}
function getIsShow(schema: FormSchema): boolean {
const { componentProps, show } = schema as any;
const { componentProps, show } = schema;
let isShow = true;
if (isBoolean(componentProps?.isShow)) {
isShow = componentProps?.isShow;
}
// if (isBoolean(show)) {
// isShow = show;
// }
return isShow;
}
function getIsShow(schema: FormSchema): boolean {
const { show } = schema;
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show({
values: itemValue,
model: formModel!,
schema: schema,
field: schema.field
});
}
isShow = isShow;
return isShow;
}

View File

@ -1,5 +1,5 @@
<template>
<div v-if="visible">
<div v-if="visible" class="system-form">
<component
:is="componentName"
v-if="visible"
@ -28,7 +28,10 @@
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { createFormEvent, loadFormEvent } from '/@/hooks/web/useFormEvent';
import { changeFormJson } from '/@/hooks/web/useWorkFlowForm';
import { useUserStore } from '/@/store/modules/user';
import {message} from "ant-design-vue";
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const props = defineProps({
systemComponent: {
@ -176,6 +179,13 @@
async function getFieldsValue(){
return SystemFormRef.value.getFieldsValue();
}
async function getFormModels() {
try {
return (SystemFormRef.value?.getFormModel && SystemFormRef.value?.getFormModel()) || await validate()
} catch (error) {
throw new Error(error);
}
}
async function getValue(){
let values = null;
if(approvalData.value?.approvedResult === ApproveCode.FINISH){
@ -198,21 +208,31 @@
// 提交表单
if (visible.value) {
let id = await submit(saveRowKey);
if(!id) {
throw new Error(`提交表单失败`);
}
let rowKey = getRowKey();
values[rowKey] = id;
values['_id'] = id;
//重新查一遍
let newValues=await SystemFormRef.value.setFormDataFromId(id,true);
let newValues=await SystemFormRef.value.setFormDataFromId(id);
if(newValues){
values=newValues;
} else {
throw new Error(`获取表单失败`);
}
}
return values;
} catch (error) {}
} catch (error) {
console.error(error)
throw new Error(error);
}
}
async function submit(saveRowKey) {
try {
let saveValId = '';
let values = await SystemFormRef.value.validate();
let rowKey = getRowKey();
@ -221,7 +241,10 @@
}
if (values[rowKey]) {
// 编辑
await SystemFormRef.value.update({ values, rowId: values[rowKey] });
let res = await SystemFormRef.value.update({ values, rowId: values[rowKey] });
if(!res) {
throw new Error(`提交表单失败`);
}
saveValId = values[rowKey];
} else {
// 新增
@ -232,6 +255,9 @@
}
}
return saveValId;
} catch(e) {
}
}
async function setDisabledForm(isDisabled) {
@ -245,13 +271,22 @@
async function handleDelete(id) {
let ret;
try {
if(!SystemFormRef.value?.handleDelete) {
throw new Error(`表单未配置删除`);
}
ret = await SystemFormRef.value.handleDelete(id);
} catch (e) {
message.error('表单未配置删除');
return null;
throw new Error(e);
}
return ret;
}
function handleInnerFun(funcName) {
if(!SystemFormRef.value?.[funcName]) {
message.error(`表单未配置${funcName}方法`);
return
}
SystemFormRef.value[funcName]()
}
defineExpose({
workflowSubmit,
@ -263,7 +298,9 @@
getFieldsValue,
getIsOldSystem,
setDisabledForm,
handleDelete
handleDelete,
getFormModels,
handleInnerFun
});
</script>

View File

@ -4,7 +4,7 @@
background-color: @component-background;
.ant-tree-treenode {
align-items: center;
align-items: center!important;
padding-bottom: 0;
}

View File

@ -39,6 +39,7 @@ import { ref, reactive, onMounted, } from 'vue';
import Preview from '/@/views/workflow/design/Preview.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
import { h } from 'vue';
const props = withDefaults(
defineProps<{
@ -89,6 +90,25 @@ const configColumns = [
title: t('备注'),
dataIndex: 'remark',
width: 180,
customRender: ({ text }) => {
return h(
'a-tooltip',
{ title: text || '' },
[
h('div', {
style: {
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-all',
cursor: 'pointer',
}
}, text || '')
]
);
},
},
{
title: t('创建时间'),
@ -147,6 +167,15 @@ const getHistoryList = async () => {
if (item.chooseCount == 0) {
item.chooseCount = '';//置空
}
// 渲染备注 由json返回
if (item.jsonContent) {
try {
const remarkObj = JSON.parse(item.jsonContent || '{}');
item.remark = remarkObj?.processConfig?.remark || '';
} catch (e) {
item.remark = '';
}
}
});
}
catch (error) {}

View File

@ -0,0 +1,32 @@
/**
* Global authority directive
* Used for fine-grained control of component permissions
* @Example v-auth="RoleEnum.TEST"
*/
import type { App, Directive, DirectiveBinding } from 'vue';
import { usePermission } from '/@/hooks/web/usePermission';
function isAllAuth(el: Element, binding: any) {
const { hasAllPermission } = usePermission();
const value = binding.value;
if (!value) return;
if (!hasAllPermission(value)) {
el.parentNode?.removeChild(el);
}
}
const mounted = (el: Element, binding: DirectiveBinding<any>) => {
isAllAuth(el, binding);
};
const allAuthDirective: Directive = {
mounted,
}
export function setupAllPermissionDirective(app: App) {
app.directive('allAuth', allAuthDirective);
}
export default allAuthDirective;

View File

@ -3,9 +3,11 @@
*/
import type { App } from 'vue';
import { setupPermissionDirective } from './permission';
import { setupAllPermissionDirective } from './allPermission';
import { setupLoadingDirective } from './loading';
export function setupGlobDirectives(app: App) {
setupPermissionDirective(app);
setupAllPermissionDirective(app);
setupLoadingDirective(app);
}

View File

@ -52,7 +52,13 @@ export enum NoHandler {
// 指定审批人
export enum DesignatedApprover {
NOT_SPECIFIED = 0, //不指定审批人
PREVIOUS_NODE, //由上一节点审批人指定
PREVIOUS_NODE = 1, //由上一节点审批人指定
}
// 退回审批人
export enum RejectApprover {
APPROVER = 0, //候选人
PREVIOUS = 1, //审批人
PREVIOUS_LAST = 2, //最终审批人
}
//权限类型
@ -195,13 +201,25 @@ export enum ApproveType {
REJECT, //驳回
FINISH, //结束
OTHER, //其他(用户自定义按钮)
TRANSFER, //转办
ADDSTEP, // 会签
DRAWBACK, // 撤回
FLOWBPMN, // 流程BPMN
DRAFT, // 草稿
FLOWRECORD, // 流程记录
}
export enum ApproveCode {
AGREE = 'agree', //同意
DISAGREE = 'disagree', //拒绝
DISAGREE = 'disagree', //不同意
REJECT = 'reject', //驳回
TRANSFER = 'transfer', // 转办
ADDSTEP = 'addStep', // 会签
DRAWBACK = 'drawBack', // 撤回
FINISH = 'finish', //结束
OTHER = 'other', //其他(用户自定义按钮)
FLOWBPMN = 'flowBpmn', // 流程BPMN
FLOWRECORD='flowRecord', // 流程记录
DRAFT = 'draft'
}
// 流程监控状态
export enum ProcessMonitorStatus {

View File

@ -2,10 +2,11 @@ import type { RouteLocationRaw, Router } from 'vue-router';
import { PageEnum } from '/@/enums/pageEnum';
import { isString } from '/@/utils/is';
import { unref } from 'vue';
import { unref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { REDIRECT_NAME } from '/@/router/constant';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
@ -13,6 +14,8 @@ function handleError(e: Error) {
console.error(e);
}
const specialRouterName = ['CreateFlow', 'ApproveFlow', 'viewForm', 'ProcessMonitoring', 'updateForm', 'createForm']
// page switch
export function useGo(_router?: Router) {
let router;
@ -40,10 +43,11 @@ export function useGo(_router?: Router) {
* @description: redo current page
*/
export const useRedo = (_router?: Router) => {
const tabStore = useMultipleTabStore();
const { push, currentRoute } = _router || useRouter();
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
const { query, params = {}, name, fullPath, path } = unref(currentRoute.value);
function redo(): Promise<boolean> {
return new Promise((resolve) => {
return new Promise(async(resolve) => {
if (name === REDIRECT_NAME) {
resolve(false);
return;
@ -55,7 +59,14 @@ export const useRedo = (_router?: Router) => {
params['_redirect_type'] = 'path';
params['path'] = fullPath;
}
if(specialRouterName.includes(name)){
await tabStore.closeTab(currentRoute.value, _router || useRouter())
nextTick(() => {
push({ path: path, params, query }).then(() => resolve(true));
})
} else {
push({ name: REDIRECT_NAME, params, query }).then(() => resolve(true));
}
});
}
return redo;

View File

@ -60,6 +60,14 @@ export function usePermission() {
closeAll();
}
function hasAllPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
if (!value) {
return def;
}
const allPerm = permissionStore.getAllButtonPermCodeList
return allPerm.includes(value as string)
}
/**
* Determine whether there is permission
*/
@ -269,6 +277,7 @@ export function usePermission() {
return {
changeRole,
hasPermission,
hasAllPermission,
togglePermissionMode,
refreshMenu,
changeMenu,

View File

@ -245,6 +245,8 @@ function getSchemePermissionItem(
if (isViewProcess) {
schema.dynamicDisabled = true;
}
} else if(schema.componentProps?.alwaysShow===true){
schema.show=true;
} else {
schema.show = false;
schema.dynamicDisabled = true;

View File

@ -14,7 +14,7 @@
</p>
</div>
<BasicForm @register="registerForm" />
<!-- <BasicForm @register="registerForm" /> -->
<div :class="`${prefixCls}__footer`">
<a-button type="primary" block class="mt-2" @click="handleLock">
@ -44,7 +44,7 @@
const userStore = useUserStore();
const lockStore = useLockStore();
const getRealName = computed(() => userStore.getUserInfo?.realName);
const getRealName = computed(() => userStore.getUserInfo?.name);
const [register, { closeModal }] = useModalInner();
const [registerForm, { validateFields, resetFields }] = useForm({
@ -63,15 +63,14 @@
});
async function handleLock() {
const values = (await validateFields()) as any;
const password: string | undefined = values.password;
// const values = (await validateFields()) as any;
// const password: string | undefined = values.password;
closeModal();
lockStore.setLockInfo({
isLock: true,
pwd: password,
});
await resetFields();
// await resetFields();
}
const avatar = computed(() => {

View File

@ -1,4 +1,5 @@
import { useI18n } from '/@/hooks/web/useI18n';
import { is } from '/@/utils/is';
const { t } = useI18n();
export interface ListItem {
id: string;
@ -18,6 +19,7 @@ export interface ListItem {
processId?: string;
schemaId?: string;
timeFormat?: string;
isRead?: number;
}
export interface TabItem {
@ -55,4 +57,10 @@ export const tabListData: TabItem[] = [
read: [],
unreadNum: 0,
},
{
key: '5',
name: t('系统通知'),
list: [],
unreadNum: 0,
},
];

View File

@ -72,6 +72,33 @@
<span @click="setReadAll(item.key)">{{ t('全部设置已读') }}</span>
</div>
</div>
<!--系统通知消息-->
<div v-else-if="item.key === '5'" class="h-88">
<div v-if="item.list.length > 0" class="h-82">
<div
class="list-item readed-mark"
v-for="it in item.list"
:key="it.id"
:class="it.isRead === 1 ? 'readed' : ''"
@click="
() => {
it.isRead = 1;
setReadSingle(it.id, item.key);
goToRouter(it);
}
"
>
<span class="list-item-title" style="width: 220px;">
<a-tooltip>
<template #title>{{ it.title }}</template>
{{ it.title }}
</a-tooltip>
</span>
<span class="list-item-time">{{ it.createDate }}</span>
</div>
</div>
<a-empty :image="simpleImage" v-else/>
</div>
<div v-else class="h-88">
<div v-if="item.list.length > 0" class="h-82">
<div
@ -138,6 +165,7 @@
import { tabListData } from './data';
import { useDesign } from '/@/hooks/web/useDesign';
import { downloadByUrl } from '/@/utils/file/download';
import {
getOaMessage,
@ -149,11 +177,14 @@
setScheduleRead,
setScheduleReadAll,
} from '/@/api/system/login';
import {queryLoginUserNotices, setSystemNoticeRead} from '/@/api/system/systemNotice/index'
import { getInfoByDownloadUrl } from '/@/api/system/file';
import { Empty } from 'ant-design-vue';
import ApprovalProcess from '/@/views/workflow/task/components/ApprovalProcess.vue';
import LookProcess from '/@/views/workflow/task/components/LookProcess.vue';
import { useI18n } from '/@/hooks/web/useI18n';
import {useRouter } from 'vue-router';
const { t } = useI18n();
export default defineComponent({
components: {
@ -166,6 +197,7 @@
LookProcess,
},
setup() {
const router = useRouter();
const Approval = ref<{
taskId?: string;
processId?: string;
@ -196,7 +228,8 @@
if (o.read) o.read = [];
});
try {
let res = import.meta.env.VITE_GLOB_DISABLE_NEWS ? [] : await getOaNews(1);
if(import.meta.env.VITE_GLOB_DISABLE_NEWS !== 'true') {
let res = await getOaNews(1);
res.list.forEach((o) => {
if (!o.readId) listData.value[0].unreadNum += 1;
listData.value[0].list.push({
@ -210,7 +243,7 @@
read: o.isRead,
});
});
let res1 = import.meta.env.VITE_GLOB_DISABLE_NEWS ? [] : await getOaNews(2);
let res1 = await getOaNews(2);
res1.list.forEach((o) => {
if (!o.readId) listData.value[1].unreadNum += 1;
listData.value[1].list.push({
@ -224,7 +257,7 @@
read: o.isRead,
});
});
let res2 = import.meta.env.VITE_GLOB_DISABLE_NEWS ? [] : await getOaMessage();
let res2 = await getOaMessage();
res2.forEach((o) => {
if (o.messageType === 0) {
if (!o.isRead) listData.value[2].unreadNum += 1;
@ -272,11 +305,20 @@
});
}
});
//系统消息
let res4 = import.meta.env.VITE_DISABLE_NOTE === 'true' ? {list:[]} : await queryLoginUserNotices({limit: 1, size: 10});
listData.value[4].list = res4.list;
listData.value[4].unreadNum = res4.list.filter((item) => item.isRead == 0).length;
}
let res3 = await getScheduleMsg();
res3.list.forEach((item) => (item.read = item.isRead));
listData.value[2].unreadNum = res3.list.filter((x) => !x.isRead).length;
listData.value[2].list.push(...res3.list);
console.log('message', listData.value);
} catch (error) {
console.error('message error', error)
clearInterval(times.value);
}
}
@ -317,11 +359,26 @@
await setScheduleRead([ids]);
} else if (num == 4) {
await setSingleRead(ids);
} else if (num == 5) {
await setSystemNoticeRead([ids]);
} else {
await setOaRead([ids]);
}
if (listData.value[num - 1].unreadNum > 0) listData.value[num - 1].unreadNum -= 1;
}
function goToRouter(record){
if(record.type === '99') {
//异步打包下载
record.paramsJson = record.paramsJson ? JSON.parse(record.paramsJson) : {};
let fileId = record.paramsJson.id || [];
getInfoByDownloadUrl({id: fileId}).then((res)=>{
let fileUrl = res.fileUrlFixed || res.fileUrl;
downloadByUrl({ url: fileUrl, fileName: (res.fileName+res.fileType) || 'files.zip' });
});
return;
}
router.push(record.path)
}
onUnmounted(() => {
clearInterval(times.value);
});
@ -358,6 +415,7 @@
Approval,
LookData,
t,
goToRouter,
};
},
});

View File

@ -9,7 +9,7 @@
:width="24"
:height="24"
:src="getUserInfo.avatar"
fallback="src/assets/images/header.jpg"
:fallback="headerImg"
/>
</div>
<span :class="`${prefixCls}__info hidden md:block`">

View File

@ -63,6 +63,8 @@ async function bootstrap() {
//全局挂载axios
app.config.globalProperties.$axios = axios;
//挂载到全局变量(浏览器环境)
window.$appContext = app._context;
//取消点击esc关闭弹窗
Modal.props.keyboard.default = false;

View File

@ -25,7 +25,9 @@ import {
ExecutionType,
MemberType,
ApproveType,
NodeEventExType, SubProcessType,
NodeEventExType,
SubProcessType,
RejectApprover,
} from '/@/enums/workflowEnum';
import { BpmnNodeKey } from '/@/enums/workflowEnum';
@ -98,8 +100,12 @@ export interface ProcessConfig {
nameRule?: string; //命名规则
nameRuleConfigs?: NameRule; // 命名规则列表
autoAgreeRule?: Array<AutoAgreeRule>; //自动同意规则
autoAgreeMsg?: string; //自动审批意见
isPrevChooseNext: DesignatedApprover; //是否上一节点审批人指定下一节点审批人
noHandler: NoHandler; //无对应处理人
isChooseMulti: Boolean;
isChooseAll: Boolean;
isReadOnly: Boolean;
defaultFormList: Array<FormSettingItem>; //默认表单
appShow: boolean; //移动端是否显示
content?: string; //bpmn 设计 xml
@ -119,6 +125,7 @@ export interface ProcessConfig {
globalSuspendedBeforeEventConfigs: NodeEventConfig[];//全局 挂起/暂停事件
globalRestoreAfterEventConfigs: NodeEventConfig[];//全局 恢复事件
globalSetSignAfterEventConfigs: NodeEventConfig[];//全局 会签事件
globalWorkflowDataUpdate: string; //回写流程状态脚本
xmlContent: String; //xml
}
@ -174,6 +181,7 @@ export interface UserTaskConfig extends BasicNodeConfig {
parentId: string; //父节点流程id
currentProgress: undefined | number; //进度
autoAgreeRule: Array<AutoAgreeRule>; //自动同意规则
autoAgreeMsg: string; //自动审批意见
isPrevChooseNext: DesignatedApprover; //是否上一节点审批人指定下一节点审批人
noHandler: NoHandler; //无对应处理人
provisionalApprover: Boolean; //临时审批人
@ -188,6 +196,20 @@ export interface UserTaskConfig extends BasicNodeConfig {
assignmentConfig: AssignmentConfig; //参数操作
timeOutHandle: TimeOutConfig; //超时处理
isChooseParallel: Boolean; //是否选择并行节点
isChooseMulti: Boolean;
isChooseAll: Boolean;
isReadOnly: Boolean;
rejectToPerson: RejectApprover, //退回到候选人/审批人
rejectIsChooseAll: Boolean, //退回是否全选
rejectIsChooseMulti: Boolean, //退回是否多选
rejectIsReadOnly: Boolean, //退回是否只读
forbidRejectToNodes: string[], //禁止退回到节点
rejectToLastNodeOnly: Boolean,//只能退回到上个节点
forbidRejectByOtherNodes: Boolean, //不能被其他节点退回到
commitToRejectNode:Boolean, //允许直接提交到退回前节点
// rejectToMainProcess:true, //退回到主流程
rejectNotToStorey: string[], //禁止退回到子流程
rejectAllSubProcess:Boolean, //退回所有子流程
}
/**
* 脚本节点配置
@ -227,12 +249,12 @@ export interface EndEventConfig extends BasicNodeConfig {
*/
export interface SubProcessConfig extends BasicNodeConfig {
parentId: string; //父节点流程id
subProcessType: SubProcessType.MULTIPLE, // 调用类型
finishType: FinishType.ALL, //完成条件
subProcessType: SubProcessType, // 调用类型
finishType: FinishType, //完成条件
percentOf: undefined, //百分比数值
executionType: ExecutionType.PARALLEL, //执行类型
executionType: ExecutionType, //执行类型
originatorNode: '', //如果调用类型为单实例 子流程发起人
originatorType: MemberType.FORM_FIELD, //发起人类型
originatorType: MemberType, //发起人类型
originatorConfig: '', //表单数据
approverConfigs: MemberConfig[], //审批人
inParams: [], //输入参数

View File

@ -9,12 +9,19 @@ import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
import { RootRoute } from '/@/router/routes';
import { getAppEnvConfig } from '/@/utils/env';
import {useTenantManager} from "/@/utils/tenantManager";
import { useUserStore } from '/@/store/modules/user';
import { storeToRefs } from 'pinia';
const LOGIN_PATH = PageEnum.BASE_LOGIN;
const ROOT_PATH = RootRoute.path;
const whitePathList: PageEnum[] = [LOGIN_PATH];
const tenantEnabled=getAppEnvConfig().VITE_GLOB_TENANT_ENABLED;
export function createPermissionGuard(router: Router) {
const userStore = useUserStoreWithOut();
const permissionStore = usePermissionStoreWithOut();
@ -22,9 +29,27 @@ export function createPermissionGuard(router: Router) {
let isOnlyShowContent=to.query?.isOnlyShowContent;
if(isOnlyShowContent=='Y'){
window.isOnlyShowContent='Y';
}else{
}else if(isOnlyShowContent=='N'){
window.isOnlyShowContent='N';
}
let autoToggleTenant=to.query?.autoToggleTenant;
if(autoToggleTenant=='Y'){
window.autoToggleTenant='Y';
}else if(autoToggleTenant=='N'){
window.autoToggleTenant='N';
}
if (tenantEnabled==='true') {
let switchTenant = to.query?.switchTenant;
let tenantCode = to.query?.tenantCode;
if (switchTenant==='Y'&&tenantCode) {
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
if(tenantCode!==userInfo?.value?.tenantCode){
const {toggleLocal} = useTenantManager();
await toggleLocal({tenantCode: tenantCode, goHome: false, tabCloseAction: "none"});
}
}
}
if (
from.path === ROOT_PATH &&
to.path === PageEnum.BASE_HOME &&

View File

@ -226,3 +226,21 @@ export const FLOW_ROUTE: AppRouteRecordRaw[] = [{
}
]
}];
export const PROCESS_MONITORING: AppRouteRecordRaw[] = [{
path: '/processMonitoring/:arg1/:arg2',
name: 'ProcessMonitoring',
meta: {
title: '流程监控'
},
component: LAYOUT,
children: [
{
path: 'processMonitoringFlow',
name: 'ProcessMonitoringFlow',
component: () => import('/@/views/secondDev/processMonitoringPage.vue'),
meta: {
title: (route) => '流程监控'
}
},
]
}]

View File

@ -5,7 +5,8 @@ import {
REDIRECT_ROUTE,
SYSTEM_ROUTE,
USERCENTER_ROUTE,
FLOW_ROUTE
FLOW_ROUTE,
PROCESS_MONITORING
// CUSTOMFORM_ROUTE,
} from '/@/router/routes/basic';
@ -64,6 +65,7 @@ export const basicRoutes = [
PAGE_NOT_FOUND_ROUTE,
SYSTEM_ROUTE,
USERCENTER_ROUTE,
...FLOW_ROUTE
...FLOW_ROUTE,
...PROCESS_MONITORING
// CUSTOMFORM_ROUTE,
];

View File

@ -5,7 +5,8 @@ import { defineStore } from 'pinia';
import { LOCK_INFO_KEY } from '/@/enums/cacheEnum';
import { Persistent } from '/@/utils/cache/persistent';
import { useUserStore } from './user';
import { usePermissionStore } from '/@/store/modules/permission';
import { sendMobileLoginCode } from '/@/api/system/login';
interface LockState {
lockInfo: Nullable<LockInfo>;
}
@ -32,21 +33,23 @@ export const useLockStore = defineStore({
// Unlock
async unLock(password?: string) {
const userStore = useUserStore();
if (this.lockInfo?.pwd === password) {
if (userStore.getToken && this.lockInfo?.pwd === password) {
this.resetLockInfo();
return true;
}
const tryLogin = async () => {
try {
const userName = userStore.getUserInfo?.userName;
const tenantCode = userStore.getUserInfo?.tenantCode || '';
const res = await userStore.login({
userName,
password: password!,
tenantCode: tenantCode, // 补充
goHome: false,
mode: 'none',
});
if (res) {
this.resetLockInfo();
this.updatePermissionAndResetLock();
}
return res;
} catch (error) {
@ -55,5 +58,34 @@ export const useLockStore = defineStore({
};
return await tryLogin();
},
// Unlock by phone
async unLockByPhone(code?: string) {
const tryLogin = async () => {
try {
const userStore = useUserStore();
const mobile = userStore.getUserInfo?.mobile;
let params = {
mobile: mobile,
code: code
}
let res = await sendMobileLoginCode(params)
if (res) {
let params = {mode: 'none', goHome: false };
userStore.tokenLogin(res.token, params);
this.updatePermissionAndResetLock();
}
return res;
} catch (error) {
return false;
}
};
return await tryLogin();
},
// 更新权限 关闭 锁屏页
async updatePermissionAndResetLock() {
const permissionStore = usePermissionStore();
await permissionStore.changePermissionCode();
this.resetLockInfo();
}
},
});

View File

@ -25,6 +25,7 @@ import { MenuAuthModel } from '/@/api/system/login/model';
import { getSubSystemList } from '/@/api/system/subSystem';
interface PermissionState {
allButtonPermCodeList: string[];
// Permission code list
permCodeList: MenuAuthModel[];
// Whether the route has been dynamically added
@ -40,6 +41,7 @@ interface PermissionState {
export const usePermissionStore = defineStore({
id: 'app-permission',
state: (): PermissionState => ({
allButtonPermCodeList: [],
permCodeList: [],
// Whether the route has been dynamically added
isDynamicAddedRoute: false,
@ -54,6 +56,9 @@ export const usePermissionStore = defineStore({
subSystemList: [],
}),
getters: {
getAllButtonPermCodeList(): string[] {
return this.allButtonPermCodeList || [];
},
getPermCodeList(): MenuAuthModel[] {
return this.permCodeList || [];
},
@ -77,6 +82,12 @@ export const usePermissionStore = defineStore({
},
},
actions: {
setAllButtonPermCodeList(permList: string[]) {
this.allButtonPermCodeList = permList;
},
getHasPermission(code) {
return this.allButtonPermCodeList.includes(code);
},
setPermCodeList(permList: MenuAuthModel[]) {
this.permCodeList = permList;
},
@ -138,6 +149,13 @@ export const usePermissionStore = defineStore({
userInfo.desktopSchema = permResult.desktopSchema;
userStore.setUserInfo(userInfo);
let allButtonPermCode = [];
permResult.menuAuthList.forEach((item) => {
if (item.buttonAuthCode) {
allButtonPermCode = allButtonPermCode.concat(item.buttonAuthCode);
}
})
this.setAllButtonPermCodeList(allButtonPermCode)
this.setPermCodeList(permResult.menuAuthList);
},
async buildRoutesAction(isRefrashPermisson = true): Promise<AppRouteRecordRaw[]> {

View File

@ -92,7 +92,7 @@ export const useUserStore = defineStore({
): Promise<GetUserInfoModel | null> {
try {
const { token } = params;
params.goHome=true;
params.goHome = true;
return await this.tokenLogin(token,params);
} catch (error) {
return Promise.reject(error);
@ -111,7 +111,7 @@ export const useUserStore = defineStore({
const { goHome = true, mode, ...loginParams } = params;
const data = await loginApi(loginParams, mode);
const { token } = data;
params.goHome=true;
params.goHome = true;
return await this.tokenLogin(token,params);
} catch (error) {
return Promise.reject(error);

View File

@ -22,7 +22,7 @@ export function getAppEnvConfig() {
(import.meta.env as unknown as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_APP_SHORT_NAME, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_REQUEST_TIMEOUT, VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_PREVIEW, VITE_GLOB_OUT_LINK_URL, VITE_GLOB_REPORT_URL, VITE_GLOB_PRINT_BASE_URL, VITE_GLOB_TENANT_ENABLED,VITE_GLOB_TENANT_INPUT_REQUIRED,VITE_GLOB_UPLOAD_ALERT_TIP } = ENV;
const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_APP_SHORT_NAME, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_REQUEST_TIMEOUT, VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_PREVIEW, VITE_GLOB_OUT_LINK_URL, VITE_GLOB_REPORT_URL, VITE_GLOB_PRINT_BASE_URL, VITE_GLOB_TENANT_ENABLED,VITE_GLOB_TENANT_INPUT_REQUIRED,VITE_GLOB_UPLOAD_ALERT_TIP, VITE_GLOB_CLOSE_ALERT_DISABLED } = ENV;
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`);
@ -41,7 +41,8 @@ export function getAppEnvConfig() {
VITE_GLOB_PRINT_BASE_URL,
VITE_GLOB_TENANT_ENABLED,
VITE_GLOB_TENANT_INPUT_REQUIRED,
VITE_GLOB_UPLOAD_ALERT_TIP
VITE_GLOB_UPLOAD_ALERT_TIP,
VITE_GLOB_CLOSE_ALERT_DISABLED
};
}

25
src/utils/flow/index.js Normal file
View File

@ -0,0 +1,25 @@
const statusMap = {
ACTIVE: '审批中',
SUSPENDED: '挂起',
COMPLETED: '已完成',
'INTERNALLY_TERMINATED': '已终止'
}
const statusColorMap = {
ACTIVE: '#0000FF',
SUSPENDED: '#EFBD47',
COMPLETED: '#009900',
'INTERNALLY_TERMINATED': '#FF0000'
}
export function setIndexFlowStatus(workflowData) {
const status = {};
if (workflowData.taskIds) {
status.label = '待审批';
status.style = { color: '#CC9900' };
} else {
status.label = statusMap[workflowData.status]
status.style = { color: statusColorMap[workflowData.status] }
}
return status
}

View File

@ -645,6 +645,7 @@ ${hasTemplatePrint ? ' reactive ' : ''}
import { usePermission } from '/@/hooks/web/usePermission';
import { useFormConfig } from '/@/hooks/web/useFormConfig';
import { useRouter } from 'vue-router';
import { setIndexFlowStatus } from '/@/utils/flow/index'
import { get${pascalMainTableName} } from '/@/api/${
model.outputConfig.outputValue
}/${lowerClassName}';
@ -1493,7 +1494,7 @@ ${hasTemplatePrint ? ' reactive ' : ''}
if (record.workflowData?.enabled) {
//与工作流有关联的表单
if (record.workflowData.status) {
// 查看按钮现在同时有流程和表单的功能
actionsList.unshift(setIndexFlowStatus(record.workflowData))
} else {
actionsList = actionsList.concat(editAndDelBtn);
}
@ -1674,7 +1675,7 @@ export function buildSimpleFormCode(model: GeneratorConfig, _tableInfo: TableInf
import { reactive, ref,onBeforeMount,onMounted } from 'vue';
import { formProps, formEventConfigs ,formConfig} from './config';
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
import { add${pascalMainTableName}, get${pascalMainTableName}, update${pascalMainTableName} } from '/@/api/${
import { add${pascalMainTableName}, get${pascalMainTableName}, update${pascalMainTableName}, delete${pascalMainTableName} } from '/@/api/${
model.outputConfig.outputValue
}/${lowerClassName}';
import { cloneDeep } from 'lodash-es';
@ -1867,6 +1868,12 @@ export function buildSimpleFormCode(model: GeneratorConfig, _tableInfo: TableInf
systemFormRef.value,
formProps.schemas); //表单事件:加载表单
}
function getFormModel() {
return systemFormRef.value.formModel
}
async function handleDelete(id) {
return await delete${pascalMainTableName}([id]);
}
defineExpose({
setFieldsValue,
resetFields,
@ -1878,6 +1885,8 @@ export function buildSimpleFormCode(model: GeneratorConfig, _tableInfo: TableInf
setMenuPermission,
setWorkFlowForm,
getRowKey,
getFormModel,
handleDelete
});
</script>\n
`;

View File

@ -24,6 +24,9 @@ import { notification } from 'ant-design-vue';
import { throttle } from 'lodash-es';
import useGlobalFlag from '/@/hooks/core/useGlobalFlag';
import { useLockStore } from '/@/store/modules/lock';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix;
const { createMessage, createErrorModal } = useMessage();
@ -66,7 +69,7 @@ const transform: AxiosTransform = {
throw new Error(t('请求出错,请稍候重试'));
}
// 这里 coderesultmessage为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
const { code, data, msg } = result;
const { code, data, msg, tid } = result;
// 这里逻辑可以根据项目进行修改
const hasSuccess = code === ResultEnum.SUCCESS;
@ -83,10 +86,23 @@ const transform: AxiosTransform = {
const userStore = useUserStoreWithOut();
userStore.setToken(undefined);
console.log('登录超时,请重新登录!');
if (!window.location.hash.includes('login')&&!window.location.pathname.includes('login')
&&!window.location.hash.includes('tokenLogin')&&!window.location.pathname.includes('tokenLogin')) {
if (userStore.getUserInfo.loginConfig?.lockPageReLogin) {
console.error('not login page goto lockpage window.location.hash={}, window.location.pathname={}', window.location.hash, window.location.pathname, timeoutMsg);
// 触发全局弹层锁屏
const lockStore = useLockStore();
lockStore.setLockInfo({
isLock: true,
msg: timeoutMsg
});
return;
} else {
userStore.logout(true);
}
}
if (data) {
if(data.urlToRedirectTo){
//登录页面不跳转
@ -98,12 +114,29 @@ const transform: AxiosTransform = {
}else{
// data 有logout 信息 存sessionStorage 给login page 弹框显示
if (data.logoutMessage) {
console.error('logoutMessage', data.logoutMessage);
timeoutMsg = data.logoutMessage;
sessionStorage.setItem('logoutInfoData', JSON.stringify({
logoutMessage: data.logoutMessage,
}));
}
if (userStore.getUserInfo.loginConfig?.lockPageReLogin) {
console.error('go-login', timeoutMsg);
// 触发全局弹层锁屏
const lockStore = useLockStore();
lockStore.setLockInfo({
isLock: true,
msg: timeoutMsg
});
return;
} else {
const go = useGo();
if(go) {
go('/login');
} else {
throw new Error(timeoutMsg || t('请求出错,请稍候重试'));
}
}
}
}
break;
@ -118,6 +151,10 @@ const transform: AxiosTransform = {
if (options.ignoreErrorInEditor && isEditorOpen.value) {
ajaxError();
} else {
if (tid) {
timeoutMsg = timeoutMsg + '(异常操作号:' + tid + ')';
}
console.error('异常操作:', timeoutMsg, new Date().toLocaleString());
if (options.errorMessageMode === 'modal') {
createErrorModal({ title: t('错误提示'), content: timeoutMsg });
} else if (options.errorMessageMode === 'message') {
@ -252,7 +289,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
// authentication schemese.g: Bearer
// authenticationScheme: 'Bearer',
authenticationScheme: 'Bearer',
timeout: 600000,
timeout: globSetting.requestTimeout||60 * 1000,
// 基础接口地址
// baseURL: globSetting.apiUrl,

View File

@ -234,7 +234,7 @@
</div>
</div>
<div class="ant-col ant-form-item-control ant-col-12">
<a-button v-auth="'monitor:appointedAuditor'" @click="flowChange">{{
<a-button v-if="canClick" v-allAuth="'adminOperation:toDesignatedNode'" @click="flowChange">{{
t('将任务流转到')
}}</a-button>
</div>
@ -244,7 +244,12 @@
<div class="ant-col ant-col-24">
<div class="ant-row ant-form-item" style="row-gap: 0px;">
<div class="ant-col ant-form-item-label" style="width: 120px;">
<label>审批人</label>
<label>
审批人
<a-tooltip title="当前节点为会签节点时是加减签,为普通节点时是加减审批人抢令牌">
<QuestionCircleOutlined style="margin-left: 4px; cursor: pointer; color: #1890ff;" />
</a-tooltip>
</label>
</div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
@ -256,16 +261,9 @@
</div>
</div>
<div class="ant-col ant-form-item-control ant-col-12">
<!-- <a-button style="width: 20%;" v-auth="'monitor:appointedAuditor'" @click="addOrSubtractUser">{{
t('加减签')
}}</a-button> -->
<a-button v-if="!showAdd" v-auth="'monitor:appointedAuditor'" style="margin-right: 10px;"
@click="approveUser">{{
t('修改审批人')
}}</a-button>
<AddOrSubtract v-else :schemaId="schemaId" :taskId="assignee.value.taskId"
:selectedUser="assignee.value.assigneeVoList">
</AddOrSubtract>
<AddOrSubtractV2 :schemaId="schemaId" :taskId="assignee.value.taskId" v-if="canClick" v-allAuth="'adminOperation:setAssignee'"
:lastAddStepUser="assignee.value.assigneeVoList" @change="changedUser">
</AddOrSubtractV2>
</div>
</div>
</div>
@ -316,7 +314,6 @@
<!-- 指派审核人 -->
<!-- 流程流转 -->
<opinionDialog ref="opinionDlg" />
<!-- <AddOrSubtract v-if="showAdd"></AddOrSubtract> -->
</template>
@ -326,12 +323,13 @@ import SimpleFormSetup from '/@/components/SimpleForm/src/SimpleFormSetup.vue';
import { Col, Form, message, Row } from 'ant-design-vue';
import SimpleFormItem from '/@/components/SimpleForm/src/components/SimpleFormItem.vue';
import { ref, reactive, inject } from 'vue';
import { CheckCircleOutlined } from '@ant-design/icons-vue';
import { CheckCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue';
import ApproveProcessMonitor from '../../../views/workflow/task/components/flow/ApproveProcessMonitorUser.vue';
import { data } from '../../demo/excel/data';
import opinionDialog from '/@/components/SecondDev/OpinionDialogSelected.vue';
import { getProcessUserNodes, SetChangeProcessNode } from '/@/api/workflow/adminOperation'
import AddOrSubtract from '../../workflow/task/components/flow/AddOrSubtractWork.vue';
import AddOrSubtractV2 from '../../workflow/task/components/flow/AddOrSubtractV2.vue';
const FormItem = Form.Item;
@ -339,6 +337,7 @@ const FormItem = Form.Item;
export default {
components: {
CheckCircleOutlined,
QuestionCircleOutlined,
Form,
Col,
SimpleFormItem,
@ -347,6 +346,7 @@ export default {
ApproveProcessMonitor,
opinionDialog,
AddOrSubtract,
AddOrSubtractV2
},
mixins: [SimpleFormSetup],
setup(props, ctx) {
@ -357,26 +357,22 @@ export default {
const isCustom = ref(Boolean)
const schemaId = ctx.attrs.schemaId;
const processId = ctx.attrs.processId;
const canClick = ctx.attrs.canClick
isCustom.value = ctx.attrs.isCustom;
const showApproveUser = ref(Boolean);
showApproveUser.value = false;
const showAdd = ref(Boolean);
showAdd.value = true;
const opinionDlg = ref();
const selectedInfo = ref();
const allTaskNodes = inject('taskNode');;
const allTaskNodes = inject('taskNode');
const refreshInfo = inject('refreshApproveInfo', () => {})
const users = ref('');
users.value = assignee.value.assigneeVoList.map((ele) => {
return ele.name + '(' + ele.code + ')';
}).join('')
if (assignee.value.assigneeVoList.length > 1) {
showAdd.value = false;
}
function approveUser() {
showApproveUser.value = true;
}
@ -395,6 +391,10 @@ export default {
});
}
function changedUser() {
refreshInfo()
}
function submit(info) {
const key = info.taskId;
const userIds = info.selectedList.map((ele) => {
@ -426,7 +426,6 @@ export default {
return {
approveUser,
flowChange,
showAdd,
processId,
schemaId,
showApproveUser,
@ -435,6 +434,8 @@ export default {
currentTaskAssigneeNames,
opinionDlg,
users,
canClick,
changedUser,
...ret
};
},
@ -455,7 +456,7 @@ export default {
}
});
return schemaMap;
}
},
},
methods: {
getIfShow2: function (key) {

View File

@ -2,7 +2,7 @@
<SimpleForm ref="systemFormRef" :formProps="data.formDataProps" :formModel="{}"
:isWorkFlow="props.fromPage != FromPageType.MENU" :clickedTaskAssignees="props.clickedTaskAssignees"
:processId="props.processId" :isCustom="props.customFlg" :schemaId="props.schemaId"
:currentTaskAssigneeNames="taskAssigneeNames" />
:currentTaskAssigneeNames="taskAssigneeNames" :canClick="canClick"/>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, computed } from 'vue';
@ -27,6 +27,10 @@ const props = defineProps({
customFlg: false,
clickedTaskAssignees: {},
currentTaskAssigneeNames: {},
canClick: {
type: Boolean,
default: false
},
fromPage: {
type: Number,
default: FromPageType.MENU,

View File

@ -755,7 +755,6 @@
}
}
async function handleLaunchProcess(record: Recordable) {
debugger
if (record.workflowData) {
if (record.workflowData.draftId) {
let res = await getDraftInfo(record.workflowData.draftId);

View File

@ -23,9 +23,22 @@
<a-button type="primary" @click="handleStepNext" v-show="current < 5">
{{ t('下一步') }}
</a-button>
<a-button type="primary" @click="handleCodeGenerator" v-show="current === 5">
<a-dropdown placement="bottom" :arrow="{ pointAtCenter: true }">
<a-button type="primary" v-show="current === 5">
{{ t('完成') }}
</a-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a href="javascript:;" @click="handleCodeGenerator('packAndDownload')">打包下载</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;" @click="handleCodeGenerator('genCodeToProject')">生成到项目中</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button type="primary" danger @click="handleClose">{{ t('关闭') }}</a-button>
</div>
</div>
@ -59,6 +72,11 @@
import { AppFormProps } from '/@/model/generator/appFormConfig';
import { addMobileFunc, editMobileFuncData, getMobileFuncInfo } from '/@/api/mobileDesign';
import DesignLogo from '/@/components/ModalPanel/src/DesignLogo.vue';
import {
downloadCodes,
} from '/@/api/system/generator';
import { downloadByData } from '/@/utils/file/download';
import { dateUtil } from '/@/utils/dateUtil';
const { t } = useI18n();
const TableConfigStep = defineAsyncComponent({
@ -229,16 +247,16 @@
handleClose();
emit('success');
}
async function handleCodeGenerator() {
async function handleCodeGenerator(actionType:String) {
const isOk = await stepValidate[5]();
if (!isOk) {
return;
}
await setParams(enabledMark.value);
await setParams(enabledMark.value, actionType);
handleClose();
emit('success');
}
async function setParams(enabledMark) {
async function setParams(enabledMark, actionType?:String) {
if (
generatorConfig.formJson?.hiddenComponent &&
generatorConfig.formJson?.hiddenComponent.length
@ -273,12 +291,22 @@
if (data.formType === 1) {
data.isGeneratorCode = generatorConfig!.outputConfig!.createCode ? 1 : 0;
}
data.actionType=actionType;
let result;
if (templateId.value) {
data.id = templateId.value;
data.appMenuId = appMenuId.value;
await editMobileFuncData(data);
result = await editMobileFuncData(data);
} else {
await addMobileFunc(data);
result = await addMobileFunc(data);
}
if(data.actionType=="packAndDownload"&&result){
const fileName=generatorConfig!.outputConfig!.className+'_'+dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
const res = await downloadCodes({uuid:result});
downloadByData(
res.data,
fileName+".zip"
);
}
}
const handleClose = () => {

View File

@ -23,7 +23,7 @@
</a-select>
</a-form-item>
<a-form-item :label="t('绑定字段')" v-if="!noHaveField.includes(data.type)">
<a-select :value="data.bindField" size="mini" :placeholder="t('请选择表字段')" disabled>
<a-select :value="data.bindField" size="mini" showSearch :placeholder="t('请选择表字段')" disabled>
<a-select-option v-for="(field, idx) in fieldsInfo" :value="field.name" :key="idx">
{{ field.name }}
<span>

View File

@ -225,27 +225,36 @@
}
async function saveDraftData() {
try {
let formModes = {};
for (let index = 0; index < forms.configs.length; index++) {
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
let values = await itemRefs.value[index].validate();
let values = await itemRefs.value[index].getFormModels();
formModes[ele.formKey] = values;
} else {
formModes[ele.formKey] = ele.formModel;
}
}
return formModes;
} catch (e) {
console.error(e)
throw new Error(e);
}
}
async function getFormModels(saveRowKey) {
try {
let formModes = {};
for (let index = 0; index < forms.configs.length; index++) {
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
let values = await itemRefs.value[index].workflowSubmit(saveRowKey);
if(!values) {
return
}
formModes[ele.formKey] = values;
} else {
formModes[ele.formKey] = ele.formModel;
@ -260,6 +269,10 @@
await submitFormEvent(ele, forms.configs[i]?.formModel);
});
return formModes;
} catch (e) {
console.error(e)
throw new Error(e);
}
}
function getSystemType() {
@ -273,13 +286,23 @@
return system;
}
function handleInnerFun(funcName) {
for (let index = 0; index < forms.configs.length; index++) {
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
itemRefs.value[index].handleInnerFun(funcName)
}
}
}
defineExpose({
validateForm,
getFormModels,
saveDraftData,
setFormData,
getUploadComponentIds,
getSystemType
getSystemType,
handleInnerFun
});
</script>

View File

@ -48,30 +48,7 @@
redirect = list[1];
}
let tenantCode='';
if(getAppEnvConfig().VITE_GLOB_TENANT_ENABLED=='true'){
let url='';
if(targetURL){
url=targetURL;
}else{
url=redirect;
}
if(url.includes('tenantCode')){
const fullString = decodeURIComponent(url);
const queryString = fullString.split('?')[1];
tenantCode = (queryString.match(/tenantCode=([^&]+)/) || [])[1]
}
if(!tenantCode){
notification.error({
message: t('提示'),
description: t('租户码不能为空!'),
});
return;
}
}
let params = {...currentRoute.value.query, targetURL: targetURL,redirect: redirect, mode: 'none',tenantCode:tenantCode }; //不要默认的错误提示
let params = {...currentRoute.value.query, targetURL: targetURL,redirect: redirect, mode: 'none'}; //不要默认的错误提示
await userStore.singleLogin(params);
}

View File

@ -10,38 +10,32 @@
</slot>
关闭
</a-button>
<a-button @click="saveDraft" v-if="!readonly">
暂存
</a-button>
<a-button @click="setDraft" v-if="rDraftsId && !readonly">
从草稿导入
</a-button>
<a-button v-if="!readonly && hasBtnApprove" type="primary" @click="onApproveClick()">
<slot name="icon">
<template v-for="(btn, index) in buttonMap.normal">
<a-button @click="onClickBtn(btn)" :type="btn.buttonCode === ApproveCode.AGREE ? 'primary' : ''">
<slot name="icon" v-if="btn.buttonCode === ApproveCode.AGREE">
<check-circle-outlined />
</slot>
同意
</a-button>
<a-button v-if="!readonly && hasBtnReject" @click="onDenyClick">
<slot name="icon">
<slot name="icon" v-if="btn.buttonCode === ApproveCode.REJECT">
<stop-outlined />
</slot>
拒绝
{{btn.buttonName}}
</a-button>
<a-dropdown>
</template>
<template v-for="(btnGroup, btnGroupKey) in buttonMap">
<a-dropdown v-if="btnGroupKey!=='normal' && btnGroup.length">
<template #overlay>
<a-menu @click="onMoreClick">
<a-menu-item v-if="!readonly && hasBtnFinish" key="finish">终止</a-menu-item>
<a-menu-item v-if="!readonly" key="transfer">转办</a-menu-item>
<a-menu-item v-if="readonly && drawNode" key="drawBack">撤回</a-menu-item>
<a-menu-item key="flowchart">查看流程图</a-menu-item>
<a-menu>
<a-menu-item v-for="(btn, index) in btnGroup" :key="btn.buttonCode" @click="onClickBtn(btn)">
{{btn.buttonName}}
</a-menu-item>
</a-menu>
</template>
<a-button>
更多
{{btnGroupKey}}
<down-outlined />
</a-button>
</a-dropdown>
</template>
</a-space>
</div>
<FormInformation
@ -67,6 +61,15 @@
<a-button type="primary" @click="closeFlowChart">关闭</a-button>
</template>
</a-modal>
<a-modal :closable="false" v-if="showRecord" visible="true" centered class="geg" title="流程记录" width="1200px" @cancel="closeFlowRecord">
<div class="flow-record-box">
<FlowRecord :list="data.taskRecords" :processId="processId"/>
</div>
<template #footer>
<a-button type="primary" @click="closeFlowRecord">关闭</a-button>
</template>
</a-modal>
<SelectUserV2 ref="selectUser" v-model:value="addStepUser" @change="changeAddStepUser" just-dialog title="加签减签"/>
</div>
</div>
</a-spin>
@ -74,14 +77,15 @@
<script setup>
import { useRouter } from 'vue-router';
import { onMounted, reactive, ref, unref, createVNode } from 'vue';
import { onMounted, reactive, ref, unref, createVNode, provide } from 'vue';
import FormInformation from '/@/views/secondDev/FormInformation.vue';
import userTaskItem from '/@/views/workflow/task/hooks/userTaskItem';
import { getApprovalProcess, postApproval, postGetNextTaskMaybeArrival, postTransfer, getDrawNode, withdraw } from '/@/api/workflow/task';
import { ApproveCode, ApproveType } from '/@/enums/workflowEnum';
import { ApproveCode, ApproveType, ButtonType } from '/@/enums/workflowEnum';
import { CheckCircleOutlined, StopOutlined, CloseOutlined, DownOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
import OpinionDialog from '/@/components/SecondDev/OpinionDialog.vue';
import TransferDialog from '/@/components/SecondDev/TransferDialog.vue';
import SelectUserV2 from '/@/components/Form/src/components/SelectUserV2.vue';
import { separator } from '/@bpmn/config/info';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import Title from '/@/components/Title/src/Title.vue';
@ -94,6 +98,8 @@
import { useUserStore } from '/@/store/modules/user';
import { deleteDraft, postDraft, putDraft, getDraftInfo } from '/@/api/workflow/process';
import { TaskTypeUrl } from '/@/enums/workflowEnum';
import { postSetSign, postSetSignV2 } from '/@/api/workflow/task';
import FlowRecord from '/@/views/workflow/task/components/flow/FlowRecord.vue';
const spinning = ref(false);
@ -121,10 +127,29 @@
const transferDlg = ref();
const validateSuccess = ref(false);
const formInformation = ref();
const selectUser = ref()
const addStepUser = ref('')
const lastAddStepUser = ref('')
const hasBtnAddStep = ref(false)
const hasBtnTransfer = ref(false)
const showFlowChart = ref(false);
const hasBtnApprove = ref(true);
const showRecord = ref(false)
const hasBtnApprove = ref(false);
const hasBtnDisagree = ref(false)
const hasBtnReject = ref(false);
const hasBtnFinish = ref(false);
const hasBtnDraft = ref(false)
const approveBtnName = ref('同意')
const transferBtnName = ref('转办')
const addStepBtnName = ref('会签')
const finishBtnName = ref('终止')
const rejectBtnName = ref('拒绝')
const drawBackBtnName = ref('撤回')
const draftBtnName = ref('暂存')
const disagreeBtnName = ref('不同意')
const buttonMap = ref({
normal: []
})
let draftData = {}
const drawNode = ref('');
const props = defineProps({
@ -132,6 +157,8 @@
type: String
}
});
const processInfo = ref();
provide("processInfo", processInfo);
let approvalData = reactive({
isCountersign: false,
@ -151,21 +178,51 @@
nextTaskUser: {} // 格式为taskKey: 用户id逗号分隔
});
let approvedType = ref(ApproveType.AGREE);
function showButton(btn) {
// 撤回有drawNode才显示流程图任何情况下都显示
let show = (btn.checked && ((!readonly.value && btn.buttonCode !== ApproveCode.DRAWBACK) || (readonly.value && btn.buttonCode === ApproveCode.DRAWBACK && drawNode.value))) || (btn.buttonCode === ApproveCode.FLOWBPMN || btn.buttonCode === ApproveCode.FLOWRECORD)
return show
}
function onClickBtn(btn) {
const key = btn.buttonCode;
if(btn.buttonType === ButtonType.DEFAULT) {
let funName = `handle${key}`
methods[funName](btn)
} else if(btn.buttonType === ButtonType.SCRIPT) {
handleFunction(btn)
}
}
function onMoreClick(e) {
const key = e.key;
if (key === 'flowchart') {
openFlowChart();
} else if (key === 'finish') {
Modal.confirm({
title: () => '提示',
content: () => '确定终止吗?',
onOk: () => {
onFinishClick();
if(btn.buttonType === ButtonType.DEFAULT) {
let funName = `handle${key}`
methods[funName](btn)
} else if(btn.buttonType === ButtonType.SCRIPT) {
handleFunction(btn)
}
});
} else if (key === 'transfer') {
onTransferClick();
} else if (key === 'drawBack') {
}
function handleFunction(btn) {
// eval(script)
if (btn.handleFuncName) {
formInformation.value?.handleInnerFun(btn.handleFuncName)
}
}
const methods = {
handlesetDraft() {
setDraft()
},
handledraft() {
saveDraft();
},
handleaddStep() {
selectUser.value.show()
},
handledrawBack() {
Modal.confirm({
title: t('提示'),
icon: createVNode(ExclamationCircleOutlined),
@ -195,19 +252,49 @@
},
onCancel() {}
});
},
handlefinish(btn) {
Modal.confirm({
title: () => '提示',
content: () => `确定${btn.buttonName}吗?`,
onOk: () => {
onFinishClick();
}
});
},
handleflowBpmn() {
showFlowChart.value = true;
},
handletransfer() {
onTransferClick();
},
handleagree() {
onApproveClick()
},
handledisagree() {
onDisagreeClick()
},
handlereject() {
onDenyClick()
},
handleflowRecord() {
showRecord.value = true;
},
}
function closeFlowChart() {
showFlowChart.value = false;
}
function openFlowChart() {
showFlowChart.value = true;
function closeFlowRecord() {
showRecord.value = false
}
function close() {
tabStore.closeTab(currentRoute, router);
if(window?.isOnlyShowContent=='Y') {
window.close();
}
}
async function setDraft(needModal = true) {
let formData = [];
@ -259,8 +346,40 @@
notificationError(title);
}
}
async function changeAddStepUser (ids, memberList) {
try {
spinning.value = true;
let idList = memberList.map(item => {
return item.id
})
let lastIdList = lastAddStepUser.value.split(',')
let addUserIds = idList.filter(item => {
return lastIdList.indexOf(item) == -1
})
let subUserIds = lastIdList.filter(item => {
return idList.indexOf(item) == -1
})
let data = {
addUserIds,
subUserIds,
schemaId: schemaId.value,
taskId: taskId.value
}
await postSetSignV2(data);
let res = await getApprovalProcess(unref(taskId), unref(processId));
initProcessData(res);
spinning.value = false;
message.success('操作成功');
} catch (e) {
message.error(e)
spinning.value = false;
message.error('操作失败,请稍后再试');
}
}
async function onApproveClick(isAutoAgreeBreak = false) {
try {
openSpinning();
if (!isAutoAgreeBreak) {
await submit();
@ -288,6 +407,42 @@
onFinish('approve');
}
});
} catch (e) {
console.error(e)
closeSpinning();
throw new Error(e);
}
}
async function onDisagreeClick() {
try {
openSpinning();
await submit();
if (!validateSuccess.value) {
closeSpinning();
return;
}
const params = await getApproveParams();
const nextNodes = await postGetNextTaskMaybeArrival(params);
approvalData.approvedType = ApproveType.DISAGREE;
approvalData.approvedResult = ApproveCode.DISAGREE;
closeSpinning();
opinionDlg.value.toggleDialog({
action: 'disagree',
nextNodes,
callback: (args) => {
approvalData.approvedContent = args.opinion;
onFinish('disagree');
}
});
} catch (e) {
console.error(e)
closeSpinning();
throw new Error(e);
}
}
async function onDenyClick() {
@ -379,12 +534,37 @@
function setBtnStatus() {
const btnConfigs = approvalData.buttonConfigs;
let draftBtn = btnConfigs.find((item) => item.buttonCode === ApproveCode.DRAFT)
if(draftBtn && rDraftsId.value) {
btnConfigs.push({
...draftBtn,
buttonName: t('从草稿导入'),
buttonCode: 'setDraft',
approveType: ApproveType.DRAFT,
index: 1
})
}
btnConfigs.forEach((btn) => {
const code = btn.buttonCode;
if (code === 'reject') {
hasBtnReject.value = true;
} else if (code === 'finish') {
hasBtnFinish.value = true;
const index = btn.index
const buttonGroup = btn?.buttonGroup
if(buttonGroup) {
if(!buttonMap.value[buttonGroup]) {
buttonMap.value[buttonGroup] = []
}
if(showButton(btn)) {
buttonMap.value[buttonGroup].push(btn)
}
} else {
if(showButton(btn)) {
buttonMap.value['normal'].push(btn)
}
}
for(let key in buttonMap.value) {
buttonMap.value[key].sort((a, b) => {
let aIndex = a?.index || 0
let bIndex = b?.index || 0
return aIndex - bIndex
})
}
});
}
@ -399,16 +579,26 @@
try {
let res = await getApprovalProcess(unref(taskId), unref(processId));
initProcessData(res);
await getBackNode();
processInfo.value = res;
const title = res?.schemaInfo?.name;
if (title) {
const tabPrefix = readonly.value ? '查看' : '审批';
tabStore.changeTitle(fullPath, `${tabPrefix}${title}`);
}
if (!readonly.value) {
if(taskId.value) {
let ids = ''
res.currentTaskAssignees[res.taskInfo.taskDefinitionKey].forEach((item, index) => {
ids = `${ids}${index == 0 ? '' : ','}${item.assigneeIdStr}`
})
addStepUser.value = ids
lastAddStepUser.value = ids
}
if (res.buttonConfigs) {
approvalData.buttonConfigs = res.buttonConfigs;
setBtnStatus();
}
if (!readonly.value) {
if (res.relationTasks) {
data.predecessorTasks = res.relationTasks;
}
@ -424,13 +614,12 @@
approvalData.circulateConfigs = [];
}
renderKey.value = Math.random() + '';
getBackNode();
setDraft()
} catch (error) {}
});
function getBackNode() {
getDrawNode(processId.value).then((res) => {
async function getBackNode() {
await getDrawNode(processId.value).then((res) => {
if (res.length) {
drawNode.value = res[0].activityId;
} else {
@ -481,6 +670,7 @@
}
async function getApproveParams() {
try {
let formModels = await formInformation.value.getFormModels();
let system = formInformation.value.getSystemType();
let fileFolderIds = getUploadFileFolderIds(formModels);
@ -499,11 +689,14 @@
isEnd:approvalData.isEnd,
nextTaskUser: approvalData.nextTaskUser
};
} catch (e) {
console.error(e)
}
}
async function onFinish(values) {
try {
if (validateSuccess.value || values === 'reject' || values === 'finish') {
if (validateSuccess.value || values === 'reject' || values === 'finish' || values === 'disagree') {
let params = await getApproveParams();
let response = await postApproval(params);
// 判断返回值是否带有isAutoAgree 来判断中间是否有自动审批的业务如有再执行判断待审人员是否包含自己不包含就直接flowSuccess
@ -513,6 +706,7 @@
}
} catch (error) {
flowFail();
throw new Error(error);
}
}
@ -525,7 +719,6 @@
&& response.length != 0
&& response[0].isAutoAgree == true //
&& response[0].approveUserIds.includes(userStore.getUserInfo.id)) {
console.error('will reSelect user=', response[0].taskId);
// 注入新得taskId
taskId.value = response[0].taskId;
data.submitLoading = false;
@ -544,3 +737,10 @@
spinning.value = false;
}
</script>
<style lang="less" scoped>
.flow-record-box {
height: 500px;
overflow: auto;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="page-bg-wrap">
<a-spin :spinning="loading" tip="加载中...">
<div class="page-bg-wrap">
<div class="geg-flow-page">
<div class="top-toolbar">
<a-space :size="10" wrap>
@ -22,11 +22,8 @@
</slot>
暂存
</a-button>
<a-button>
<slot name="icon">
<printer-outlined />
</slot>
打印
<a-button @click="handleDelete" danger v-if="rDraftsId!='0'">
删除
</a-button>
<a-button @click="openFlowChart">
<slot name="icon">
@ -47,8 +44,8 @@
</template>
</a-modal>
<opinion-dialog ref="opinionDlg" />
</a-spin>
</div>
</a-spin>
</template>
<script setup>
@ -63,7 +60,7 @@
import { deleteDraft, postDraft, putDraft } from '/@/api/workflow/process';
import { useI18n } from '/@/hooks/web/useI18n';
import { separator } from '/@bpmn/config/info';
import { message } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
import OpinionDialog from '/@/components/SecondDev/OpinionDialog.vue';
import { ApproveCode, ApproveType } from '/@/enums/workflowEnum';
import useEventBus from '/@/hooks/event/useEventBus';
@ -80,7 +77,7 @@
const fullPath = currentRoute.fullPath;
const rQuery = currentRoute.query;
const rSchemaId = rParams.arg1;
const rDraftsId = rParams.arg2;
let rDraftsId = ref(rParams.arg2);
const taskId = ref();
const loading = ref(false);
const draftsJsonStr = localStorage.getItem('draftsJsonStr');
@ -183,11 +180,12 @@
try {
disableSubmit.value = true;
let formModels = await formInformation.value.saveDraftData();
if (rDraftsId !== '0') {
let res = await putDraft(rSchemaId, formModels, rDraftsId, props.rowKeyData);
if (rDraftsId.value !== '0') {
let res = await putDraft(rSchemaId, formModels, rDraftsId.value, props.rowKeyData);
showResult(res, '保存草稿');
} else {
let res = await postDraft(rSchemaId, formModels, props.rowKeyData);
rDraftsId.value = res
showResult(res, '保存草稿');
}
} catch (error) {
@ -291,11 +289,27 @@
stampPassword: values.password,*/
isOldSystem: system,
nextTaskUser: approvalData.nextTaskUser,
draftId: rDraftsId,
draftId: rDraftsId.value,
};
}
async function saveLaunch() {
if (!taskId.value && rDraftsId.value!='0') {
try {
await new Promise((resolve, reject) => {
Modal.confirm({
title: '提示',
content: '请确认是否提交流程,提交后流程不能删除',
okText: '确定',
cancelText: '取消',
onOk: () => resolve(),
onCancel: () => reject()
});
});
} catch {
return;
}
}
data.submitLoading = true;
loading.value = true;
try {
@ -306,6 +320,7 @@
let successValidate = validateForms.filter((ele) => {
return ele.validate;
});
console.info("validateForms:"+JSON.stringify(validateForms));
if (successValidate.length == validateForms.length) {
mainFormModels.value = await formInformation.value.getFormModels(true);
/*for (let i in mainFormModels.value) {
@ -388,6 +403,29 @@
});
return fileFolderIds;
}
async function handleDelete() {
Modal.confirm({
title: '提示信息',
content: '是否确认删除?删除后无法恢复数据!',
okText: '确认',
cancelText: '取消',
async onOk() {
try {
let res = await deleteDraft([rDraftsId.value]);
if (res) {
message.success('删除成功');
setTimeout(() => {
bus.emit(CREATE_FLOW, {});
close();
}, 500);
} else {
message.error('删除失败');
}
} catch (error) {}
},
onCancel() {},
});
}
</script>
<style lang="less"></style>

View File

@ -0,0 +1,493 @@
<template>
<!-- 表单信息 -->
<div class="form-container">
<div class="box">
<div class="form-right">
<div v-for="(item, index) in forms.configs" :key="index" :tab="item.formName">
<div v-show="activeIndex == index">
<div class="page-bg-wrap">
<div class="top-toolbar" style="display: flex;margin-bottom: 10px">
<div id="adminButtons" v-show="activeIndex == index" style="margin-right:10px">
<a-button @click="handleCancel" v-if="forms.modes[index] == 'edit'">取消</a-button>
<a-button @click="handleSave" v-if="forms.modes[index] == 'edit'" type="primary" style="margin-left: 12px">保存</a-button>
<a-button @click="handleEdit" v-if="forms.modes[index] == 'view'">编辑</a-button>
<a-button @click="handleDelete" type="danger" style="margin-left: 12px">删除</a-button>
</div>
<div id="approveExtendButton"></div>
<div id="approveRightButton"></div>
<div id="approveExtendButtonLeft" v-show="false"></div>
</div>
<SystemForm class="form-box" v-if="item.formType == FormType.SYSTEM"
:systemComponent="item.systemComponent" :isViewProcess="props.disabled" :formModel="item.formModel"
:workflowConfig="item" :ref="setItemRef" />
<SimpleForm v-else-if="item.formType == FormType.CUSTOM" class="form-box testClass" :ref="setItemRef"
:formProps="item.formProps" :formModel="item.formModel" :isWorkFlow="true" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
import { FewerLeft, FewerRight } from '/@/components/ModalPanel';
import { NodeHead } from '/@/components/ModalPanel/index';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { onBeforeUpdate, nextTick, onMounted, reactive, computed, ref, inject, createVNode } from 'vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { TaskApproveOpinion, ValidateForms } from '/@/model/workflow/bpmnConfig';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { FormEventColumnConfig } from '/@/model/generator/formEventConfig';
import { changeFormJson } from '/@/hooks/web/useWorkFlowForm';
import { SystemForm } from '/@/components/SystemForm/index';
import { FormType } from '/@/enums/workflowEnum';
import { createFormEvent, loadFormEvent, submitFormEvent } from '/@/hooks/web/useFormEvent';
import { message, Modal } from "ant-design-vue";
import { updateWorkflow } from '/@/api/workflow/adminOperation';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { useRouter } from 'vue-router';
import useEventBus from '/@/hooks/event/useEventBus';
const tabStore = useMultipleTabStore();
const { bus, FLOW_PROCESSED } = useEventBus();
const router = useRouter();
const { currentRoute } = useRouter();
const { t } = useI18n();
const props = withDefaults(
defineProps<{
disabled: boolean | undefined;
formInfos: Array<any>;
opinions?: Array<TaskApproveOpinion> | undefined;
opinionsComponents?: Array<string> | undefined;
formAssignmentData?: null | Recordable;
processId: string;
}>(),
{
disabled: false,
formInfos: () => {
return [];
},
processId: ''
},
);
const flowInfo = inject('flowInfo')
const emits = defineEmits(['getFormConfigs']);
let uploadComponent: { ids: Array<string> } = reactive({ ids: [] });
let activeIndex = ref(0);
let itemRefs = ref([]) as any;
const setItemRef = (el: never) => {
itemRefs.value.push(el);
};
onBeforeUpdate(() => {
itemRefs.value = [];
});
let forms: {
formModels: Array<Recordable>;
configs: Array<{
formName: string;
formProps: {};
formModel: Recordable;
formKey: string;
validate: boolean;
formType: FormType;
workflowPermissions?: Array<any>;
opinions?: Array<any>;
opinionsComponents?: Array<any>;
systemComponent?: {
functionalModule: string;
functionName: string;
functionFormName: string;
};
formJson?: Array<any>;
isOldSystem?: boolean;
}>;
formEventConfigs: FormEventColumnConfig[];
modes: string[];
} = reactive({
formModels: [],
configs: [],
formEventConfigs: [],
modes: []
});
onMounted(async () => {
for await (let element of props.formInfos) {
let formModels = {};
if (element.formData) {
formModels = cloneDeep(element.formData);
}
// 参数赋值[赋值权限最大]
if (props.formAssignmentData) {
if (props.formAssignmentData[element.formConfig.formId]) {
formModels = { ...formModels, ...props.formAssignmentData[element.formConfig.formId] };
}
}
forms.formModels.push(formModels);
// 默认赋值view
forms.modes.push('view');
// 系统表单
if (element.formType == FormType.SYSTEM) {
forms.configs.push({
formName: element.formConfig.formName,
formProps: {},
formModel: formModels,
formKey: element.formConfig.key,
validate: true,
formType: element.formType,
workflowPermissions: element.formConfig.children,
opinions: props.opinions,
opinionsComponents: props.opinionsComponents,
systemComponent: {
functionalModule: element.functionalModule,
functionName: element.functionName,
functionFormName: 'Form',
},
formJson: element.formJson,
isOldSystem: false,
});
// 上传组件Id集合
setTimeout(() => {
getSystemUploadComponentIds();
}, 0);
} else {
const model = JSON.parse(element.formJson) as GeneratorConfig;
const { formJson, formEventConfig } = model;
if (formEventConfig) {
forms.formEventConfigs.push(formEventConfig);
//初始化表单
await createFormEvent(formEventConfig, formModels, true);
//加载表单
await loadFormEvent(formEventConfig, formModels, true);
//TODO 暂不放开 工作流没有获取表单数据这个步骤 获取表单数据
// getFormDataEvent(formEventConfig, formModels,true);
}
let formKey = element.formConfig.key;
let config = {
formName: element.formConfig.formName,
formProps: {},
formModel: {},
formKey,
validate: true,
formType: element.formType,
};
let isViewProcess = props.disabled;
let { buildOptionJson, uploadComponentIds } = changeFormJson(
{
formJson,
formConfigChildren: element.formConfig.children,
formConfigKey: element.formConfig.key,
opinions: props.opinions,
opinionsComponents: props.opinionsComponents,
},
isViewProcess,
uploadComponent.ids,
);
uploadComponent.ids = uploadComponentIds;
if (buildOptionJson.schemas) {
config.formProps = buildOptionJson;
forms.configs.push(config);
}
}
// });
}
await nextTick();
setTimeout(() => {
setFormModel();
}, 0);
emits('getFormConfigs', forms.configs.length ? forms.configs[activeIndex.value] : null);
});
function setFormModel() {
for (let index = 0; index < itemRefs.value.length; index++) {
if (itemRefs.value[index]) {
itemRefs.value[index].setFieldsValue(forms.formModels[index]);
}
}
}
async function setFormData(formData) {
await nextTick();
forms.formModels = formData;
setFormModel();
}
function changeActiveIndex(index: number) {
activeIndex.value = index;
emits('getFormConfigs', forms.configs[activeIndex.value]);
}
function getSystemUploadComponentIds() {
for (let index = 0; index < itemRefs.value.length; index++) {
if (itemRefs.value[index] && itemRefs.value[index].getUploadComponentIds) {
let ids = itemRefs.value[index].getUploadComponentIds();
uploadComponent.ids = [...uploadComponent.ids, ...ids];
}
}
}
// 获取上传组件的字段值集合
function getUploadComponentIds() {
return uploadComponent.ids;
}
async function validateForm() {
let validateForms: ValidateForms = [];
for (let index = 0; index < itemRefs.value.length; index++) {
if (itemRefs.value[index]) {
try {
await itemRefs.value[index]?.validate();
validateForms.push({
validate: true,
msgs: [],
isOldSystem: forms.configs[index].isOldSystem,
});
forms.configs[index].validate = true;
} catch (error: any | Array<{ errors: Array<string>; name: Array<string> }>) {
validateForms.push({
validate: false,
msgs: error?.errorFields,
});
forms.configs[index].validate = false;
}
}
}
return validateForms;
}
async function saveDraftData() {
let formModes = {};
for (let index = 0; index < forms.configs.length; index++) {
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
let values = await itemRefs.value[index].validate();
formModes[ele.formKey] = values;
} else {
formModes[ele.formKey] = ele.formModel;
}
}
return formModes;
}
async function getFormModels(saveRowKey,isOnlyActive) {
let formModes = {};
for (let index = 0; index < forms.configs.length; index++) {
if (isOnlyActive && index != activeIndex.value) {
continue;
}
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
let values = await itemRefs.value[index].workflowSubmit(saveRowKey);
formModes[ele.formKey] = values;
} else {
formModes[ele.formKey] = ele.formModel;
}
}
// forms.configs.forEach((ele) => {
// formModes[ele.formKey] = ele.formModel;
// });
forms.formEventConfigs.forEach(async (ele, i) => {
if (isOnlyActive && i != activeIndex.value) {
return true;
}
//此组件 获取数据 就是为了提交表单 所以 表单提交数据 事件 就此处执行
await submitFormEvent(ele, forms.configs[i]?.formModel);
});
return formModes;
}
function getSystemType() {
let system = {};
for (let index = 0; index < forms.configs.length; index++) {
const ele = forms.configs[index];
if (ele.formType == FormType.SYSTEM) {
system[ele.formKey] = itemRefs.value[index].getIsOldSystem();
}
}
return system;
}
function handleCancel() {
itemRefs.value[activeIndex.value].setDisabledForm(true);
forms.modes[activeIndex.value] = 'view';
itemRefs.value[activeIndex.value].setFieldsValue(forms.formModels[activeIndex.value]);
}
async function handleDelete() {
Modal.confirm({
title: '提示信息',
icon: createVNode(ExclamationCircleOutlined),
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',
async onOk() {
try{
let formVal = await itemRefs.value[activeIndex.value].getFormModels();
await itemRefs.value[activeIndex.value].handleDelete(formVal.id)
message.success('删除成功')
setTimeout(() => {
tabStore.closeTab(currentRoute.value, router);
bus.emit(FLOW_PROCESSED);
}, 1000)
} catch (err){
message.error('删除失败,请稍后再试');
}
},
onCancel() {},
});
}
async function handleSave() {
const params = await getFormModels(true, true);
const code = await updateWorkflow({ 'variables': params, 'processInstanceId': flowInfo.value.processId })
if (code) {
message.success(t('保存成功'));
} else {
message.success(t('保存失败,请稍后再试'));
}
itemRefs.value[activeIndex.value].setDisabledForm(true);
forms.modes[activeIndex.value] = 'view';
}
function handleEdit() {
itemRefs.value[activeIndex.value].setDisabledForm(false);
forms.modes[activeIndex.value] = 'edit';
}
defineExpose({
validateForm,
getFormModels,
saveDraftData,
setFormData,
getUploadComponentIds,
getSystemType,
handleEdit,
handleSave,
handleCancel,
handleDelete
});
</script>
<style lang="less" scoped>
.form-container {
display: flex;
height: 100%;
margin-top: -10px;
}
.box {
width: 100%;
.form-left {
float: left;
height: 100vh;
box-shadow: 5px 5px 5px rgb(0 0 0 / 10%);
z-index: 9998;
.resize-shrink-sidebar {
cursor: col-resize;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
bottom: 0;
right: 0;
z-index: 9999;
.shrink-sidebar-text {
padding: 0 2px;
background: #f2f2f2;
border-radius: 10px;
}
}
.left-box {
margin-right: 10px;
border-right: 1px solid #f0f0f0;
height: 80vh;
}
span {
font-size: 16px;
font-weight: 500;
padding-left: 4px;
}
.form-name {
height: 36px;
display: flex;
align-items: center;
font-size: 14px;
cursor: pointer;
color: rgb(102 102 102 / 99.6%);
margin-right: -2px;
padding-left: 4px;
}
.actived {
border-right: 1px solid #5e95ff;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: transparent;
margin-right: 4px;
}
.validate {
background-color: @clear-color;
}
.icon-box {
font-size: 16px;
margin-right: 4px;
}
.left-title {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
padding: 10px 4px 10px 0;
top: 0;
left: 0;
.in-or-out {
position: absolute;
right: 10px;
}
}
}
.form-box {
overflow: auto;
height: calc(100vh - 280px);
}
.form-right {
width: 100%;
padding-top: 20px;
}
}
.page-bg-wrap {
background-color: #fff;
}
.top-toolbar {
min-height: 44px;
margin-bottom: 12px;
border-bottom: 1px solid #eee;
}
</style>

View File

@ -1,6 +1,7 @@
<template>
<a-spin :spinning="spinning" tip="请稍后...">
<div class="page-bg-wrap">
<div class="top-toolbar">
<div class="top-toolbar" id="formViewPage">
<a-space :size="10" wrap style="gap:0">
<a-button style="margin-right: 10px" @click="close">
<slot name="icon">
@ -18,6 +19,7 @@
</div>
<component :is="dynamicComponent" ref="formRef" :fromPage="FromPageType.MENU" @form-mounted="onFormMounted" />
</div>
</a-spin>
</template>
<script setup>
@ -46,6 +48,8 @@ const tabStore = useMultipleTabStore();
const formProps = ref(null);
const formId = ref(currentRoute.value?.params?.id);
const spinning = ref(false)
const { notification } = useMessage();
const { t } = useI18n();
const hash = location.hash||location.pathname;
@ -84,6 +88,7 @@ function close() {
}
async function handleSubmit() {
spinning.value = true
try {
const saveSuccess = await saveModal();
if (saveSuccess) {
@ -98,6 +103,7 @@ async function handleSubmit() {
}, 1000);
}
} finally {
spinning.value = false
}
}
@ -105,7 +111,8 @@ async function saveModal() {
let saveSuccess = false;
const _mode = mode.value;
try {
const values = await formRef.value?.validate();
await formRef.value?.validate();
const values = (formRef.value?.getFormModal && formRef.value.getFormModal()) || await formRef.value?.validate();
//添加隐藏组件
if (formProps.hiddenComponent?.length) {
formProps.hiddenComponent.forEach((component) => {

View File

@ -0,0 +1,82 @@
<template>
<div class="flow-page">
<FormInformation
v-if="!isSelfForm"
:key="renderKey"
ref="formInformation"
:disabled="readonly"
:formAssignmentData="data.formAssignmentData"
:formInfos="data.formInfos"
:opinions="data.opinions"
:opinionsComponents="data.opinionsComponents"
@get-form-configs="(config) => (formConfigs = config)"
/>
<component v-else-if="data" :is="componentName" :formData="data" :flowData="flowData"/>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
import { onMounted, reactive, ref, unref, createVNode, provide, defineExpose, computed, defineAsyncComponent } from 'vue';
import FormInformation from './flowFormInformation.vue';
import { getApprovalProcess } from '/@/api/workflow/task';
import Title from '/@/components/Title/src/Title.vue';
import { message, Modal } from 'ant-design-vue';
const selfFormList = [
// 流程编码
// 'exchange_opinion',
// 'audit_programme'
]
const selfFormMap = {
// 流程编码对应文件路径
// 'exchange_opinion': 'auditOperationsCenter/exchangeViews/index.vue',
// 'audit_programme': 'auditOperationsCenter/preAuditProgramme/index.vue'
}
const isSelfForm = computed(() => {
return selfFormList.includes(props.data.item.code)
})
const componentName = computed(() => {
return defineAsyncComponent({
loader: () =>
import(
`../${selfFormMap[props.data.item.code]}`
),
})
})
const readonly = ref(true); // 查看流程会触发只读模式
const renderKey = ref('');
const formConfigs = ref();
const formInformation = ref();
const props = defineProps({
data: {
type: Object,
default: () => null
},
flowData: {
type: Object,
default: () => null
}
});
onMounted(async () => {
});
defineExpose({
})
</script>
<style lang="less" scoped>
.flow-page {
position: relative;
box-sizing: border-box;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,144 @@
<template>
<div class="process-monitoring-page">
<a-spin :spinning="spinning" tip="请稍后...">
<a-tabs tab-position="top" v-model:activeKey="activeKey" type="card">
<a-tab-pane :key="1" :tab="'表单信息'" force-render>
</a-tab-pane>
<a-tab-pane :key="2" :tab="'流程信息'">
</a-tab-pane>
<a-tab-pane :key="3" :tab="'流转记录'" style="overflow: auto">
</a-tab-pane>
<!-- <a-tab-pane :key="4" :tab="'附件汇总'">
<SummaryOfAttachments :processId="processId"/>
</a-tab-pane> -->
<a-tab-pane :key="5" :tab="'流程变更记录'">
</a-tab-pane>
<a-tab-pane :key="6" :tab="'审批记录'">
</a-tab-pane>
<a-tab-pane :key="7" :tab="'流程变量'">
</a-tab-pane>
<!-- <a-tab-pane :key="8 + index" v-for="(item, index) in predecessorTasks" :tab="item.schemaName">
<LookRelationTask
v-show="activeKey === 8 + index"
:taskId="item.taskId"
:processId="item.processId"
position="left"
/>
</a-tab-pane> -->
</a-tabs>
<div class="tab-content" v-if="dataComplete">
<processMonitoringFlowPage :data="data" :flowData="flowData" v-show="activeKey == '1'"></processMonitoringFlowPage>
<ProcessInformationWithInfo :process-id="processId" :data="data" :flowData="flowData" v-if="activeKey == '2'" :canClick="true" :key="ProcessInformationKey"/>
<FlowRecord :list="data.taskRecords" :processId="processId" v-show="activeKey == '3'"/>
<ChangeRecord v-if="activeKey == 5" :processId="processId" :formDataId="data.formInfos[0].formData.id" :formInfos="data.formInfos" />
<AuditRecord :processId="processId" :schemaId="schemaId" :xml="xml" v-show="activeKey == '6'"/>
<ProcVarPage :processId="processId" :schemaId="schemaId" :xml="xml" v-show="activeKey == '7'"/>
</div>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, unref, provide } from 'vue';
import FlowRecord from '/@/views/workflow/task/components/flow/FlowRecord.vue';
import ProcessInformationWithInfo from '/@/views/workflow/task/components/flow/ProcessInformationWithInfo.vue';
import SummaryOfAttachments from '/@/views/workflow/task/components/flow/SummaryOfAttachments.vue';
import LookRelationTask from '/@/views/workflow/task/components/flow/LookRelationTask.vue';
import { SchemaTaskItem } from '/@/model/workflow/bpmnConfig';
import ChangeRecord from '/@/views/formChange/formChangeLog/index.vue';
import AuditRecord from '/@/views/auditOpt/auditRecord/index.vue';
import processMonitoringFlowPage from './processMonitoringFlowPage.vue'
// import ProcVarPage from '/@/views/editProVar/procVarManage/index.vue';
import { getApprovalProcess } from '/@/api/workflow/task';
import { Modal } from 'ant-design-vue';
import userTaskItem from '/@/views/workflow/task/hooks/userTaskItem';
const { data, initProcessData } = userTaskItem();
import { useRouter } from 'vue-router';
const router = useRouter();
const currentRoute = router.currentRoute.value;
const fullPath = currentRoute.fullPath;
const rQuery = currentRoute.query;
const rParams = currentRoute.params;
const schemaId = ref(rParams.arg1);
const processId = ref(rParams.arg2);
const ProcessInformationKey = ref(0)
const spinning = ref(false);
const dataComplete = ref(false);
const flowInfo = ref({
schemaId: schemaId.value,
processId: processId.value
})
const flowData = ref({})
provide('flowInfo', flowInfo)
provide('refreshApproveInfo', getApproveInfo)
const activeKey = ref(1);
onMounted(async () => {
await getApproveInfo()
});
async function getApproveInfo() {
try {
spinning.value = true
let res = await getApprovalProcess('', unref(processId.value));
initProcessData(res);
flowData.value = res
spinning.value = false
dataComplete.value = true
ProcessInformationKey.value++
} catch (error) {
console.error('processMonitoringPage Error', error)
}
}
</script>
<style lang="less" scoped>
.process-monitoring-page {
height: 100%;
width: 100%;
:deep(.ant-spin-nested-loading) {
height: 100%;
width: 100%;
display: flex;
.ant-spin-container {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
padding: 20px;
.ant-tabs-nav {
margin: 0;
}
.top-toolbar {
display: flex;
border-bottom: none;
padding: 16px 0;
}
.tab-content {
flex: 1;
min-height: 0;
overflow: auto;
box-sizing: border-box;
display: flex;
.process-information-with-info {
flex: 1;
display: flex;
flex-direction: column;
.flow-record-box {
flex: 1;
}
}
}
}
}
}
</style>

View File

@ -10,47 +10,107 @@
v-show="showDate"
>
<LockOutlined />
<span>{{ t('sys.lock.unlock') }}</span>
<span>{{ t('解锁') }}</span>
</div>
<div class="flex w-screen h-screen justify-center items-center">
<div :class="`${prefixCls}__hour`" class="relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5">
<div class="flex flex-col w-screen h-screen justify-center items-center">
<!-- 第一行logo和系统名 -->
<div :class="`${prefixCls}__logo-row`" class="flex items-center justify-center mb-8">
<img :src="logoConfig.menuLogoUrl || logo" width="144" style="height: 144px" />
<div :class="`${prefixCls}__sysname-box`" class="ml-4">
{{ sysName }}
</div>
</div>
<!-- 第二行hour和minute并排 -->
<div class="flex items-center justify-center">
<div :class="`${prefixCls}__hour`" class="relative mr-5 md:mr-20">
<span>{{ hour }}</span>
<span class="meridiem absolute left-5 top-5 text-md xl:text-xl" v-show="showDate">
{{ meridiem }}
</span>
</div>
<div :class="`${prefixCls}__minute w-2/5 h-2/5 md:h-4/5 `">
<div :class="`${prefixCls}__minute`">
<span> {{ minute }}</span>
</div>
</div>
<!-- 新增lockErrorMsg 显示 -->
<div v-if="lockErrorMsg" style="margin-top: 16px; color: #ff4d4f; font-size: 1.2rem;">
{{ lockErrorMsg }}
</div>
</div>
<transition name="fade-slide">
<div :class="`${prefixCls}-entry`" v-show="!showDate">
<div :class="`${prefixCls}-entry-content`">
<div :class="`${prefixCls}-entry__header enter-x`">
<img :src="userinfo.avatar || headerImg" :class="`${prefixCls}-entry__header-img`" />
<p :class="`${prefixCls}-entry__header-name`">
{{ userinfo.realName }}
{{ userinfo.name }}
</p>
<p :class="`${prefixCls}-entry__header-name`">
{{ userinfo.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
</p>
</div>
<template v-if="loginType === 'password'">
<InputPassword
:placeholder="t('sys.lock.placeholder')"
:placeholder="t('登录密码')"
class="enter-x"
v-model:value="password"
@pressEnter="handleLogin"
/>
<span :class="`${prefixCls}-entry__err-msg enter-x`" v-if="errMsg">
{{ t('sys.lock.alert') }}
{{ t('请重新登录') }}
</span>
</template>
<div v-else>
<Input v-model:value="formData.code" :placeholder="t('验证码')" class="enter-x" size="small" style="height: 58px;">
<template #suffix>
<Button type="link" class="f-16" @click="getLoginCode" size="small" :disabled="codeButtonDisabled">
{{ getCodeButtonName }}
</Button>
</template>
</Input>
</div>
<div style="margin-top: 10px">
<a-button :style="{ 'border-radius': '8px' }" block class="sub-button" type="primary" @click="handleLogin">
解锁
</a-button>
</div>
<div :class="`${prefixCls}-entry__footer enter-x`">
<a-button
type="link"
size="small"
class="mt-2 mr-2 enter-x"
:disabled="loading"
style="font-size: 1.2rem;"
@click="handleShowForm(true)"
>
{{ t('common.back') }}
<!-- 返回锁屏界面 -->
{{ t('取消') }}
</a-button>
<a-button
type="link"
size="small"
class="mt-2 mr-2 enter-x"
:disabled="loading"
style="font-size: 1.2rem;"
@click="goLogin"
>
{{ t('返回登录界面') }}
</a-button>
<a-button
class="mt-2"
type="link"
size="small"
style="font-size: 1.2rem;"
@click="changeLoginType"
:loading="loading">
{{ loginType == 'sms' ? t('密码解锁') : t('短信解锁') }}
</a-button>
</div>
</div>
<!-- <div class="back_login">
<a-button
type="link"
size="small"
@ -58,13 +118,9 @@
:disabled="loading"
@click="goLogin"
>
{{ t('sys.lock.backToLogin') }}
{{ t('返回登录界面') }}
</a-button>
<a-button class="mt-2" type="link" size="small" @click="unLock()" :loading="loading">
{{ t('sys.lock.entry') }}
</a-button>
</div>
</div>
</div> -->
</div>
</transition>
@ -74,6 +130,28 @@
</div>
<div class="text-2xl">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
</div>
<a-modal
v-model:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
:maskClosable="false"
centered
title="请完成下列验证后继续"
width="20%"
cancelText=""
>
<div class="login-modal-content">
<div class="refresh" @click="refreshTodo">
刷新
<a-icon :spin="refreshLoading" icon="ant-design:redo-outlined" class="redo-outlined" />
</div>
<a-image
:width="200"
:src="imgObj.imgBase64 || ''"
/>
<a-input v-model:value="imgCode" :placeholder="t('验证码')" size="large" />
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
@ -87,16 +165,42 @@
import { LockOutlined } from '@ant-design/icons-vue';
import headerImg from '/@/assets/images/header.jpg';
import { useAppStore } from '/@/store/modules/app';
import logo from '/@/assets/images/logo_geg.png';
import { getMobileLoginCode, getMobileLoginImg } from '/@/api/system/login';
const appStore = useAppStore();
const logoConfig = appStore.getLogoConfig;
const sysName = ref(import.meta.env.VITE_SYSTEM_NAME);
const InputPassword = Input.Password;
const password = ref('');
const captchaCode = ref('');
const formData = ref({
code: ''
});
const imgBase64 = ref('');
const loginType = ref('password');
const loading = ref(false);
const errMsg = ref(false);
const showDate = ref(true);
const lockErrorMsg = computed(() => lockStore.getLockInfo?.msg || '');
const { prefixCls } = useDesign('lock-page');
const lockStore = useLockStore();
const userStore = useUserStore();
const getCodeButtonName = ref('获取验证码')
const codeButtonDisabled = ref(false)
const countdown = ref(60)
const visible = ref(false);
const refreshLoading = ref(false);
const imgCode = ref('');
const imgObj = ref({
imgBase64: ''
})
let setCodeInterval = null
const { hour, month, minute, meridiem, year, day, week } = useNow(true);
@ -131,12 +235,87 @@
function handleShowForm(show = false) {
showDate.value = show;
}
const getLoginCode = async () => {
countdown.value = 60
visible.value = true
imgCode.value = ''
await onMobileLoginImg()
}
async function onMobileLoginImg() {
imgObj.value = await getMobileLoginImg({account: userinfo.value.mobile})
}
async function refreshTodo() {
refreshLoading.value = true
await onMobileLoginImg()
refreshLoading.value = false
}
async function handleOk() {
await getMobileLoginCode({captchaCode: imgCode.value ,mobile: userinfo.value.mobile})
setCodeInterval = setInterval(updateCountdown, 1000);
onVisible()
}
function updateCountdown() {
if (countdown.value === 0) {
getCodeButtonName.value = '获取验证码';
codeButtonDisabled.value = false;
clearInterval(setCodeInterval)
} else {
countdown.value--;
getCodeButtonName.value = countdown.value + ' 秒后可重发';
codeButtonDisabled.value = true;
}
}
function onVisible () {
visible.value = false
imgObj.value.imgBase64 = ''
}
function handleCancel() {
onVisible()
}
function changeLoginType () {
loginType.value = loginType.value == 'password' ? 'sms' : 'password'
}
/**
* @description: unLockByPhone
*/
async function unLockByPhone() {
if (!formData.value.code) {
return;
}
let code = formData.value.code;
try {
loading.value = true;
const res = await lockStore.unLockByPhone(code);
errMsg.value = !res;
} finally {
loading.value = false;
}
}
function handleLogin() {
const obj = {
password: unLock,
sms: unLockByPhone,
}
obj[loginType.value]()
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-lock-page';
.@{prefix-cls} {
z-index: @lock-page-z-index;
z-index: 999;
// 新增或修改背景色
background: linear-gradient(180deg, #00356d 0%, rgb(0 53 109 / 0%) 100%), linear-gradient(180deg, #0074d3 2%, #011853 100%);
&__unlock {
transform: translate(-50%, 0);
@ -146,8 +325,8 @@
&__minute {
display: flex;
font-weight: 700;
color: #bababa;
background-color: #141313;
color: #ffffff;
background-color: linear-gradient(180deg, #00356d 0%, rgb(0 53 109 / 0%) 100%), linear-gradient(180deg, #0074d3 2%, #011853 100%);
border-radius: 30px;
justify-content: center;
align-items: center;
@ -187,6 +366,27 @@
}
}
&__logo-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
img {
width: 144px;
height: 144px;
border-radius: 50%;
}
}
&__sysname-box {
font-size: 6rem;
font-weight: 600;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
&-entry {
position: absolute;
top: 0;
@ -200,7 +400,7 @@
align-items: center;
&-content {
width: 260px;
width: 390px;
}
&__header {
@ -231,4 +431,16 @@
}
}
}
.back_login {
position: absolute;
left: 10px;
top: 10px;
}
.login-modal-content {
text-align: center;
.refresh {
text-align: right;
cursor: pointer;
}
}
</style>

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>

Some files were not shown because too many files have changed in this diff Show More