Merge branch 'dev' into dev-cjw

This commit is contained in:
chen_junior
2025-04-21 11:36:26 +08:00
22 changed files with 885 additions and 142 deletions

View File

@ -34,6 +34,7 @@ enum Api {
loginConfig = '/system/loginConfig', loginConfig = '/system/loginConfig',
mobileLoginCode = '/system/captcha', mobileLoginCode = '/system/captcha',
mobileLoginImg = '/system/captchaImg', mobileLoginImg = '/system/captchaImg',
checkAccountCaptcha = '/system/checkAccountCaptcha',
} }
/** /**
@ -262,3 +263,14 @@ export function sendMobileLoginCode(params, mode: ErrorMessageMode = 'modal') {
}, },
); );
} }
export function checkAccountCaptchaApi(params, mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{
url: Api.checkAccountCaptcha,
params: params,
},
{
errorMessageMode: mode,
},
);
}

View File

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

View File

@ -22,12 +22,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed, defineAsyncComponent, reactive } from 'vue'; import { ref, onMounted, computed, defineAsyncComponent, reactive,inject } from 'vue';
import { FromPageType } from '/@/enums/workflowEnum'; import {ApproveCode, FromPageType} from '/@/enums/workflowEnum';
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue'; import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
import { GeneratorConfig } from '/@/model/generator/generatorConfig'; import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { createFormEvent, loadFormEvent } from '/@/hooks/web/useFormEvent'; import { createFormEvent, loadFormEvent } from '/@/hooks/web/useFormEvent';
import { changeFormJson } from '/@/hooks/web/useWorkFlowForm'; import { changeFormJson } from '/@/hooks/web/useWorkFlowForm';
import {message} from "ant-design-vue";
const props = defineProps({ const props = defineProps({
systemComponent: { systemComponent: {
@ -63,6 +64,7 @@
default: false, default: false,
}, },
}); });
const approvalData = ref(null);
const SystemFormRef = ref(); const SystemFormRef = ref();
const visible = ref(false); const visible = ref(false);
const formProps = ref({}); const formProps = ref({});
@ -129,6 +131,7 @@
onMounted(() => { onMounted(() => {
visible.value = true; visible.value = true;
approvalData.value = inject("approvalData");
}); });
// //
@ -168,11 +171,30 @@
} }
async function setFieldsValue(record) { async function setFieldsValue(record) {
flowConfig.draftsFormData = record; flowConfig.draftsFormData = record;
SystemFormRef.value?.setFieldsValue(record);
}
async function getFieldsValue(){
return SystemFormRef.value.getFieldsValue();
}
async function getValue(){
let values = null;
if(approvalData.value?.approvedResult === ApproveCode.FINISH){
//终止的单据不走校验逻辑直接更新
try{
//有些旧表单可能没有生成getFieldsValue
values = await SystemFormRef.value.getFieldsValue();
}catch (e){}
}
if(values === null){
values = await SystemFormRef.value.validate();
}
//添加一个flowAction标志用于合同单号回收
values.flowAction = approvalData.value?.approvedResult;
return values;
} }
async function workflowSubmit(saveRowKey) { async function workflowSubmit(saveRowKey) {
let values = {};
try { try {
values = await SystemFormRef.value.validate(); let values = await getValue();
// 提交表单 // 提交表单
if (visible.value) { if (visible.value) {
let id = await submit(saveRowKey); let id = await submit(saveRowKey);
@ -189,6 +211,7 @@
return values; return values;
} catch (error) {} } catch (error) {}
} }
async function submit(saveRowKey) { async function submit(saveRowKey) {
let saveValId = ''; let saveValId = '';
let values = await SystemFormRef.value.validate(); let values = await SystemFormRef.value.validate();
@ -215,6 +238,10 @@
return SystemFormRef.value.setDisabledForm(isDisabled); return SystemFormRef.value.setDisabledForm(isDisabled);
} }
async function resetFields(){
return SystemFormRef.value.resetFields();
}
async function handleDelete(id) { async function handleDelete(id) {
let ret; let ret;
try { try {
@ -231,7 +258,9 @@
getRowKey, getRowKey,
validate, validate,
getUploadComponentIds, getUploadComponentIds,
resetFields,
setFieldsValue, setFieldsValue,
getFieldsValue,
getIsOldSystem, getIsOldSystem,
setDisabledForm, setDisabledForm,
handleDelete handleDelete

View File

@ -467,16 +467,16 @@ function getTableItemConfig(tableName: string, element) {
} }
// 辅助设置表单Disabled // 辅助设置表单Disabled
export function changeSchemaDisabled(schemas) { export function changeSchemaDisabled(schemas,isDisabled=true) {
const layoutComponents = ['tab', 'grid', 'card']; const layoutComponents = ['tab', 'grid', 'card'];
schemas?.map((info) => { schemas?.map((info) => {
if (layoutComponents.includes(info.type!)) { if (layoutComponents.includes(info.type!)) {
info.children?.map((childInfo) => { info.children?.map((childInfo) => {
childInfo.list.map((com) => { childInfo.list.map((com) => {
if (layoutComponents.includes(com.type)) { if (layoutComponents.includes(com.type)) {
changeSchemaDisabled(childInfo.list); changeSchemaDisabled(childInfo.list,isDisabled);
} else { } else {
com.dynamicDisabled = true; com.dynamicDisabled = isDisabled;
} }
}); });
}); });
@ -485,17 +485,24 @@ export function changeSchemaDisabled(schemas) {
childInfo.list.map((com) => { childInfo.list.map((com) => {
com.children.map((el) => { com.children.map((el) => {
if (layoutComponents.includes(el.type) || el.type == 'table-layout') { if (layoutComponents.includes(el.type) || el.type == 'table-layout') {
changeSchemaDisabled(com.children); changeSchemaDisabled(com.children,isDisabled);
} else { } else {
el.dynamicDisabled = true; el.dynamicDisabled = isDisabled;
} }
}); });
}); });
}); });
} else if (info.type == 'one-for-one') { } else if (info.type == 'one-for-one') {
changeSchemaDisabled(info.componentProps.childSchemas); changeSchemaDisabled(info.componentProps.childSchemas,isDisabled);
} else if (info.type == 'form') {
info.dynamicDisabled = isDisabled;
info.componentProps.disabled = isDisabled;
info.componentProps.columns?.forEach((column) => {
column.dynamicDisabled = isDisabled;
if(column?.componentProps) column.componentProps.disabled = isDisabled
})
} else { } else {
info.dynamicDisabled = true; info.dynamicDisabled = isDisabled;
} }
}); });
return schemas; return schemas;

View File

@ -109,11 +109,12 @@ export interface ProcessConfig {
globalEndEventConfigs: NodeEventConfig[];//全局 用户节点 结束事件 globalEndEventConfigs: NodeEventConfig[];//全局 用户节点 结束事件
globalPrequalifyBeforeEventConfigs: NodeEventConfig[];//预审前 globalPrequalifyBeforeEventConfigs: NodeEventConfig[];//预审前
globalPrequalifyAfterEventConfigs: NodeEventConfig[];//预审后 globalPrequalifyAfterEventConfigs: NodeEventConfig[];//预审后
globalFinishEventConfigs: NodeEventConfig[];//终止事件 globalFinishBeforeEventConfigs: NodeEventConfig[];//终止事件
globalRejectEventConfigs: NodeEventConfig[];//全局退回事件 globalRejectAfterEventConfigs: NodeEventConfig[];//全局退回事件
globalAgreeEventConfigs: NodeEventConfig[];//全局同意事件 globalAgreeAfterEventConfigs: NodeEventConfig[];//全局同意事件
globalSuspendedEventConfigs: NodeEventConfig[];//全局 挂起/暂停事件 globalSuspendedBeforeEventConfigs: NodeEventConfig[];//全局 挂起/暂停事件
globalRestoreEventConfigs: NodeEventConfig[];//全局 恢复事件 globalRestoreAfterEventConfigs: NodeEventConfig[];//全局 恢复事件
globalSetSignAfterEventConfigs: NodeEventConfig[];//全局 会签事件
xmlContent: String; //xml xmlContent: String; //xml
} }
@ -287,6 +288,11 @@ export interface BasicNodeConfig {
remark: string; //节点描述 remark: string; //节点描述
startEventConfigs: NodeEventConfig[]; startEventConfigs: NodeEventConfig[];
endEventConfigs: NodeEventConfig[]; endEventConfigs: NodeEventConfig[];
prequalifyBeforeEventConfigs: [],//预审前
prequalifyAfterEventConfigs: [],//预审后
rejectAfterEventConfigs: [],//退回后事件
agreeAfterEventConfigs: [],//同意后事件
setSignAfterEventConfigs: [],//会签后事件
} }
export interface NodeEventConfig { export interface NodeEventConfig {

View File

@ -1745,8 +1745,8 @@ export function buildSimpleFormCode(model: GeneratorConfig, _tableInfo: TableInf
await systemFormRef.value.resetFields(); await systemFormRef.value.resetFields();
} }
// 设置表单数据全部为Disabled 【查看】 // 设置表单数据全部为Disabled 【查看】
async function setDisabledForm() { async function setDisabledForm(isDisabled) {
data.formDataProps.schemas = changeSchemaDisabled(cloneDeep(data.formDataProps.schemas)); data.formDataProps.schemas = changeSchemaDisabled(cloneDeep(data.formDataProps.schemas),isDisabled);
} }
// 获取行键值 // 获取行键值
function getRowKey() { function getRowKey() {

View File

@ -3,7 +3,6 @@
<BasicTable @register="registerTable" ref="tableRef" @row-dbClick="dbClickRow"> <BasicTable @register="registerTable" ref="tableRef" @row-dbClick="dbClickRow">
<template #toolbar> <template #toolbar>
<template v-for="button in tableButtonConfig" :key="button.code"> <template v-for="button in tableButtonConfig" :key="button.code">
<a-button v-if="button.isDefault" :type="button.type" @click="buttonClick(button.code)"> <a-button v-if="button.isDefault" :type="button.type" @click="buttonClick(button.code)">

View File

@ -279,3 +279,8 @@
} }
} }
</style> </style>
<style scoped>
:deep(.center-box) {
width: 540px;
}
</style>

View File

@ -3,7 +3,7 @@
<div> <div>
<FormItem class="enter-x" name="account" v-if="loginType =='pw'"> <FormItem class="enter-x" name="account" v-if="loginType =='pw'">
<label class="form-title"> {{ t('账号') }}</label> <label class="form-title"> {{ t('账号') }}</label>
<Input v-model:value="formData.account" :placeholder="t('账号')" class="fix-auto-fill" size="large" style="height: 58px;"> <Input v-model:value="formData.account" :placeholder="t('账号')" class="fix-auto-fill" size="large" style="height: 58px;" @blur="handleBlur">
<template #prefix> <template #prefix>
<IconFontSymbol class="user-icon" icon="yonghu-xianxing" /> <IconFontSymbol class="user-icon" icon="yonghu-xianxing" />
</template> </template>
@ -17,6 +17,22 @@
</template> </template>
</InputPassword> </InputPassword>
</FormItem> </FormItem>
<FormItem class="enter-x" name="captchaCode" v-if="loginType =='pw' && loginUseType == 'captcha'">
<label class="form-title"> {{ t('图形验证码') }}</label>
<Input v-model:value="formData.captchaCode" :placeholder="t('图形验证码')" class="fix-auto-fill" size="large" style="height: 58px;" visibilityToggle>
<template #prefix>
<PictureOutlined :style="{fontSize: '25px', color: '#717c91'}"/>
</template>
<template #suffix>
<a-image
:src="captchaImage.imgBase64 || ''"
alt="验证码"
class="captcha-image"
/>
<ReloadOutlined :style="{fontSize: '25px', color: '#717c91'}" @click="refreshCaptcha"/>
</template>
</Input>
</FormItem>
<FormItem v-if="getAppEnvConfig().VITE_TENANT_ENABLED && loginType =='pw'" name="tenantCode" class="enter-x"> <FormItem v-if="getAppEnvConfig().VITE_TENANT_ENABLED && loginType =='pw'" name="tenantCode" class="enter-x">
<label class="form-title"> {{ t('租户码') }}</label> <label class="form-title"> {{ t('租户码') }}</label>
@ -43,8 +59,10 @@
<FormItem class="enter-x" name="code" v-if="loginType =='mobile'"> <FormItem class="enter-x" name="code" v-if="loginType =='mobile'">
<label class="form-title"> {{ t('验证码') }}</label> <label class="form-title"> {{ t('验证码') }}</label>
<Input v-model:value="formData.code" :placeholder="t('验证码')" class="fix-auto-fill" size="large" style="height: 58px;"> <Input v-model:value="formData.code" :placeholder="t('验证码')" class="fix-auto-fill" size="large" style="height: 58px;">
<template #prefix>
<PhoneOutlined :style="{fontSize: '25px', color: '#717c91'}"/>
</template>
<template #suffix> <template #suffix>
<!-- <span>111</span> -->
<Button type="link" class="f-16" @click="getLoginCode" size="small" :disabled="codeButtonDisabled"> <Button type="link" class="f-16" @click="getLoginCode" size="small" :disabled="codeButtonDisabled">
{{ getCodeButtonName }} {{ getCodeButtonName }}
</Button> </Button>
@ -64,7 +82,7 @@
<FormItem> <FormItem>
<!-- No logic, you need to deal with it yourself --> <!-- No logic, you need to deal with it yourself -->
<Button type="link" class="f-16" @click="changeLoginType" size="small"> <Button type="link" class="f-16" @click="changeLoginType" size="small">
{{ loginType == 'mobile' ? t('账号密码登录') : t('验证码登录') }} {{ loginType == 'mobile' ? t('账号密码登录') : t('手机登录') }}
</Button> </Button>
</FormItem> </FormItem>
</ACol> </ACol>
@ -118,7 +136,7 @@
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user';
import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from '/@/views/sys/login/useLogin'; import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from '/@/views/sys/login/useLogin';
import { getMobileLoginCode, getMobileLoginImg, sendMobileLoginCode } from '/@/api/system/login'; import { getMobileLoginCode, getMobileLoginImg, sendMobileLoginCode, checkAccountCaptchaApi, } from '/@/api/system/login';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
@ -127,6 +145,8 @@
import { getAppEnvConfig } from '/@/utils/env'; import { getAppEnvConfig } from '/@/utils/env';
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index';
import { PhoneOutlined, PictureOutlined, ReloadOutlined } from '@ant-design/icons-vue';
const ACol = Col; const ACol = Col;
const ARow = Row; const ARow = Row;
const FormItem = Form.Item; const FormItem = Form.Item;
@ -138,7 +158,7 @@
const { currentRoute } = useRouter(); const { currentRoute } = useRouter();
const { getLoginState } = useLoginState(); const { getLoginState, setLoginState } = useLoginState();
const { getFormRules } = useFormRules(); const { getFormRules } = useFormRules();
const formRef = ref(); const formRef = ref();
@ -146,6 +166,7 @@
const loading = ref(false); const loading = ref(false);
const rememberMe = ref(false); const rememberMe = ref(false);
const loginType = ref('mobile') const loginType = ref('mobile')
const loginUseType = ref();
const countdown = ref(60) const countdown = ref(60)
const visible = ref(false); const visible = ref(false);
@ -153,6 +174,9 @@
const imgObj = ref({ const imgObj = ref({
imgBase64: '' imgBase64: ''
}) })
const captchaImage = ref({
imgBase64: ''
})
const imgCode = ref('') const imgCode = ref('')
const formData = reactive({ const formData = reactive({
@ -160,7 +184,8 @@
password: '', password: '',
mobile: '', mobile: '',
code: '', code: '',
tenantCode: 'system' tenantCode: 'system',
captchaCode: ''
}); });
const getCodeButtonName = ref('获取验证码') const getCodeButtonName = ref('获取验证码')
const codeButtonDisabled = ref(false) const codeButtonDisabled = ref(false)
@ -228,6 +253,24 @@
} }
} }
/**
* 账号失焦 请求后台获取业务状态
*/
async function handleBlur() {
if (!formData.account) {
return;
}
let checkAccountCaptcha = await checkAccountCaptchaApi({username: formData.account})
if(checkAccountCaptcha == true) {
loginUseType.value = 'captcha';
// setLoginState(LoginStateEnum.LOGIN_WITH_CAPTCHA)
refreshCaptcha();
} else {
loginUseType.value = '';
// setLoginState(LoginStateEnum.LOGIN)
}
}
function refreshTodo() { function refreshTodo() {
refreshLoading.value = true refreshLoading.value = true
onMobileLoginImg() onMobileLoginImg()
@ -236,7 +279,12 @@
// 图形验证 // 图形验证
async function onMobileLoginImg() { async function onMobileLoginImg() {
imgObj.value = await getMobileLoginImg({mobile: formData.mobile}) imgObj.value = await getMobileLoginImg({account: formData.mobile})
}
// 图形账号验证
async function refreshCaptcha() {
captchaImage.value = await getMobileLoginImg({account: formData.account})
} }
async function handleOk() { async function handleOk() {
@ -283,11 +331,22 @@
if (!data) return; if (!data) return;
if (loginType.value == 'pw') { if (loginType.value == 'pw') {
try { try {
// 校验有没带图形验证码提交
if (loginUseType.value == 'captcha' && !data.captchaCode) {
createErrorModal({
title: t('错误提示'),
content: '请输入图形验证码',
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
});
return;
}
loading.value = true; loading.value = true;
const userInfo = await userStore.login({ const userInfo = await userStore.login({
password: data.password, password: data.password,
userName: data.account, userName: data.account,
tenantCode: data.tenantCode, tenantCode: data.tenantCode,
captchaCode: data.captchaCode,
deviceType: 0, //pc-0,app-1 deviceType: 0, //pc-0,app-1
mode: 'none' //不要默认的错误提示 mode: 'none' //不要默认的错误提示
}); });
@ -309,6 +368,11 @@
} }
} }
} catch (error) { } catch (error) {
if ((error as unknown as Error).message.includes('验证码')) {
loginUseType.value = 'captcha';
// setLoginState(LoginStateEnum.LOGIN_WITH_CAPTCHA);
refreshCaptcha();
}
createErrorModal({ createErrorModal({
title: t('错误提示'), title: t('错误提示'),
content: (error as unknown as Error).message || t('网络异常,请检查您的网络连接是否正常!'), content: (error as unknown as Error).message || t('网络异常,请检查您的网络连接是否正常!'),
@ -316,6 +380,7 @@
}); });
} finally { } finally {
loading.value = false; loading.value = false;
handleBlur();
} }
} else { } else {
try { try {
@ -400,4 +465,14 @@
cursor: pointer; cursor: pointer;
} }
} }
:deep(.captcha-image) {
cursor: pointer;
border: 1px solid #ddd;
border-radius: 4px;
height: 48px;
}
// :deep(.phone-outlined-class) {
// height: 25.99px;
// width: 25.99px;
// }
</style> </style>

View File

@ -9,6 +9,7 @@ export enum LoginStateEnum {
RESET_PASSWORD, RESET_PASSWORD,
MOBILE, MOBILE,
QR_CODE, QR_CODE,
LOGIN_WITH_CAPTCHA,
} }
const currentState = ref(LoginStateEnum.LOGIN); const currentState = ref(LoginStateEnum.LOGIN);
@ -43,6 +44,7 @@ export function useFormRules(formData?: Recordable) {
const getAccountFormRule = computed(() => createRule(t('请输入账号'))); const getAccountFormRule = computed(() => createRule(t('请输入账号')));
const getPasswordFormRule = computed(() => createRule(t('请输入密码'))); const getPasswordFormRule = computed(() => createRule(t('请输入密码')));
const getCaptchaCodeFormRule = computed(() => createRule(t('请输入图形验证码')));
const getSmsFormRule = computed(() => createRule(t('请输入验证码'))); const getSmsFormRule = computed(() => createRule(t('请输入验证码')));
const getMobileFormRule = computed(() => createRule(t('请输入手机号码'))); const getMobileFormRule = computed(() => createRule(t('请输入手机号码')));
@ -65,6 +67,7 @@ export function useFormRules(formData?: Recordable) {
const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => { const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => {
const accountFormRule = unref(getAccountFormRule); const accountFormRule = unref(getAccountFormRule);
const passwordFormRule = unref(getPasswordFormRule); const passwordFormRule = unref(getPasswordFormRule);
const captchaCodeFormRule = unref(getCaptchaCodeFormRule);
const smsFormRule = unref(getSmsFormRule); const smsFormRule = unref(getSmsFormRule);
const mobileFormRule = unref(getMobileFormRule); const mobileFormRule = unref(getMobileFormRule);
@ -96,6 +99,14 @@ export function useFormRules(formData?: Recordable) {
case LoginStateEnum.MOBILE: case LoginStateEnum.MOBILE:
return mobileRule; return mobileRule;
// 枚举没实现,暂不适用
case LoginStateEnum.LOGIN_WITH_CAPTCHA:
return {
account: accountFormRule,
password: passwordFormRule,
captchaCode: captchaCodeFormRule,
};
// login form rules // login form rules
default: default:
return { return {

View File

@ -110,8 +110,8 @@
await systemFormRef.value.resetFields(); await systemFormRef.value.resetFields();
} }
// 设置表单数据全部为Disabled 【查看】 // 设置表单数据全部为Disabled 【查看】
async function setDisabledForm() { async function setDisabledForm(isDisabled) {
data.formDataProps.schemas = changeSchemaDisabled(cloneDeep(data.formDataProps.schemas)); data.formDataProps.schemas = changeSchemaDisabled(cloneDeep(data.formDataProps.schemas), isDisabled);
} }
// 获取行键值 // 获取行键值
function getRowKey() { function getRowKey() {

View File

@ -117,6 +117,100 @@
/> />
</a-form-item> </a-form-item>
· <a-form-item name="checkErrorLoginCaptcha">
<div class="flex">
<div
class="gouBox"
:class="formState.checkErrorLoginCaptcha ? 'active' : ''"
@click="handleCaptchaClick()"
>{{ t('密码错误超过最大次数需要填写验证码') }}
<div class="triangle" v-if="formState.checkErrorLoginCaptcha"
><Icon color="#fff" icon="uil:check"
/></div>
</div>
</div>
<template #label>
<div>验证码策略</div>
<a-tooltip placement="bottomLeft" class="left-reset">
<template #title>
<div style="max-width: 336px">
<p>1.登录验证码策略是指在登录过程中出现密码错误情况时的系统处理方式</p>
<p>2.默认密码错误超过最大次需要验证码登录的策略状态为启用</p>
<p>3.如果设置验证码最大次数为0 即每次登录都需要验证码</p>
</div>
</template>
<QuestionCircleFilled
style="
font-size: 16px;
color: rgb(204 204 204);
position: absolute;
left: -90px;
top: 8px;
"
/>
</a-tooltip>
</template>
</a-form-item>
<a-form-item :label="t('最大次数')" name="checkErrorLoginCaptchaCount">
<a-input-number
v-model:value="formState.checkErrorLoginCaptchaCount"
:placeholder="t('请输入最大次数')"
style="width: 50%"
:min="0"
/>
</a-form-item>
<a-form-item :label="t('锁ip策略')" name="checkErrorLoginBlockIp">
<div class="flex">
<div
class="gouBox"
:class="formState.checkErrorLoginBlockIp ? 'active' : ''"
@click="handleBlockIpClick()"
>{{ t('密码错误超过最大次数锁定') }}
<div class="triangle" v-if="formState.checkErrorLoginBlockIp"
><Icon color="#fff" icon="uil:check"
/></div>
</div>
</div>
<a-tooltip placement="bottomLeft">
<template #title>
<div style="max-width: 336px">
<p>1.锁定ip策略是指在登录过程中出现密码错误情况时的系统处理方式</p>
<p>2.默认密码错误超过最大次数锁定ip的策略状态为启用</p>
<p
>3.当用户的ip被锁定后需要等待配置时间恢复后才能重新登录或者请联系管理员</p
>
</div>
</template>
<QuestionCircleFilled
style="
font-size: 16px;
color: rgb(204 204 204);
position: absolute;
left: -90px;
top: 8px;
"
/>
</a-tooltip>
</a-form-item>
<a-form-item :label="t('最大次数')" name="checkErrorLoginBlockIpCount">
<a-input-number
v-model:value="formState.checkErrorLoginBlockIpCount"
:placeholder="t('请输入最大次数')"
style="width: 50%"
:min="1"
/>
</a-form-item>
<a-form-item :label="t('恢复时间(小时)')" name="checkErrorLoginBlockIpRestoreTime">
<a-input-number
v-model:value="formState.checkErrorLoginBlockIpRestoreTime"
:placeholder="t('请输入恢复时间(小时)')"
style="width: 50%"
:min="1"
/>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 6, span: 24 }"> <a-form-item :wrapper-col="{ offset: 6, span: 24 }">
<a-button type="primary" html-type="submit" class="ml-4">{{ t('提交') }}</a-button> <a-button type="primary" html-type="submit" class="ml-4">{{ t('提交') }}</a-button>
</a-form-item> </a-form-item>
@ -145,6 +239,11 @@
withoutLogin?: number; withoutLogin?: number;
strategyMaxNumber?: number | null; strategyMaxNumber?: number | null;
passwordStrategy?: number; passwordStrategy?: number;
checkErrorLoginCaptcha?: boolean;
checkErrorLoginCaptchaCount?: number | null;
checkErrorLoginBlockIp?: boolean;
checkErrorLoginBlockIpCount?: number | null;
checkErrorLoginBlockIpRestoreTime?: number | null;
} }
let formState = ref<FormState>({}); let formState = ref<FormState>({});
@ -162,6 +261,11 @@
withoutLogin: res.withoutLogin, withoutLogin: res.withoutLogin,
strategyMaxNumber: res.strategyMaxNumber, strategyMaxNumber: res.strategyMaxNumber,
passwordStrategy: res.passwordStrategy, passwordStrategy: res.passwordStrategy,
checkErrorLoginCaptcha: res.checkErrorLoginCaptcha,
checkErrorLoginCaptchaCount: res.checkErrorLoginCaptchaCount,
checkErrorLoginBlockIp: res.checkErrorLoginBlockIp,
checkErrorLoginBlockIpCount: res.checkErrorLoginBlockIpCount,
checkErrorLoginBlockIpRestoreTime: res.checkErrorLoginBlockIpRestoreTime,
}; };
id.value = res.id; id.value = res.id;
}); });
@ -198,6 +302,22 @@
} }
} }
function handleCaptchaClick() {
if (formState.value.checkErrorLoginCaptcha) {
formState.value.checkErrorLoginCaptcha = false;
} else {
formState.value.checkErrorLoginCaptcha = true;
}
}
function handleBlockIpClick() {
if (formState.value.checkErrorLoginBlockIp) {
formState.value.checkErrorLoginBlockIp = false;
} else {
formState.value.checkErrorLoginBlockIp = true;
}
}
function handleClick(val) { function handleClick(val) {
if (proDisabled.value) return; if (proDisabled.value) return;
if (formState.value.mulLogin?.includes(val)) { if (formState.value.mulLogin?.includes(val)) {

View File

@ -186,7 +186,7 @@
}); });
findMenuTree(buttonSelectData.value, treeData.value, btnCheckedKey, 'button'); findMenuTree(buttonSelectData.value, treeData.value, btnCheckedKey, 'button');
} }
btnKeys.value = btnCheckedKey; btnKeys.value = Array.from(new Set([...btnKeys.value, ...btnCheckedKey]));
let lostColKey = []; let lostColKey = [];
colKeys.value.forEach((o) => { colKeys.value.forEach((o) => {
@ -205,7 +205,7 @@
}); });
findMenuTree(columnSelectData.value, treeData.value, colCheckedKey, 'column'); findMenuTree(columnSelectData.value, treeData.value, colCheckedKey, 'column');
} }
colKeys.value = colCheckedKey; colKeys.value = Array.from(new Set([...colKeys.value, ...colCheckedKey]));
let lostFieldKey = []; let lostFieldKey = [];
fieldKeys.value.forEach((o) => { fieldKeys.value.forEach((o) => {
@ -224,7 +224,7 @@
}); });
findMenuTree(fieldSelectData.value, treeData.value, fieldCheckedKey, 'field'); findMenuTree(fieldSelectData.value, treeData.value, fieldCheckedKey, 'field');
} }
fieldKeys.value = fieldCheckedKey; fieldKeys.value = Array.from(new Set([...fieldKeys.value, ...fieldCheckedKey]));
nextTick(() => { nextTick(() => {
getTree(unref(ButtonRef))?.setCheckedKeys(authList.buttonIds); getTree(unref(ButtonRef))?.setCheckedKeys(authList.buttonIds);
@ -289,6 +289,11 @@
// colKeys.value = getTree(unref(ColumnRef)).getCheckedKeys(); // colKeys.value = getTree(unref(ColumnRef)).getCheckedKeys();
// fieldKeys.value = getTree(unref(FieldRef)).getCheckedKeys(); // fieldKeys.value = getTree(unref(FieldRef)).getCheckedKeys();
// 过滤无效的权限 ID
btnKeys.value = btnKeys.value.filter((o) => btnFilterKeys.value.includes(o));
colKeys.value = colKeys.value.filter((o) => colFilterKeys.value.includes(o));
fieldKeys.value = fieldKeys.value.filter((o) => fieldFilterKeys.value.includes(o));
// btnKeys.value = btnKeys.value.filter((o) => { // btnKeys.value = btnKeys.value.filter((o) => {
// return btnFilterKeys.value.includes(o); // return btnFilterKeys.value.includes(o);
// }); // });
@ -299,6 +304,7 @@
// return fieldFilterKeys.value.includes(o); // return fieldFilterKeys.value.includes(o);
// }); // });
// 提交更新后的权限数据
await RoleSetAuth({ await RoleSetAuth({
id: rowId.value, id: rowId.value,
type: typeKey.value == 4?NaN:typeKey.value, type: typeKey.value == 4?NaN:typeKey.value,
@ -307,6 +313,7 @@
columnIds: colKeys.value, columnIds: colKeys.value,
formIds: fieldKeys.value, formIds: fieldKeys.value,
}); });
notification.success({ notification.success({
message: t('提示'), message: t('提示'),
description: t('功能授权更新成功'), description: t('功能授权更新成功'),
@ -362,14 +369,23 @@
colFilterKeys.value = []; colFilterKeys.value = [];
fieldFilterKeys.value = []; fieldFilterKeys.value = [];
findMenuTree(buttonSelectData.value, treeData.value, addMenuSelect, 'button'); // 使用 Set 合并已选中和新增选中的菜单,避免重复
findMenuTree(columnSelectData.value, treeData.value, addMenuSelect, 'column'); const allMenuKeys = new Set([...menuKeys.value, ...addMenuSelect]);
findMenuTree(fieldSelectData.value, treeData.value, addMenuSelect, 'field');
let newBtnSelect = [...new Set([...btnKeys.value, ...addMenuSelect])]; // 确保未选中菜单的子节点不会丢失
getTree(unref(ButtonRef))?.setExpandedKeys(newBtnSelect); findMenuTree(buttonSelectData.value, treeData.value, Array.from(allMenuKeys), 'button');
getTree(unref(ColumnRef))?.setExpandedKeys([...new Set([...colKeys.value, ...addMenuSelect])]); findMenuTree(columnSelectData.value, treeData.value, Array.from(allMenuKeys), 'column');
getTree(unref(FieldRef))?.setExpandedKeys([...new Set([...fieldKeys.value, ...addMenuSelect])]); findMenuTree(fieldSelectData.value, treeData.value, Array.from(allMenuKeys), '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]));
// 扩展树的展开状态
getTree(unref(ButtonRef))?.setExpandedKeys(Array.from(allMenuKeys));
getTree(unref(ColumnRef))?.setExpandedKeys(Array.from(allMenuKeys));
getTree(unref(FieldRef))?.setExpandedKeys(Array.from(allMenuKeys));
} }
function getParentKeys(checkedKey, arr, keys) { function getParentKeys(checkedKey, arr, keys) {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {

View File

@ -51,6 +51,7 @@
<a-select v-model:value="record[column.dataIndex]"> <a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option> <a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option> <a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select> </a-select>
</template> </template>
<template v-if="column.key === 'operateConfig'"> <template v-if="column.key === 'operateConfig'">
@ -76,6 +77,11 @@
:options="liteFlowOptions" :options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }" :field-names="{ label: 'chainName', value: 'id' }"
/> />
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteStartEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteStartEvent(index)" />
@ -105,6 +111,7 @@
<a-select v-model:value="record[column.dataIndex]"> <a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option> <a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option> <a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select> </a-select>
</template> </template>
<template v-if="column.key === 'operateConfig'"> <template v-if="column.key === 'operateConfig'">
@ -130,6 +137,11 @@
:options="liteFlowOptions" :options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }" :field-names="{ label: 'chainName', value: 'id' }"
/> />
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteEndEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteEndEvent(index)" />
@ -137,6 +149,306 @@
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" :tab="t('会签后事件')" v-if="formInfo.setSignAfterEventConfigs || formInfo.type==BpmnNodeKey.USER">
<div class="process-top">
<a-button type="primary" @click="addSetSignAfterEvent"> {{ t('添加会签后事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.setSignAfterEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteSetSignAfterEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="4" :tab="t('预审前事件')" v-if="formInfo.prequalifyBeforeEventConfigs || formInfo.type==BpmnNodeKey.USER">
<div class="process-top">
<a-button type="primary" @click="addPrequalifyBeforeEvent"> {{ t('添加预审前事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.prequalifyBeforeEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deletePrequalifyBeforeEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="5" :tab="t('预审后事件')" v-if="formInfo.prequalifyAfterEventConfigs || formInfo.type==BpmnNodeKey.USER">
<div class="process-top">
<a-button type="primary" @click="addPrequalifyAfterEvent"> {{ t('添加预审后事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.prequalifyAfterEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deletePrequalifyAfterEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="6" :tab="t('同意后事件')" v-if="formInfo.agreeAfterEventConfigs || formInfo.type==BpmnNodeKey.USER">
<div class="process-top">
<a-button type="primary" @click="addAgreeAfterEvent"> {{ t('添加同意后事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.agreeAfterEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteAgreeAfterEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="7" :tab="t('退回后事件')" v-if="formInfo.rejectAfterEventConfigs || formInfo.type==BpmnNodeKey.USER">
<div class="process-top">
<a-button type="primary" @click="addRejectAfterEvent"> {{ t('添加退回后事件') }} </a-button>
</div>
<a-table :columns="columns" :data-source="formInfo.rejectAfterEventConfigs" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.END, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteRejectAfterEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs> </a-tabs>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@ -167,6 +479,9 @@
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { NodeEventExType } from '/@/enums/workflowEnum'; import { NodeEventExType } from '/@/enums/workflowEnum';
import { NodeEventConfig } from '/@/model/workflow/workflowConfig'; import { NodeEventConfig } from '/@/model/workflow/workflowConfig';
import {
BpmnNodeKey,
} from '/@/enums/workflowEnum';
const { t } = useI18n(); const { t } = useI18n();
const { showPanel, formInfo, nodeName } = useStateFormInfo(); const { showPanel, formInfo, nodeName } = useStateFormInfo();
const updateElementName = inject('updateElementName') as any; const updateElementName = inject('updateElementName') as any;
@ -222,7 +537,12 @@
() => { () => {
if ( if (
formInfo.value.endEventConfigs?.length > 0 || formInfo.value.endEventConfigs?.length > 0 ||
formInfo.value.startEventConfigs?.length > 0 formInfo.value.startEventConfigs?.length > 0 ||
formInfo.value.setSignAfterEventConfigs?.length > 0 ||
formInfo.value.prequalifyBeforeEventConfigs?.length > 0 ||
formInfo.value.prequalifyAfterEventConfigs?.length > 0 ||
formInfo.value.agreeAfterEventConfigs?.length > 0 ||
formInfo.value.rejectAfterEventConfigs?.length > 0
) { ) {
nextTick(() => { nextTick(() => {
const tbody: any = document.querySelector('.ant-table-tbody'); const tbody: any = document.querySelector('.ant-table-tbody');
@ -246,6 +566,66 @@
const getList = async () => { const getList = async () => {
liteFlowOptions.value = (await getLiteflowList()) || []; liteFlowOptions.value = (await getLiteflowList()) || [];
}; };
//新增会签 因为旧值中不包含所以html 判断node节点这里对add 可以新增空数组
const addSetSignAfterEvent = () => {
formInfo.value.setSignAfterEventConfigs = formInfo.value.setSignAfterEventConfigs==null?[]:formInfo.value.setSignAfterEventConfigs;
formInfo.value.setSignAfterEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const deleteSetSignAfterEvent = (index) => {
formInfo.value.setSignAfterEventConfigs.splice(index, 1);
};
// 预审前 因为旧值中不包含所以html 判断node节点这里对add 可以新增空数组
const addPrequalifyBeforeEvent = () => {
formInfo.value.prequalifyBeforeEventConfigs = formInfo.value.prequalifyBeforeEventConfigs==null?[]:formInfo.value.prequalifyBeforeEventConfigs;
formInfo.value.prequalifyBeforeEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const deletePrequalifyBeforeEvent = (index) => {
formInfo.value.prequalifyBeforeEventConfigs.splice(index, 1);
};
// 预审后 因为旧值中不包含所以html 判断node节点这里对add 可以新增空数组
const addPrequalifyAfterEvent = () => {
formInfo.value.prequalifyAfterEventConfigs = formInfo.value.prequalifyAfterEventConfigs==null?[]:formInfo.value.prequalifyAfterEventConfigs;
formInfo.value.prequalifyAfterEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const deletePrequalifyAfterEvent = (index) => {
formInfo.value.prequalifyAfterEventConfigs.splice(index, 1);
};
// 同意 因为旧值中不包含所以html 判断node节点这里对add 可以新增空数组
const addAgreeAfterEvent = () => {
formInfo.value.agreeAfterEventConfigs = formInfo.value.agreeAfterEventConfigs==null?[]:formInfo.value.agreeAfterEventConfigs;
formInfo.value.agreeAfterEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const deleteAgreeAfterEvent = (index) => {
formInfo.value.agreeAfterEventConfigs.splice(index, 1);
};
// 退回 因为旧值中不包含所以html 判断node节点这里对add 可以新增空数组
const addRejectAfterEvent = () => {
formInfo.value.rejectAfterEventConfigs = formInfo.value.rejectAfterEventConfigs==null?[]:formInfo.value.rejectAfterEventConfigs;
formInfo.value.rejectAfterEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
};
const deleteRejectAfterEvent = (index) => {
formInfo.value.rejectAfterEventConfigs.splice(index, 1);
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -89,11 +89,12 @@ export const processConfig: ProcessConfig = {
globalEndEventConfigs: [],//全局 用户节点 结束事件 globalEndEventConfigs: [],//全局 用户节点 结束事件
globalPrequalifyBeforeEventConfigs: [],//预审前 globalPrequalifyBeforeEventConfigs: [],//预审前
globalPrequalifyAfterEventConfigs: [],//预审后 globalPrequalifyAfterEventConfigs: [],//预审后
globalFinishEventConfigs: [],//终止事件 globalFinishBeforeEventConfigs: [],//终止事件
globalRejectEventConfigs: [],//全局退回事件 globalRejectAfterEventConfigs: [],//全局退回事件
globalAgreeEventConfigs: [],//全局同意事件 globalAgreeAfterEventConfigs: [],//全局同意事件
globalSuspendedEventConfigs: [],//全局 挂起/暂停事件 globalSuspendedBeforeEventConfigs: [],//全局 挂起/暂停事件
globalRestoreEventConfigs: [],//全局 恢复事件 globalRestoreAfterEventConfigs: [],//全局 恢复事件
globalSetSignAfterEventConfigs: [],//全局 会签事件
xmlContent: '', xmlContent: '',
}; };
// 默认属性 // 默认属性
@ -204,6 +205,11 @@ const UserProperties: UserTaskConfig = {
}, //超时处理 }, //超时处理
startEventConfigs: [], startEventConfigs: [],
endEventConfigs: [], endEventConfigs: [],
prequalifyBeforeEventConfigs: [],//预审前
prequalifyAfterEventConfigs: [],//预审后
rejectAfterEventConfigs: [],//退回事件
agreeAfterEventConfigs: [],//同意事件
setSignAfterEventConfigs: [],//会签事件
}; };
// 脚本节点默认属性 // 脚本节点默认属性
const ScriptProperties: ScriptTaskConfig = { const ScriptProperties: ScriptTaskConfig = {

View File

@ -132,7 +132,7 @@
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" :tab="t('全局预审前事件')"> <a-tab-pane key="3" :tab="t('预审前事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalPrequalifyBeforeEvent"> <a-button type="primary" @click="addGlobalPrequalifyBeforeEvent">
{{ t('添加全局预审前事件') }} {{ t('添加全局预审前事件') }}
@ -198,7 +198,7 @@
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" :tab="t('全局预审后事件')"> <a-tab-pane key="4" :tab="t('预审后事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalPrequalifyAfterEvent"> <a-button type="primary" @click="addGlobalPrequalifyAfterEvent">
{{ t('添加全局预审后事件') }} {{ t('添加全局预审后事件') }}
@ -264,15 +264,15 @@
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" :tab="t('终止事件')"> <a-tab-pane key="5" :tab="t('终止事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalFinishEvent"> <a-button type="primary" @click="addGlobalFinishBeforeEvent">
{{ t('添加终止事件') }} {{ t('添加终止事件') }}
</a-button> </a-button>
</div> </div>
<a-table <a-table
:columns="columns" :columns="columns"
:dataSource="processInfo.globalFinishEventConfigs" :dataSource="processInfo.globalFinishBeforeEventConfigs"
:pagination="false" :pagination="false"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
@ -325,20 +325,20 @@
/> />
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteFinishEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteFinishBeforeEvent(index)" />
</template> </template>
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="6" :tab="t('全局退回事件')"> <a-tab-pane key="6" :tab="t('退回事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalRejectEvent"> <a-button type="primary" @click="addGlobalRejectAfterEvent">
{{ t('添加全局退回事件') }} {{ t('添加全局退回事件') }}
</a-button> </a-button>
</div> </div>
<a-table <a-table
:columns="columns" :columns="columns"
:dataSource="processInfo.globalRejectEventConfigs" :dataSource="processInfo.globalRejectAfterEventConfigs"
:pagination="false" :pagination="false"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
@ -391,20 +391,20 @@
/> />
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteRejectEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteRejectAfterEvent(index)" />
</template> </template>
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="7" :tab="t('全局同意事件')"> <a-tab-pane key="7" :tab="t('同意事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalAgreeEvent"> <a-button type="primary" @click="addGlobalAgreeAfterEvent">
{{ t('添加全局同意事件') }} {{ t('添加全局同意事件') }}
</a-button> </a-button>
</div> </div>
<a-table <a-table
:columns="columns" :columns="columns"
:dataSource="processInfo.globalAgreeEventConfigs" :dataSource="processInfo.globalAgreeAfterEventConfigs"
:pagination="false" :pagination="false"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
@ -457,20 +457,20 @@
/> />
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteAgreeEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteAgreeAfterEvent(index)" />
</template> </template>
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="8" :tab="t('挂起事件')"> <a-tab-pane key="8" :tab="t('挂起事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalSuspendedEvent"> <a-button type="primary" @click="addGlobalSuspendedBeforeEvent">
{{ t('添加挂起事件') }} {{ t('添加挂起事件') }}
</a-button> </a-button>
</div> </div>
<a-table <a-table
:columns="columns" :columns="columns"
:dataSource="processInfo.globalSuspendedEventConfigs" :dataSource="processInfo.globalSuspendedBeforeEventConfigs"
:pagination="false" :pagination="false"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
@ -523,20 +523,20 @@
/> />
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteSuspendedEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteSuspendedBeforeEvent(index)" />
</template> </template>
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="9" :tab="t('恢复事件')"> <a-tab-pane key="9" :tab="t('恢复事件')">
<div class="process-top"> <div class="process-top">
<a-button type="primary" @click="addGlobalRestoreEvent"> <a-button type="primary" @click="addGlobalRestoreAfterEvent">
{{ t('添加恢复事件') }} {{ t('添加恢复事件') }}
</a-button> </a-button>
</div> </div>
<a-table <a-table
:columns="columns" :columns="columns"
:dataSource="processInfo.globalRestoreEventConfigs" :dataSource="processInfo.globalRestoreAfterEventConfigs"
:pagination="false" :pagination="false"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
@ -589,7 +589,73 @@
/> />
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteRestoreEvent(index)" /> <DeleteTwoTone two-tone-color="#ff8080" @click="deleteRestoreAfterEvent(index)" />
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="10" :tab="t('会签后事件')">
<div class="process-top">
<a-button type="primary" @click="addGlobalSetSignAfterEvent">
{{ t('添加会签后事件') }}
</a-button>
</div>
<a-table
:columns="columns"
:dataSource="processInfo.globalSetSignAfterEventConfigs"
:pagination="false"
>
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'type'">
<a-select v-model:value="record[column.dataIndex]">
<a-select-option :value="0">{{ t('执行API') }}</a-select-option>
<a-select-option :value="1">{{ t('规则引擎') }}</a-select-option>
<a-select-option :value="2">{{ t('类注入') }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<!-- <a-input
v-if="record.type === NodeEventExType.API"
v-model:value="record['apiConfig'].path"
@click="showConfig(NodeEventType.START, index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input> -->
<ScriptApiSelect
v-if="record.type === NodeEventExType.API"
style="width: 100%"
v-model="record['apiConfig']"
:need-hide-components="true"
/>
<a-select
style="width: 100%"
v-else-if="record.type === NodeEventExType.LITEFLOW"
v-model:value="record['liteflowId']"
:options="liteFlowOptions"
:field-names="{ label: 'chainName', value: 'id' }"
/>
<a-input
style="width: 100%"
v-else-if="record.type === NodeEventExType.SERVICE"
v-model:value="record['serviceName']"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteSetSignAfterEvent(index)" />
</template> </template>
</template> </template>
</a-table> </a-table>
@ -683,61 +749,71 @@ const deletePrequalifyAfterEvent = (index) => {
processInfo.value.globalPrequalifyAfterEventConfigs.splice(index, 1); processInfo.value.globalPrequalifyAfterEventConfigs.splice(index, 1);
}; };
const addGlobalFinishEvent = () => { const addGlobalFinishBeforeEvent = () => {
processInfo.value.globalFinishEventConfigs.push({ processInfo.value.globalFinishBeforeEventConfigs.push({
type: NodeEventExType.API, type: NodeEventExType.API,
apiConfig: {}, apiConfig: {},
} as NodeEventConfig); } as NodeEventConfig);
}; };
const deleteFinishEvent = (index) => { const deleteFinishBeforeEvent = (index) => {
processInfo.value.globalFinishEventConfigs.splice(index, 1); processInfo.value.globalFinishBeforeEventConfigs.splice(index, 1);
}; };
const addGlobalRejectEvent = () => { const addGlobalRejectAfterEvent = () => {
processInfo.value.globalRejectEventConfigs.push({ processInfo.value.globalRejectAfterEventConfigs.push({
type: NodeEventExType.API, type: NodeEventExType.API,
apiConfig: {}, apiConfig: {},
} as NodeEventConfig); } as NodeEventConfig);
}; };
const deleteRejectEvent = (index) => { const deleteRejectAfterEvent = (index) => {
processInfo.value.globalRejectEventConfigs.splice(index, 1); processInfo.value.globalRejectAfterEventConfigs.splice(index, 1);
}; };
const addGlobalAgreeEvent = () => { const addGlobalAgreeAfterEvent = () => {
processInfo.value.globalAgreeEventConfigs.push({ processInfo.value.globalAgreeAfterEventConfigs.push({
type: NodeEventExType.API, type: NodeEventExType.API,
apiConfig: {}, apiConfig: {},
} as NodeEventConfig); } as NodeEventConfig);
}; };
const deleteAgreeEvent = (index) => { const deleteAgreeAfterEvent = (index) => {
processInfo.value.globalAgreeEventConfigs.splice(index, 1); processInfo.value.globalAgreeAfterEventConfigs.splice(index, 1);
}; };
const addGlobalSuspendedEvent = () => { const addGlobalSuspendedBeforeEvent = () => {
processInfo.value.globalSuspendedEventConfigs.push({ processInfo.value.globalSuspendedBeforeEventConfigs.push({
type: NodeEventExType.API, type: NodeEventExType.API,
apiConfig: {}, apiConfig: {},
} as NodeEventConfig); } as NodeEventConfig);
}; };
const deleteSuspendedEvent = (index) => { const deleteSuspendedBeforeEvent = (index) => {
processInfo.value.globalSuspendedEventConfigs.splice(index, 1); processInfo.value.globalSuspendedBeforeEventConfigs.splice(index, 1);
}; };
const addGlobalRestoreEvent = () => { const addGlobalRestoreAfterEvent = () => {
processInfo.value.globalRestoreEventConfigs.push({ processInfo.value.globalRestoreAfterEventConfigs.push({
type: NodeEventExType.API, type: NodeEventExType.API,
apiConfig: {}, apiConfig: {},
} as NodeEventConfig); } as NodeEventConfig);
}; };
const deleteRestoreEvent = (index) => { const deleteRestoreAfterEvent = (index) => {
processInfo.value.globalRestoreEventConfigs.splice(index, 1); processInfo.value.globalRestoreAfterEventConfigs.splice(index, 1);
};
// 全局会签事件添加
const addGlobalSetSignAfterEvent = () => {
processInfo.value.globalSetSignAfterEventConfigs.push({
type: NodeEventExType.API,
apiConfig: {},
} as NodeEventConfig);
}; };
const deleteSetSignAfterEvent = (index) => {
processInfo.value.globalSetSignAfterEventConfigs.splice(index, 1);
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -57,7 +57,11 @@
</BasicTable> </BasicTable>
<!-- 查看 --> <!-- 查看 -->
<LookProcess ref="lookProcess" :taskId="taskId" :processId="processId" @close="reload"/> <LookProcess ref="lookProcess"
:taskId="taskId"
:processId="processId"
:schemaId="schemaId"
@close="reload"/>
<!-- 指派审核人 --> <!-- 指派审核人 -->
<ApproveProcessMonitorUser <ApproveProcessMonitorUser
v-if="data.approvedUserVisible" v-if="data.approvedUserVisible"
@ -407,6 +411,7 @@
async function dbClickRow(record) { async function dbClickRow(record) {
processId.value = record.processId; processId.value = record.processId;
taskId.value = record.taskId;//改版 一个流程中可能有多个taskId 已不返回 taskId.value = record.taskId;//改版 一个流程中可能有多个taskId 已不返回
schemaId.value = record.schemaId;
await nextTick() await nextTick()
await lookProcess.value.look(); await lookProcess.value.look();
} }

View File

@ -13,6 +13,7 @@
:taskRecords="data.taskRecords" :taskRecords="data.taskRecords"
:predecessorTasks="selectedPredecessorTasks" :predecessorTasks="selectedPredecessorTasks"
:processId="props.processId" :processId="props.processId"
:schemaId="props.schemaId"
position="top" position="top"
> >
<FormInformation <FormInformation

View File

@ -8,7 +8,7 @@
<a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button> <a-button type="primary" class="clean-icon" @click.stop="close">{{ t('关闭') }}</a-button>
</template> </template>
<template #full> <template #full>
<LookTask v-if="visible" :taskId="props.taskId" :processId="props.processId" /> <LookTask v-if="visible" :taskId="props.taskId" :processId="props.processId" :schemaId="props.schemaId"/>
</template> </template>
</ProcessLayout> </ProcessLayout>
</span> </span>
@ -22,20 +22,20 @@
import { onActivated, onMounted, ref } from 'vue'; import { onActivated, onMounted, ref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = withDefaults(
processId: { defineProps<{
type: String, processId: string;
default: '' taskId: string;
schemaId: string;
visible?: boolean;
}>(),
{
processId: '',
taskId: '',
schemaId: '',
visible: false,
}, },
taskId: { );
type: String,
default: ''
},
visible: {
type: Boolean,
default: false
},
})
let emits = defineEmits(['close']); let emits = defineEmits(['close']);
let visible = ref(false); let visible = ref(false);
let showLoading = ref(false); let showLoading = ref(false);

View File

@ -10,6 +10,7 @@
:taskRecords="data.taskRecords" :taskRecords="data.taskRecords"
:predecessorTasks="selectedPredecessorTasks" :predecessorTasks="selectedPredecessorTasks"
:processId="props.processId" :processId="props.processId"
:schemaId="props.schemaId"
position="top" position="top"
> >
<FormInformation <FormInformation

View File

@ -19,8 +19,9 @@
:class="activeIndex == index ? 'form-name actived' : 'form-name'" :class="activeIndex == index ? 'form-name actived' : 'form-name'"
> >
<span :class="item.validate ? 'dot' : 'dot validate'"></span> <span :class="item.validate ? 'dot' : 'dot validate'"></span>
<div class="icon-box"> <IconFontSymbol icon="formItem" /> </div <div class="icon-box">
><span @click="changeActiveIndex(index)" v-show="showPanel">{{ item.formName }}</span> <IconFontSymbol icon="formItem" />
</div><span @click="changeActiveIndex(index)" v-show="showPanel">{{ item.formName }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -38,25 +39,11 @@
<div id="approveExtendButton"></div> <div id="approveExtendButton"></div>
<div id="approveRightButton"></div> <div id="approveRightButton"></div>
</div> </div>
<div class="top-toolbar"> <SystemForm class="form-box" v-if="item.formType == FormType.SYSTEM"
<SystemForm :systemComponent="item.systemComponent" :isViewProcess="props.disabled" :formModel="item.formModel"
class="form-box" :workflowConfig="item" :ref="setItemRef" />
v-if="item.formType == FormType.SYSTEM" <SimpleForm v-else-if="item.formType == FormType.CUSTOM" class="form-box testClass" :ref="setItemRef"
:systemComponent="item.systemComponent" :formProps="item.formProps" :formModel="item.formModel" :isWorkFlow="true" />
:isViewProcess="props.disabled"
:formModel="item.formModel"
:workflowConfig="item"
:ref="setItemRef"
/>
<SimpleForm
v-else-if="item.formType == FormType.CUSTOM"
class="form-box"
:ref="setItemRef"
:formProps="item.formProps"
:formModel="item.formModel"
:isWorkFlow="true"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,7 +7,12 @@
:predecessorTasks="[]" :predecessorTasks="[]"
:processId="props.processId" :processId="props.processId"
position="top" position="top"
> :currentTaskInfo="data.currentTaskInfo"
:currentTaskAssigneeNames="data.currentTaskAssigneeNames"
:currentTaskAssignees="data.currentTaskAssignees"
:schemaId="props.schemaId"
:formDataId="data.formInfos[0].formData.id"
:formInfos="data.formInfos">
<FormInformation <FormInformation
:opinionsComponents="data.opinionsComponents" :opinionsComponents="data.opinionsComponents"
:opinions="data.opinions" :opinions="data.opinions"
@ -24,7 +29,7 @@
import { getApprovalProcess } from '/@/api/workflow/task'; import { getApprovalProcess } from '/@/api/workflow/task';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import userTaskItem from './../../hooks/userTaskItem'; import userTaskItem from './../../hooks/userTaskItem';
let props = defineProps(['position', 'processId', 'taskId']); let props = defineProps(['position', 'processId', 'taskId', 'schemaId']);
let visible = ref(false); let visible = ref(false);
const { data, initProcessData } = userTaskItem(); const { data, initProcessData } = userTaskItem();
onMounted(async () => { onMounted(async () => {
@ -32,6 +37,7 @@
let res = await getApprovalProcess(props.taskId, props.processId); let res = await getApprovalProcess(props.taskId, props.processId);
initProcessData(res); initProcessData(res);
visible.value = true; visible.value = true;
console.error('555555', data)
} catch (error) {} } catch (error) {}
}); });
</script> </script>