---初始化后台管理web页面项目
This commit is contained in:
382
src/views/secondDev/FormInformation.vue
Normal file
382
src/views/secondDev/FormInformation.vue
Normal file
@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<!-- 表单信息 -->
|
||||
<div class="form-container">
|
||||
<div class="box">
|
||||
<div :style="{}" class="form-right">
|
||||
<div v-for="(item, index) in forms.configs" :key="index" :tab="item.formName">
|
||||
<div v-show="activeIndex == index">
|
||||
<!-- 如果是生成文件模式 formType = 0,也就是FormType.SYSTEM,会加载对应项目的Form.vue-->
|
||||
<SystemForm v-if="item.formType == FormType.SYSTEM" :ref="setItemRef" :formModel="item.formModel" :isViewProcess="props.disabled" :systemComponent="item.systemComponent" :workflowConfig="item" class="form-box" />
|
||||
<SimpleForm v-else-if="item.formType == FormType.CUSTOM" :ref="setItemRef" :formModel="item.formModel" :formProps="item.formProps" :isWorkFlow="true" class="form-box" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
|
||||
import { onBeforeUpdate, nextTick, onMounted, reactive, ref } from 'vue';
|
||||
import { TaskApproveOpinion, ValidateForms } from '/@/model/workflow/bpmnConfig';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
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';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled: boolean | undefined;
|
||||
formInfos: Array<any>;
|
||||
opinions?: Array<TaskApproveOpinion> | undefined;
|
||||
opinionsComponents?: Array<string> | undefined;
|
||||
formAssignmentData?: null | Recordable;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
formInfos: () => {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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[];
|
||||
} = reactive({
|
||||
formModels: [],
|
||||
configs: [],
|
||||
formEventConfigs: []
|
||||
});
|
||||
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);
|
||||
// 系统表单
|
||||
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 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) {
|
||||
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);
|
||||
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) => {
|
||||
//此组件 获取数据 就是为了提交表单 所以 表单提交数据 事件 就此处执行
|
||||
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;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateForm,
|
||||
getFormModels,
|
||||
saveDraftData,
|
||||
setFormData,
|
||||
getUploadComponentIds,
|
||||
getSystemType
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-container {
|
||||
display: flex;
|
||||
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-right {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
318
src/views/secondDev/Login.vue
Normal file
318
src/views/secondDev/Login.vue
Normal file
@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<div :class="prefixCls" class="login-box relative w-full h-full" v-if="!isSingleLogin">
|
||||
<div class="center-box">
|
||||
<div class="login-left-title">
|
||||
<div class="sub-title">{{ sysName }}</div>
|
||||
</div>
|
||||
<div :class="`${prefixCls}-form`">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 100%; height: 300px; display: flex;justify-content: center;align-items: center;" v-else>
|
||||
<a-spin />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import LoginForm from './LoginForm.vue';
|
||||
import { getAppEnvConfig } from '/@/utils/env';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { getLogoInfo } from '/@/api/system/login';
|
||||
import { onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { LogoConfig } from '/#/config';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const isSingleLogin = ref(false)
|
||||
|
||||
const { notification } = useMessage();
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(async () => {
|
||||
const fullPath = currentRoute.value.fullPath;
|
||||
if (currentRoute.value.query?.ltpasToken) {
|
||||
isSingleLogin.value = true
|
||||
let targetURL = ''
|
||||
let redirect = ''
|
||||
if (fullPath.includes('targetURL')) {
|
||||
const list = fullPath.split('targetURL=');
|
||||
targetURL = list[1];
|
||||
} else {
|
||||
const list = fullPath.split('redirect=');
|
||||
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 }; //不要默认的错误提示
|
||||
await userStore.singleLogin(params);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
defineProps({
|
||||
sessionTimeout: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
const { prefixCls } = useDesign('login');
|
||||
const sysName = ref(import.meta.env.VITE_SYSTEM_NAME);
|
||||
|
||||
let logoConfig = ref<LogoConfig>({});
|
||||
let show = ref(false);
|
||||
getLogoInfo().then((res) => {
|
||||
show.value = true;
|
||||
logoConfig.value = {
|
||||
companyName: res.companyName,
|
||||
shortName: res.shortName,
|
||||
refreshLogoUrl: res.refreshLogoUrl,
|
||||
backgroundLogoUrl: res.backgroundLogoUrl,
|
||||
designerLogoUrl: res.designerLogoUrl,
|
||||
loginLogoUrl: res.loginLogoUrl,
|
||||
menuLogoUrl: res.menuLogoUrl
|
||||
};
|
||||
appStore.setLogoConfig(logoConfig.value);
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-login';
|
||||
@logo-prefix-cls: ~'@{namespace}-app-logo';
|
||||
@countdown-prefix-cls: ~'@{namespace}-countdown-input';
|
||||
@dark-bg: linear-gradient(to bottom, #364876, #112049);
|
||||
|
||||
.vben-login-form {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
display: flex;
|
||||
background: linear-gradient(180deg, #00356d 0%, rgb(0 53 109 / 0%) 100%), linear-gradient(180deg, #0074d3 2%, #011853 100%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo-box {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.logo-box img {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.center-box {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.login-left-title {
|
||||
position: relative;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.login-left-title .title {
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.login-left-title .sub-title {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #5e95ff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-box .right-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.fixed-tool {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 40px;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.right-top-box {
|
||||
width: 400px;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.login-box {
|
||||
background: @dark-bg;
|
||||
}
|
||||
|
||||
.login-left-title {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.login-left-title .title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
background-color: @dark-bg;
|
||||
|
||||
// &::before {
|
||||
// background-image: url(/@/assets/svg/login-bg-dark.svg);
|
||||
// }
|
||||
|
||||
.ant-input,
|
||||
.ant-input-password {
|
||||
background-color: #232a3b;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
border-color: #525e7c;
|
||||
background-color: #232a3b;
|
||||
}
|
||||
|
||||
.ant-checkbox-inner {
|
||||
border-color: #525e7c;
|
||||
}
|
||||
|
||||
.ant-btn:not(.ant-btn-link, .ant-btn-primary) {
|
||||
border: 1px solid #4a5569;
|
||||
}
|
||||
|
||||
&-form {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.app-iconify {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
input.fix-auto-fill,
|
||||
.fix-auto-fill input {
|
||||
-webkit-text-fill-color: #c9d1d9 !important;
|
||||
box-shadow: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
min-height: 100%;
|
||||
overflow: hidden;
|
||||
@media (max-width: @screen-xl) {
|
||||
background-color: @dark-bg;
|
||||
|
||||
.@{prefix-cls}-form {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.@{logo-prefix-cls} {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
height: 30px;
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
.@{logo-prefix-cls} {
|
||||
display: flex;
|
||||
width: 60%;
|
||||
height: 80px;
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-sign-in-way {
|
||||
.anticon {
|
||||
font-size: 22px;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input:not([type='checkbox']) {
|
||||
min-width: 320px;
|
||||
|
||||
@media (max-width: @screen-xl) {
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-lg) {
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-md) {
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-sm) {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.@{countdown-prefix-cls} input {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.ant-divider-inner-text {
|
||||
font-size: 12px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
:deep(.center-box) {
|
||||
width: 540px;
|
||||
}
|
||||
</style>
|
||||
496
src/views/secondDev/LoginForm.vue
Normal file
496
src/views/secondDev/LoginForm.vue
Normal file
@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<Form v-show="getShow" ref="formRef" :model="formData" :rules="getFormRules" class="p-4 enter-x form-box" @keypress.enter="handleLogin">
|
||||
<div>
|
||||
<FormItem class="enter-x" name="account" v-if="loginType =='pw'">
|
||||
<label class="form-title"> {{ t('账号') }}</label>
|
||||
<Input v-model:value="formData.account" :placeholder="t('账号')" class="fix-auto-fill" size="large" style="height: 58px;" @blur="handleBlur">
|
||||
<template #prefix>
|
||||
<IconFontSymbol class="user-icon" icon="yonghu-xianxing" />
|
||||
</template>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<FormItem class="enter-x" name="password" v-if="loginType =='pw'">
|
||||
<label class="form-title"> {{ t('密码') }}</label>
|
||||
<InputPassword v-model:value="formData.password" :placeholder="t('密码')" size="large" style="height: 58px;" visibilityToggle>
|
||||
<template #prefix>
|
||||
<IconFontSymbol class="user-icon" icon="mima" />
|
||||
</template>
|
||||
</InputPassword>
|
||||
</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 pic-code" size="large" style="height: 58px; flex: 1; min-width: auto;" 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_GLOB_TENANT_ENABLED && getAppEnvConfig().VITE_GLOB_TENANT_INPUT_REQUIRED!=='false' && loginType =='pw'" name="tenantCode" class="enter-x">
|
||||
<label class="form-title"> {{ t('租户码') }}</label>
|
||||
<Input
|
||||
size="large"
|
||||
visibilityToggle
|
||||
v-model:value="formData.tenantCode"
|
||||
:placeholder="t('租户码')"
|
||||
style="height: 58px; width: 450px;"
|
||||
>
|
||||
<template #prefix>
|
||||
<IconFontSymbol icon="zuzhiguanli" class="user-icon" />
|
||||
</template>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<FormItem class="enter-x" name="mobile" v-if="loginType =='mobile'">
|
||||
<label class="form-title"> {{ t('手机号') }}</label>
|
||||
<Input v-model:value="formData.mobile" :placeholder="t('手机号')" class="fix-auto-fill" size="large" style="height: 58px;">
|
||||
<template #prefix>
|
||||
<IconFontSymbol class="user-icon" icon="yonghu-xianxing" />
|
||||
</template>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<FormItem class="enter-x" name="code" v-if="loginType =='mobile'">
|
||||
<label class="form-title"> {{ t('验证码') }}</label>
|
||||
<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>
|
||||
<Button type="link" class="f-16" @click="getLoginCode" size="small" :disabled="codeButtonDisabled">
|
||||
{{ getCodeButtonName }}
|
||||
</Button>
|
||||
</template>
|
||||
</Input>
|
||||
</FormItem>
|
||||
<ARow class="enter-x">
|
||||
<ACol :span="12">
|
||||
<FormItem v-if="loginType =='pw'">
|
||||
<!-- No logic, you need to deal with it yourself -->
|
||||
<Checkbox v-model:checked="rememberMe" class="f-16" size="small">
|
||||
{{ t('记住我') }}
|
||||
</Checkbox>
|
||||
</FormItem>
|
||||
</ACol>
|
||||
<ACol :span="12" style="text-align: right;">
|
||||
<FormItem>
|
||||
<!-- No logic, you need to deal with it yourself -->
|
||||
<Button type="link" class="f-16" @click="changeLoginType" size="small">
|
||||
{{ loginType == 'mobile' ? t('账号密码登录') : t('手机登录') }}
|
||||
</Button>
|
||||
</FormItem>
|
||||
</ACol>
|
||||
<!-- <ACol :span="12">
|
||||
<FormItem :style="{ 'text-align': 'right' }">
|
||||
No logic, you need to deal with it yourself
|
||||
<Button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">
|
||||
{{ t('忘记密码?') }}
|
||||
</Button>
|
||||
</FormItem>
|
||||
</ACol> -->
|
||||
</ARow>
|
||||
|
||||
<FormItem class="enter-x">
|
||||
<Button :loading="loading" :style="{ 'border-radius': '8px' }" block class="sub-button" type="primary" @click="handleLogin">
|
||||
{{ t('登录') }}
|
||||
</Button>
|
||||
</FormItem>
|
||||
|
||||
<div class="tip-msg">本平台为非涉密平台,严禁处理、传输国家秘密、工作秘密、商业秘密、敏感信息</div>
|
||||
</div>
|
||||
</Form>
|
||||
<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">
|
||||
刷新
|
||||
<Icon :spin="refreshLoading" icon="ant-design:redo-outlined" class="redo-outlined" />
|
||||
</div>
|
||||
<a-image
|
||||
:width="200"
|
||||
:src="imgObj.imgBase64 || ''"
|
||||
/>
|
||||
<Input v-model:value="imgCode" :placeholder="t('验证码')" size="large" />
|
||||
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, unref, computed, onMounted } from 'vue';
|
||||
import { Checkbox, Form, Input, Row, Col, Button } from 'ant-design-vue';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from '/@/views/sys/login/useLogin';
|
||||
import { getMobileLoginCode, getMobileLoginImg, sendMobileLoginCode, checkAccountCaptchaApi, } from '/@/api/system/login';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getAppEnvConfig } from '/@/utils/env';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
|
||||
import { PhoneOutlined, PictureOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const ACol = Col;
|
||||
const ARow = Row;
|
||||
const FormItem = Form.Item;
|
||||
const InputPassword = Input.Password;
|
||||
const { t } = useI18n();
|
||||
const { notification, createErrorModal } = useMessage();
|
||||
const { prefixCls } = useDesign('login');
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
const { getLoginState, setLoginState } = useLoginState();
|
||||
const { getFormRules } = useFormRules();
|
||||
|
||||
const formRef = ref();
|
||||
// const iframeRef = ref();
|
||||
const loading = ref(false);
|
||||
const rememberMe = ref(false);
|
||||
const loginType = ref('pw')
|
||||
const loginUseType = ref();
|
||||
const countdown = ref(60)
|
||||
|
||||
const visible = ref(false);
|
||||
const refreshLoading = ref(false);
|
||||
const imgObj = ref({
|
||||
imgBase64: ''
|
||||
})
|
||||
const captchaImage = ref({
|
||||
imgBase64: ''
|
||||
})
|
||||
const imgCode = ref('')
|
||||
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
password: '',
|
||||
mobile: '',
|
||||
code: '',
|
||||
tenantCode: 'system',
|
||||
captchaCode: ''
|
||||
});
|
||||
const getCodeButtonName = ref('获取验证码')
|
||||
const codeButtonDisabled = ref(false)
|
||||
let setCodeInterval = null
|
||||
|
||||
onMounted(async () => {
|
||||
//如果是第三方登录跳转回来 会携带token
|
||||
if (currentRoute.value.query.token) {
|
||||
oauthLogin(currentRoute.value.query.token)
|
||||
}
|
||||
//如果第三方登录 登录错误 会携带错误信息
|
||||
if (currentRoute.value.query.error) {
|
||||
createErrorModal({
|
||||
title: t('错误提示'),
|
||||
content: t(currentRoute.value.query.error as string),
|
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
|
||||
});
|
||||
}
|
||||
const loginInfo = window.localStorage.getItem('USER__LOGIN__INFO__');
|
||||
if (loginInfo) {
|
||||
formData.account = Base64.decode(JSON.parse(loginInfo).account);
|
||||
formData.password = Base64.decode(JSON.parse(loginInfo).password);
|
||||
formData.tenantCode = Base64.decode(JSON.parse(loginInfo).tenantCode);
|
||||
}
|
||||
});
|
||||
const oauthLogin = async (token) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const userInfo = await userStore.oauthLogin({
|
||||
token: token as string,
|
||||
mode: 'none' //不要默认的错误提示
|
||||
});
|
||||
if (userInfo) {
|
||||
notification.success({
|
||||
message: t('登录成功'),
|
||||
description: `${t('欢迎回来')}: ${userInfo.name}`,
|
||||
duration: 3
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
createErrorModal({
|
||||
title: t('错误提示'),
|
||||
content: (error as unknown as Error).message || t('网络异常,请检查您的网络连接是否正常!'),
|
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
const changeLoginType = () => {
|
||||
loginType.value = loginType.value == 'pw' ? 'mobile' : 'pw'
|
||||
}
|
||||
const getLoginCode = async () => {
|
||||
countdown.value = 60
|
||||
if(isValidPhoneNumber(formData.mobile)) {
|
||||
visible.value = true
|
||||
imgCode.value = ''
|
||||
onMobileLoginImg()
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('手机号有误'),
|
||||
description: `${t('手机号有误,请重新填写')}`,
|
||||
duration: 3
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号失焦 请求后台获取业务状态
|
||||
*/
|
||||
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() {
|
||||
refreshLoading.value = true
|
||||
onMobileLoginImg()
|
||||
refreshLoading.value = false
|
||||
}
|
||||
|
||||
// 图形验证
|
||||
async function onMobileLoginImg() {
|
||||
imgObj.value = await getMobileLoginImg({account: formData.mobile})
|
||||
}
|
||||
|
||||
// 图形账号验证
|
||||
async function refreshCaptcha() {
|
||||
captchaImage.value = await getMobileLoginImg({account: formData.account})
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
await getMobileLoginCode({captchaCode: imgCode.value ,mobile: formData.mobile})
|
||||
setCodeInterval = setInterval(updateCountdown, 1000);
|
||||
onVisible()
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
onVisible()
|
||||
}
|
||||
|
||||
function onVisible () {
|
||||
visible.value = false
|
||||
srcImg.value = ''
|
||||
}
|
||||
|
||||
// 更新倒计时显示
|
||||
function updateCountdown() {
|
||||
if (countdown.value === 0) {
|
||||
getCodeButtonName.value = '获取验证码';
|
||||
codeButtonDisabled.value = false;
|
||||
clearInterval(setCodeInterval)
|
||||
} else {
|
||||
countdown.value--;
|
||||
getCodeButtonName.value = countdown.value + ' 秒后可重发';
|
||||
codeButtonDisabled.value = true;
|
||||
}
|
||||
}
|
||||
const isValidPhoneNumber = (phoneNumber) => {
|
||||
// 中国大陆手机号码正则表达式
|
||||
const reg = /^1[3|4|5|7|8]\d{9}$/
|
||||
return reg.test(phoneNumber);
|
||||
}
|
||||
|
||||
const { validForm } = useFormValid(formRef);
|
||||
|
||||
//onKeyStroke('Enter', handleLogin);
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN);
|
||||
|
||||
async function handleLogin() {
|
||||
const data = await validForm();
|
||||
if (!data) return;
|
||||
if (loginType.value == 'pw') {
|
||||
try {
|
||||
// 校验有没带图形验证码提交
|
||||
if (loginUseType.value == 'captcha' && !data.captchaCode) {
|
||||
createErrorModal({
|
||||
title: t('错误提示'),
|
||||
content: '请输入图形验证码',
|
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const userInfo = await userStore.login({
|
||||
password: data.password,
|
||||
userName: data.account,
|
||||
tenantCode: data.tenantCode,
|
||||
captchaCode: data.captchaCode,
|
||||
deviceType: 0, //pc-0,app-1
|
||||
mode: 'none' //不要默认的错误提示
|
||||
});
|
||||
if (userInfo) {
|
||||
notification.success({
|
||||
message: t('登录成功'),
|
||||
description: `${t('欢迎回来')}: ${userInfo.name}`,
|
||||
duration: 3
|
||||
});
|
||||
if (rememberMe.value) {
|
||||
const info = {
|
||||
account: Base64.encode(data.account),
|
||||
password: Base64.encode(data.password),
|
||||
tenantCode: Base64.encode(data.tenantCode)
|
||||
};
|
||||
window.localStorage.setItem('USER__LOGIN__INFO__', JSON.stringify(info));
|
||||
} else {
|
||||
window.localStorage.removeItem('USER__LOGIN__INFO__');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as unknown as Error).message.includes('验证码')) {
|
||||
loginUseType.value = 'captcha';
|
||||
// setLoginState(LoginStateEnum.LOGIN_WITH_CAPTCHA);
|
||||
refreshCaptcha();
|
||||
}
|
||||
createErrorModal({
|
||||
title: t('错误提示'),
|
||||
content: (error as unknown as Error).message || t('网络异常,请检查您的网络连接是否正常!'),
|
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
handleBlur();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
let params = {
|
||||
mobile: data.mobile,
|
||||
code: data.code
|
||||
}
|
||||
let res = await sendMobileLoginCode(params)
|
||||
await oauthLogin(res.token)
|
||||
} catch (error) {
|
||||
createErrorModal({
|
||||
title: t('错误提示'),
|
||||
content: (error as unknown as Error).message || t('网络异常,请检查您的网络连接是否正常!'),
|
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.tip-msg {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-box {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.f-16 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-inner) {
|
||||
border-color: #ced5f2;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item input[type='checkbox']) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
line-height: 40px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sub-button {
|
||||
height: 48px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ant-form label {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
font-size: 26px;
|
||||
margin-right: 8px;
|
||||
color: #707c92;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
border-color: #ced5f2;
|
||||
border-style: none none solid;
|
||||
}
|
||||
|
||||
:deep(.ant-input-password-icon) {
|
||||
font-size: 20px;
|
||||
color: #707c92;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper-lg) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.login-modal-content {
|
||||
text-align: center;
|
||||
|
||||
.refresh {
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.captcha-image) {
|
||||
cursor: pointer;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
height: 40px;
|
||||
}
|
||||
// :deep(.phone-outlined-class) {
|
||||
// height: 25.99px;
|
||||
// width: 25.99px;
|
||||
// }
|
||||
</style>
|
||||
<style lang="less">
|
||||
.pic-code {
|
||||
input {
|
||||
min-width: 0!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
src/views/secondDev/SingleLogin.vue
Normal file
24
src/views/secondDev/SingleLogin.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div style="width: 100%; height: 300px; display: flex;justify-content: center;align-items: center;">
|
||||
<a-spin />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
const userStore = useUserStore();
|
||||
onMounted(async () => {
|
||||
const fullPath = currentRoute.value.fullPath;
|
||||
const list = fullPath.split('targetURL=');
|
||||
const targetURL = list[1];
|
||||
let params = {...currentRoute.value.query, targetURL: targetURL, mode: 'none' }; //不要默认的错误提示
|
||||
await userStore.singleLogin(params);
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
</style>
|
||||
|
||||
40
src/views/secondDev/TokenLogin.vue
Normal file
40
src/views/secondDev/TokenLogin.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div style="width: 100%; height: 300px; display: flex;justify-content: center;align-items: center;">
|
||||
<a-spin />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(async () => {
|
||||
const fullPath = currentRoute.value.fullPath;
|
||||
let targetURL = ''
|
||||
let redirect = ''
|
||||
if (fullPath.includes('targetURL')) {
|
||||
const list = fullPath.split('targetURL=');
|
||||
targetURL = list[1];
|
||||
} else {
|
||||
const list = fullPath.split('redirect=');
|
||||
redirect = list[1];
|
||||
}
|
||||
let params = {...currentRoute.value.query, targetURL: targetURL,redirect: redirect, mode: 'none',goHome:true }; //不要默认的错误提示
|
||||
|
||||
const token=currentRoute.value.query?.token;
|
||||
//cas ticket的话走一遍后端换成token
|
||||
const ticket=currentRoute.value.query?.ticket;
|
||||
if(ticket){
|
||||
params.ticket=ticket;
|
||||
await userStore.casLogin(params);
|
||||
}else if (token) {
|
||||
await userStore.tokenLogin(token,params);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
</style>
|
||||
546
src/views/secondDev/approveFlowPage.vue
Normal file
546
src/views/secondDev/approveFlowPage.vue
Normal file
@ -0,0 +1,546 @@
|
||||
<template>
|
||||
<a-spin :spinning="spinning" tip="请稍后...">
|
||||
<div class="page-bg-wrap">
|
||||
<div class="geg-flow-page">
|
||||
<div class="top-toolbar">
|
||||
<a-space :size="10" wrap>
|
||||
<a-button style="margin-right: 10px" @click="close">
|
||||
<slot name="icon">
|
||||
<close-outlined />
|
||||
</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">
|
||||
<check-circle-outlined />
|
||||
</slot>
|
||||
同意
|
||||
</a-button>
|
||||
<a-button v-if="!readonly && hasBtnReject" @click="onDenyClick">
|
||||
<slot name="icon">
|
||||
<stop-outlined />
|
||||
</slot>
|
||||
拒绝
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<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>
|
||||
</template>
|
||||
<a-button>
|
||||
更多
|
||||
<down-outlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</div>
|
||||
<FormInformation
|
||||
:key="renderKey"
|
||||
ref="formInformation"
|
||||
:disabled="readonly"
|
||||
:formAssignmentData="data.formAssignmentData"
|
||||
:formInfos="data.formInfos"
|
||||
:opinions="data.opinions"
|
||||
:opinionsComponents="data.opinionsComponents"
|
||||
@get-form-configs="(config) => (formConfigs = config)"
|
||||
/>
|
||||
<Title :font-size="18" default-value="流转信息"></Title>
|
||||
<flow-history :items="getTaskRecords()"></flow-history>
|
||||
<opinion-dialog ref="opinionDlg" />
|
||||
<transfer-dialog ref="transferDlg" />
|
||||
<a-modal :closable="false" v-if="showFlowChart" visible="true" centered class="geg" title="流程图" width="1200px" @cancel="closeFlowChart">
|
||||
<process-information :process-id="processId" :xml="data.xml"
|
||||
:currentTaskInfo="data.currentTaskInfo"
|
||||
:currentTaskAssigneeNames="data.currentTaskAssigneeNames"
|
||||
:currentTaskAssignees="data.currentTaskAssignees"/>
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="closeFlowChart">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { onMounted, reactive, ref, unref, createVNode } 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 { 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 { separator } from '/@bpmn/config/info';
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import Title from '/@/components/Title/src/Title.vue';
|
||||
import FlowHistory from '/@/components/SecondDev/FlowHistory.vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import useEventBus from '/@/hooks/event/useEventBus';
|
||||
import ProcessInformation from '/@/views/workflow/task/components/flow/ProcessInformation.vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { deleteDraft, postDraft, putDraft, getDraftInfo } from '/@/api/workflow/process';
|
||||
import { TaskTypeUrl } from '/@/enums/workflowEnum';
|
||||
|
||||
|
||||
const spinning = ref(false);
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const { notification } = useMessage();
|
||||
|
||||
const { data, approveUserData, initProcessData, notificationError, notificationSuccess } = userTaskItem();
|
||||
const { bus, FLOW_PROCESSED } = useEventBus();
|
||||
|
||||
const tabStore = useMultipleTabStore();
|
||||
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 taskId = ref(rQuery.taskId);
|
||||
const processId = ref(rParams.arg2);
|
||||
const readonly = ref(!!rQuery.readonly); // 查看流程会触发只读模式
|
||||
const rDraftsId = ref(rQuery.draftId || '')
|
||||
const renderKey = ref('');
|
||||
const formConfigs = ref();
|
||||
const opinionDlg = ref();
|
||||
const transferDlg = ref();
|
||||
const validateSuccess = ref(false);
|
||||
const formInformation = ref();
|
||||
const showFlowChart = ref(false);
|
||||
const hasBtnApprove = ref(true);
|
||||
const hasBtnReject = ref(false);
|
||||
const hasBtnFinish = ref(false);
|
||||
let draftData = {}
|
||||
const drawNode = ref('');
|
||||
const props = defineProps({
|
||||
rowKeyData: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
let approvalData = reactive({
|
||||
isCountersign: false,
|
||||
isAddOrSubSign: false,
|
||||
isEnd: false,
|
||||
stampInfo: {
|
||||
stampId: '',
|
||||
password: ''
|
||||
},
|
||||
buttonConfigs: [],
|
||||
approvedType: ApproveType.AGREE,
|
||||
approvedResult: ApproveCode.AGREE,
|
||||
approvedContent: '',
|
||||
rejectNodeActivityId: '',
|
||||
rejectNodeActivityIds: [],
|
||||
circulateConfigs: [],
|
||||
nextTaskUser: {} // 格式为taskKey: 用户id(逗号分隔)
|
||||
});
|
||||
let approvedType = ref(ApproveType.AGREE);
|
||||
function onMoreClick(e) {
|
||||
const key = e.key;
|
||||
if (key === 'flowchart') {
|
||||
openFlowChart();
|
||||
} else if (key === 'finish') {
|
||||
Modal.confirm({
|
||||
title: () => '提示',
|
||||
content: () => '确定终止吗?',
|
||||
onOk: () => {
|
||||
onFinishClick();
|
||||
}
|
||||
});
|
||||
} else if (key === 'transfer') {
|
||||
onTransferClick();
|
||||
} else if (key === 'drawBack') {
|
||||
Modal.confirm({
|
||||
title: t('提示'),
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: t('请确认是否撤回该流程?'),
|
||||
okText: t('确定'),
|
||||
okType: 'danger',
|
||||
cancelText: t('取消'),
|
||||
onOk() {
|
||||
openSpinning();
|
||||
withdraw(processId.value, drawNode.value).then((res) => {
|
||||
if (res) {
|
||||
notification.open({
|
||||
type: 'success',
|
||||
message: t('撤回'),
|
||||
description: t('撤回成功')
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: 'error',
|
||||
message: t('撤回'),
|
||||
description: t('撤回失败')
|
||||
});
|
||||
}
|
||||
}).finally(()=>{
|
||||
closeSpinning();
|
||||
});
|
||||
},
|
||||
onCancel() {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function closeFlowChart() {
|
||||
showFlowChart.value = false;
|
||||
}
|
||||
|
||||
function openFlowChart() {
|
||||
showFlowChart.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
tabStore.closeTab(currentRoute, router);
|
||||
}
|
||||
async function setDraft(needModal = true) {
|
||||
let formData = [];
|
||||
let params = {
|
||||
taskId: taskId.value,
|
||||
userId: userStore.getUserInfo.id,
|
||||
}
|
||||
let res = await getDraftInfo('', taskId.value, userStore.getUserInfo.id)
|
||||
if(res) {
|
||||
draftData = JSON.parse(res.formData)
|
||||
rDraftsId.value = res.id
|
||||
if(!needModal) return
|
||||
Modal.confirm({
|
||||
title: () => '提示',
|
||||
content: () => '确认使用草稿覆盖当前数据?',
|
||||
onOk: async () => {
|
||||
data.formInfos.forEach((item) => {
|
||||
if (draftData && item.formConfig && item.formConfig.key && draftData[item.formConfig.key]) {
|
||||
formData.push(item.formConfig.key ? draftData[item.formConfig.key] : {});
|
||||
}
|
||||
});
|
||||
await formInformation.value.setFormData(formData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
async function saveDraft() {
|
||||
try {
|
||||
spinning.value = true;
|
||||
let formModels = await formInformation.value.saveDraftData();
|
||||
if (rDraftsId.value) {
|
||||
let res = await putDraft(schemaId.value, formModels, rDraftsId.value, props.rowKeyData, processId.value, taskId.value);
|
||||
showResult(res, '保存草稿');
|
||||
} else {
|
||||
let res = await postDraft(schemaId.value, formModels, props.rowKeyData, processId.value, taskId.value );
|
||||
showResult(res, '保存草稿');
|
||||
}
|
||||
setDraft(false)
|
||||
spinning.value = false;
|
||||
} catch (error) {
|
||||
spinning.value = false;
|
||||
notificationError('保存草稿');
|
||||
}
|
||||
}
|
||||
function showResult(res, title) {
|
||||
if (res) {
|
||||
notificationSuccess(title);
|
||||
} else {
|
||||
notificationError(title);
|
||||
}
|
||||
}
|
||||
|
||||
async function onApproveClick(isAutoAgreeBreak = false) {
|
||||
openSpinning();
|
||||
if (!isAutoAgreeBreak) {
|
||||
await submit();
|
||||
}
|
||||
if (!validateSuccess.value) {
|
||||
closeSpinning();
|
||||
return;
|
||||
}
|
||||
const params = await getApproveParams();
|
||||
const nextNodes = await postGetNextTaskMaybeArrival(params);
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvalData.approvedResult = ApproveCode.AGREE;
|
||||
//如果是自动同意触发的,关闭弹层的loading
|
||||
if (isAutoAgreeBreak) {
|
||||
opinionDlg.value.stopLoading()
|
||||
}
|
||||
closeSpinning();
|
||||
opinionDlg.value.toggleDialog({
|
||||
action: 'agree',
|
||||
nextNodes,
|
||||
callback: (args) => {
|
||||
approvalData.approvedContent = args.opinion;
|
||||
approvalData.nextTaskUser = args.nextTaskUser;
|
||||
approvalData.isEnd=args.isEnd;
|
||||
onFinish('approve');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function onDenyClick() {
|
||||
approvalData.approvedType = ApproveType.REJECT;
|
||||
approvalData.approvedResult = ApproveCode.REJECT;
|
||||
opinionDlg.value.toggleDialog({
|
||||
action: 'reject',
|
||||
processId: processId.value,
|
||||
taskId: taskId.value,
|
||||
callback: (args) => {
|
||||
approvalData.approvedContent = args.opinion;
|
||||
approvalData.rejectNodeActivityId = args.rejectNodeId;
|
||||
approvalData.nextTaskUser = args.nextTaskUser;
|
||||
onFinish('reject');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onTransferClick() {
|
||||
transferDlg.value.toggleDialog({
|
||||
taskId: taskId.value,
|
||||
callback: (args) => {
|
||||
onTransfer(args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onTransfer({ taskId, userId }) {
|
||||
transferDlg.value.validate().then(() => {
|
||||
transferDlg.value.startLoading();
|
||||
postTransfer(taskId, userId)
|
||||
.then((res) => {
|
||||
transferDlg.value.toggleDialog({
|
||||
isClose: true
|
||||
});
|
||||
message.success('转办成功');
|
||||
setTimeout(() => {
|
||||
bus.emit(FLOW_PROCESSED);
|
||||
close();
|
||||
}, 500);
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('转办失败');
|
||||
transferDlg.value.toggleDialog({
|
||||
isClose: true
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onTransferFlowFail() {
|
||||
transferDlg.value.stopLoading();
|
||||
}
|
||||
|
||||
async function onFinishClick() {
|
||||
approvalData.approvedType = ApproveType.FINISH;
|
||||
approvalData.approvedResult = ApproveCode.FINISH;
|
||||
onFinish('finish');
|
||||
}
|
||||
|
||||
function flowSuccess() {
|
||||
opinionDlg.value.toggleDialog({
|
||||
isClose: true
|
||||
});
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
bus.emit(FLOW_PROCESSED);
|
||||
close();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function flowFail() {
|
||||
opinionDlg.value.stopLoading();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
approvalData.isAddOrSubSign = false;
|
||||
approvalData.stampInfo = {
|
||||
stampId: '',
|
||||
password: ''
|
||||
};
|
||||
approvalData.buttonConfigs = [];
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
}
|
||||
|
||||
function setBtnStatus() {
|
||||
const btnConfigs = approvalData.buttonConfigs;
|
||||
btnConfigs.forEach((btn) => {
|
||||
const code = btn.buttonCode;
|
||||
if (code === 'reject') {
|
||||
hasBtnReject.value = true;
|
||||
} else if (code === 'finish') {
|
||||
hasBtnFinish.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getTaskRecords() {
|
||||
if (data?.taskApproveOpinions?.length) {
|
||||
return data.taskApproveOpinions || [];
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
let res = await getApprovalProcess(unref(taskId), unref(processId));
|
||||
initProcessData(res);
|
||||
const title = res?.schemaInfo?.name;
|
||||
if (title) {
|
||||
const tabPrefix = readonly.value ? '查看' : '审批';
|
||||
tabStore.changeTitle(fullPath, `${tabPrefix}:${title}`);
|
||||
}
|
||||
if (!readonly.value) {
|
||||
if (res.buttonConfigs) {
|
||||
approvalData.buttonConfigs = res.buttonConfigs;
|
||||
setBtnStatus();
|
||||
}
|
||||
if (res.relationTasks) {
|
||||
data.predecessorTasks = res.relationTasks;
|
||||
}
|
||||
if (res.isAddOrSubSign) {
|
||||
approvalData.isAddOrSubSign = res.isAddOrSubSign;
|
||||
}
|
||||
|
||||
approvalData.approvedType = ApproveType.AGREE;
|
||||
approvedType.value = ApproveType.AGREE;
|
||||
approvalData.approvedContent = '';
|
||||
approvalData.rejectNodeActivityId = '';
|
||||
approvalData.rejectNodeActivityIds = [];
|
||||
approvalData.circulateConfigs = [];
|
||||
}
|
||||
renderKey.value = Math.random() + '';
|
||||
getBackNode();
|
||||
setDraft()
|
||||
} catch (error) {}
|
||||
});
|
||||
|
||||
function getBackNode() {
|
||||
getDrawNode(processId.value).then((res) => {
|
||||
if (res.length) {
|
||||
drawNode.value = res[0].activityId;
|
||||
} else {
|
||||
drawNode.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
data.submitLoading = true;
|
||||
validateSuccess.value = false;
|
||||
try {
|
||||
let validateForms = await formInformation.value.validateForm();
|
||||
if (validateForms.length > 0) {
|
||||
let successValidate = validateForms.filter((ele) => {
|
||||
return ele.validate;
|
||||
});
|
||||
if (successValidate.length == validateForms.length) {
|
||||
validateSuccess.value = true;
|
||||
data.submitLoading = false;
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('表单校验未通过'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('审批流程'), t('审批流程失败'));
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadFileFolderIds(formModels) {
|
||||
let fileFolderIds = [];
|
||||
let uploadComponentIds = formInformation.value.getUploadComponentIds();
|
||||
uploadComponentIds.forEach((ids) => {
|
||||
if (ids.includes(separator)) {
|
||||
let arr = ids.split(separator);
|
||||
if (arr.length == 2 && formModels[arr[0]][arr[1]]) {
|
||||
fileFolderIds.push(formModels[arr[0]][arr[1]]);
|
||||
} else if (arr.length == 3 && formModels[arr[0]][arr[1]] && Array.isArray(formModels[arr[0]][arr[1]])) {
|
||||
formModels[arr[0]][arr[1]].forEach((o) => {
|
||||
fileFolderIds.push(o[arr[2]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileFolderIds;
|
||||
}
|
||||
|
||||
async function getApproveParams() {
|
||||
let formModels = await formInformation.value.getFormModels();
|
||||
let system = formInformation.value.getSystemType();
|
||||
let fileFolderIds = getUploadFileFolderIds(formModels);
|
||||
return {
|
||||
approvedType: approvalData.approvedType,
|
||||
approvedResult: approvalData.approvedResult, // approvalData.approvedType 审批结果 如果为 4 就需要传buttonCode
|
||||
approvedContent: approvalData.approvedContent,
|
||||
formData: formModels,
|
||||
rejectNodeActivityId: approvalData.rejectNodeActivityId,
|
||||
taskId: taskId.value,
|
||||
fileFolderIds,
|
||||
circulateConfigs: approvalData.circulateConfigs,
|
||||
/*stampId: values.stampId,
|
||||
stampPassword: values.password,*/
|
||||
isOldSystem: system,
|
||||
isEnd:approvalData.isEnd,
|
||||
nextTaskUser: approvalData.nextTaskUser
|
||||
};
|
||||
}
|
||||
|
||||
async function onFinish(values) {
|
||||
try {
|
||||
if (validateSuccess.value || values === 'reject' || values === 'finish') {
|
||||
let params = await getApproveParams();
|
||||
let response = await postApproval(params);
|
||||
// 判断返回值是否带有isAutoAgree 来判断中间是否有自动审批的业务,如有再执行判断待审人员是否包含自己,不包含就直接flowSuccess
|
||||
if (checkIsAutoAgree(response)) return
|
||||
flowSuccess();
|
||||
data.submitLoading = false;
|
||||
}
|
||||
} catch (error) {
|
||||
flowFail();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断该次审核是否触发自动同意事务,并且检验返回得task 是否是自身作为被审需要弹框再次审核
|
||||
* @param response
|
||||
*/
|
||||
function checkIsAutoAgree(response) {
|
||||
if (response != null
|
||||
&& 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;
|
||||
onApproveClick(true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function openSpinning() {
|
||||
spinning.value = true;
|
||||
}
|
||||
function closeSpinning() {
|
||||
spinning.value = false;
|
||||
}
|
||||
</script>
|
||||
393
src/views/secondDev/createFlow.vue
Normal file
393
src/views/secondDev/createFlow.vue
Normal file
@ -0,0 +1,393 @@
|
||||
<template>
|
||||
<div class="page-bg-wrap">
|
||||
<a-spin :spinning="loading" tip="加载中...">
|
||||
<div class="geg-flow-page">
|
||||
<div class="top-toolbar">
|
||||
<a-space :size="10" wrap>
|
||||
<a-button style="margin-right: 10px" @click="close">
|
||||
<slot name="icon">
|
||||
<close-outlined />
|
||||
</slot>
|
||||
关闭
|
||||
</a-button>
|
||||
<a-button :disabled="data.submitLoading" type="primary" @click="saveLaunch">
|
||||
<slot name="icon">
|
||||
<send-outlined />
|
||||
</slot>
|
||||
提交
|
||||
</a-button>
|
||||
<a-button :disabled="data.submitLoading" @click="saveDraft">
|
||||
<slot name="icon">
|
||||
<clock-circle-outlined />
|
||||
</slot>
|
||||
暂存
|
||||
</a-button>
|
||||
<a-button>
|
||||
<slot name="icon">
|
||||
<printer-outlined />
|
||||
</slot>
|
||||
打印
|
||||
</a-button>
|
||||
<a-button @click="openFlowChart">
|
||||
<slot name="icon">
|
||||
<apartment-outlined />
|
||||
</slot>
|
||||
流程图
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="flow-content">
|
||||
<FormInformation :key="randKey" ref="formInformation" :disabled="false" :formAssignmentData="data.formAssignmentData" :formInfos="data.formInfos" :opinions="data.opinions" :opinionsComponents="data.opinionsComponents" />
|
||||
</div>
|
||||
</div>
|
||||
<a-modal :visible="showFlowChart" centered class="geg" closable title="流程图" width="1200px" @cancel="closeFlowChart">
|
||||
<process-information :process-id="processId" :xml="data.xml" />
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="closeFlowChart">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
<opinion-dialog ref="opinionDlg" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import userTaskItem from '/@/views/workflow/task/hooks/userTaskItem';
|
||||
import FormInformation from '/@/views/secondDev/FormInformation.vue';
|
||||
import ProcessInformation from '/@/views/workflow/task/components/flow/ProcessInformation.vue';
|
||||
import { getStartProcessInfo, getReStartProcessInfo, reLaunch, postLaunch, postApproval, postGetNextTaskMaybeArrival } from '/@/api/workflow/task';
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import { CloseOutlined, SendOutlined, ClockCircleOutlined, PrinterOutlined, ApartmentOutlined } from '@ant-design/icons-vue';
|
||||
import { nextTick, onMounted, ref, toRaw, reactive } from 'vue';
|
||||
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 OpinionDialog from '/@/components/SecondDev/OpinionDialog.vue';
|
||||
import { ApproveCode, ApproveType } from '/@/enums/workflowEnum';
|
||||
import useEventBus from '/@/hooks/event/useEventBus';
|
||||
|
||||
const { bus, CREATE_FLOW } = useEventBus();
|
||||
|
||||
const router = useRouter();
|
||||
const tabStore = useMultipleTabStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { data, approveUserData, initProcessData, notificationSuccess, notificationError } = userTaskItem();
|
||||
const currentRoute = router.currentRoute.value;
|
||||
const rParams = currentRoute.params;
|
||||
const fullPath = currentRoute.fullPath;
|
||||
const rQuery = currentRoute.query;
|
||||
const rSchemaId = rParams.arg1;
|
||||
const rDraftsId = rParams.arg2;
|
||||
const taskId = ref();
|
||||
const loading = ref(false);
|
||||
const draftsJsonStr = localStorage.getItem('draftsJsonStr');
|
||||
const formJsonStr = localStorage.getItem('formJsonStr');
|
||||
let formInformation = ref();
|
||||
const opinionDlg = ref();
|
||||
let pageMode = 'new';
|
||||
const showFlowChart = ref(false);
|
||||
const disableSubmit = ref(false);
|
||||
const mainFormModels = ref();
|
||||
let randKey = ref('randkey'); // 强制表单重新渲染
|
||||
let approvalData = reactive({
|
||||
isCountersign: false,
|
||||
isAddOrSubSign: false,
|
||||
stampInfo: {
|
||||
stampId: '',
|
||||
password: ''
|
||||
},
|
||||
buttonConfigs: [],
|
||||
approvedType: ApproveType.AGREE,
|
||||
approvedResult: ApproveCode.AGREE,
|
||||
approvedContent: '',
|
||||
rejectNodeActivityId: '',
|
||||
rejectNodeActivityIds: [],
|
||||
circulateConfigs: [],
|
||||
nextTaskUser: {} // 格式为taskKey: 用户id(逗号分隔)
|
||||
});
|
||||
|
||||
if (draftsJsonStr) {
|
||||
localStorage.removeItem('draftsJsonStr');
|
||||
pageMode = 'draft';
|
||||
}
|
||||
if (formJsonStr) {
|
||||
localStorage.removeItem('formJsonStr');
|
||||
}
|
||||
|
||||
function closeFlowChart() {
|
||||
showFlowChart.value = false;
|
||||
}
|
||||
|
||||
function openFlowChart() {
|
||||
showFlowChart.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
tabStore.closeTab(currentRoute, router);
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
rowKeyData: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 发起流程
|
||||
let res = await getStartProcessInfo(rSchemaId);
|
||||
const title = res?.schemaInfo?.name;
|
||||
if (title) {
|
||||
const tabPrefix = pageMode === 'new' ? '新建' : '草稿';
|
||||
tabStore.changeTitle(fullPath, `${tabPrefix}:${title}`);
|
||||
}
|
||||
initProcessData(res);
|
||||
} catch (error) {}
|
||||
randKey.value = Math.random() + '';
|
||||
// 这里的顺序不能变 表单不渲染的时候 设置表单初值没用
|
||||
await nextTick();
|
||||
await initDraftsFormData();
|
||||
await initFromFormData();
|
||||
});
|
||||
|
||||
async function initDraftsFormData() {
|
||||
//isPublish.value = Object.keys(data.formInfos).length > 0 ? false : true;
|
||||
if (draftsJsonStr) {
|
||||
let formDataJson = JSON.parse(draftsJsonStr);
|
||||
let formData = [];
|
||||
|
||||
data.formInfos.forEach((item) => {
|
||||
if (formDataJson && item.formConfig && item.formConfig.key && formDataJson[item.formConfig.key]) {
|
||||
formData.push(item.formConfig.key ? formDataJson[item.formConfig.key] : {});
|
||||
}
|
||||
});
|
||||
await formInformation.value.setFormData(formData);
|
||||
}
|
||||
}
|
||||
|
||||
async function initFromFormData() {
|
||||
if (formJsonStr) {
|
||||
let formDataJson = JSON.parse(formJsonStr);
|
||||
let formData = [];
|
||||
if(rQuery.fromKey&&formDataJson[rQuery.fromKey]){
|
||||
formData.push(formDataJson[rQuery.fromKey]);
|
||||
}
|
||||
await formInformation.value.setFormData(formData);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDraft() {
|
||||
try {
|
||||
disableSubmit.value = true;
|
||||
let formModels = await formInformation.value.saveDraftData();
|
||||
if (rDraftsId !== '0') {
|
||||
let res = await putDraft(rSchemaId, formModels, rDraftsId, props.rowKeyData);
|
||||
showResult(res, '保存草稿');
|
||||
} else {
|
||||
let res = await postDraft(rSchemaId, formModels, props.rowKeyData);
|
||||
showResult(res, '保存草稿');
|
||||
}
|
||||
} catch (error) {
|
||||
notificationError('保存草稿');
|
||||
}
|
||||
}
|
||||
|
||||
function showResult(res, title) {
|
||||
if (res) {
|
||||
notificationSuccess(title);
|
||||
} else {
|
||||
notificationError(title);
|
||||
}
|
||||
}
|
||||
|
||||
function createFlowSuccess(taskList) {
|
||||
/*opinionDlg.value.toggleDialog({
|
||||
isClose: true
|
||||
});*/
|
||||
if (taskList) {
|
||||
approvalCreate();
|
||||
} else {
|
||||
message.success('流程发起成功');
|
||||
|
||||
data.submitLoading = false;
|
||||
loading.value = false;
|
||||
setTimeout(() => {
|
||||
bus.emit(CREATE_FLOW, {});
|
||||
close();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
async function approvalCreate() {
|
||||
const params = await getApproveParams();
|
||||
const nextNodes = await postGetNextTaskMaybeArrival(params);
|
||||
if(nextNodes.length==0){
|
||||
message.error('流程没有可以选择的下一节点');
|
||||
loading.value = false;
|
||||
data.submitLoading = false;
|
||||
return;
|
||||
}
|
||||
opinionDlg.value.toggleDialog({
|
||||
action: 'agree',
|
||||
nextNodes,
|
||||
rejectCancel: () => {
|
||||
loading.value = false;
|
||||
data.submitLoading = false;
|
||||
},
|
||||
callback: (args) => {
|
||||
approvalData.approvedContent = args.opinion;
|
||||
approvalData.nextTaskUser = args.nextTaskUser;
|
||||
onFinish({});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flowSuccess() {
|
||||
opinionDlg.value.toggleDialog({
|
||||
isClose: true
|
||||
});
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
bus.emit(CREATE_FLOW, {});
|
||||
close();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function flowFail() {
|
||||
opinionDlg.value.stopLoading();
|
||||
}
|
||||
|
||||
async function onFinish(values) {
|
||||
try {
|
||||
if (/*validateSuccess.value*/ true) {
|
||||
let params = await getApproveParams();
|
||||
await postApproval(params);
|
||||
flowSuccess();
|
||||
data.submitLoading = false;
|
||||
loading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
flowFail();
|
||||
}
|
||||
}
|
||||
|
||||
async function getApproveParams() {
|
||||
let formModels = mainFormModels.value;
|
||||
let system = formInformation.value.getSystemType();
|
||||
let fileFolderIds = getUploadFileFolderIds(formModels);
|
||||
return {
|
||||
approvedType: approvalData.approvedType,
|
||||
approvedResult: approvalData.approvedResult, // approvalData.approvedType 审批结果 如果为 4 就需要传buttonCode
|
||||
approvedContent: approvalData.approvedContent,
|
||||
formData: formModels,
|
||||
rejectNodeActivityId: approvalData.rejectNodeActivityId,
|
||||
taskId: taskId.value,
|
||||
fileFolderIds,
|
||||
circulateConfigs: approvalData.circulateConfigs,
|
||||
/*stampId: values.stampId,
|
||||
stampPassword: values.password,*/
|
||||
isOldSystem: system,
|
||||
nextTaskUser: approvalData.nextTaskUser,
|
||||
draftId: rDraftsId,
|
||||
};
|
||||
}
|
||||
|
||||
async function saveLaunch() {
|
||||
data.submitLoading = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
let validateForms = await formInformation.value.validateForm();
|
||||
let system = formInformation.value.getSystemType();
|
||||
|
||||
if (validateForms.length > 0) {
|
||||
let successValidate = validateForms.filter((ele) => {
|
||||
return ele.validate;
|
||||
});
|
||||
if (successValidate.length == validateForms.length) {
|
||||
mainFormModels.value = await formInformation.value.getFormModels(true);
|
||||
/*for (let i in mainFormModels.value) {
|
||||
const item = mainFormModels.value[i];
|
||||
if (!item['_id']) {
|
||||
throw new Error('发起失败');
|
||||
} else {
|
||||
delete item['_id'];
|
||||
}
|
||||
}*/
|
||||
let relationTasks = [];
|
||||
if (data.predecessorTasks && data.predecessorTasks.length > 0) {
|
||||
relationTasks = data.predecessorTasks.map((ele) => {
|
||||
return { taskId: ele.taskId, schemaId: ele.schemaId };
|
||||
});
|
||||
}
|
||||
let fileFolderIds = getUploadFileFolderIds(mainFormModels.value);
|
||||
if (taskId.value) {
|
||||
// 如果在草稿节点取消,重新从草稿开始走
|
||||
return createFlowSuccess(true);
|
||||
}
|
||||
//如果传入了processId 代表是重新发起流程
|
||||
let res;
|
||||
res = await postLaunch(rSchemaId, mainFormModels.value, relationTasks, fileFolderIds, system);
|
||||
|
||||
// 下一节点审批人
|
||||
let taskList = [];
|
||||
if (res && res.length > 0) {
|
||||
taskId.value = res[0].taskId;
|
||||
taskList = res
|
||||
.filter((ele) => {
|
||||
return ele.isMultiInstance == false && ele.isAppoint == true;
|
||||
})
|
||||
.map((ele) => {
|
||||
return {
|
||||
taskId: ele.taskId,
|
||||
taskName: ele.taskName,
|
||||
provisionalApprover: ele.provisionalApprover,
|
||||
selectIds: []
|
||||
};
|
||||
});
|
||||
const strictDesign = false;
|
||||
if (strictDesign && taskList.length > 0) {
|
||||
notificationError('提交失败', '流程设计错误,开始后的第一个节点必须是审批人为发起人的起草节点。');
|
||||
data.submitLoading = false;
|
||||
loading.value = false;
|
||||
} else {
|
||||
createFlowSuccess(taskList);
|
||||
}
|
||||
} else {
|
||||
createFlowSuccess();
|
||||
}
|
||||
} else {
|
||||
data.submitLoading = false;
|
||||
notificationError(t('发起流程'), t('表单校验未通过'));
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
data.submitLoading = false;
|
||||
loading.value = false;
|
||||
notificationError(t('发起流程'), t('发起流程失败'));
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadFileFolderIds(formModels) {
|
||||
let fileFolderIds = [];
|
||||
let uploadComponentIds = formInformation.value.getUploadComponentIds();
|
||||
uploadComponentIds.forEach((ids) => {
|
||||
if (ids.includes(separator)) {
|
||||
let arr = ids.split(separator);
|
||||
if (arr.length == 2 && formModels[arr[0]][arr[1]]) {
|
||||
fileFolderIds.push(formModels[arr[0]][arr[1]]);
|
||||
} else if (arr.length == 3 && formModels[arr[0]][arr[1]] && Array.isArray(formModels[arr[0]][arr[1]])) {
|
||||
formModels[arr[0]][arr[1]].forEach((o) => {
|
||||
fileFolderIds.push(o[arr[2]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return fileFolderIds;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
141
src/views/secondDev/formCreatePage.vue
Normal file
141
src/views/secondDev/formCreatePage.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="page-bg-wrap">
|
||||
<div class="top-toolbar">
|
||||
<a-space :size="10" wrap style="gap:0">
|
||||
<a-button style="margin-right: 10px" @click="close">
|
||||
<slot name="icon">
|
||||
<close-outlined />
|
||||
</slot>
|
||||
关闭
|
||||
</a-button>
|
||||
<a-button v-if="mode != 'view'" type="primary" @click="handleSubmit">
|
||||
<slot name="icon">
|
||||
<check-circle-outlined />
|
||||
</slot>
|
||||
确认
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<component :is="dynamicComponent" ref="formRef" :fromPage="FromPageType.MENU" @form-mounted="onFormMounted" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { FromPageType } from '/@/enums/workflowEnum';
|
||||
import { ref, computed, onMounted, onBeforeMount, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { CheckCircleOutlined, StopOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import useEventBus from '/@/hooks/event/useEventBus';
|
||||
|
||||
const dynamicComponent = ref(null);
|
||||
const formType = ref('2'); // 0 新建 1 修改 2 查看
|
||||
const formRef = ref();
|
||||
const props = defineProps({});
|
||||
const { bus, FORM_LIST_MODIFIED } = useEventBus();
|
||||
|
||||
const router = useRouter();
|
||||
const { currentRoute } = router;
|
||||
|
||||
const { formPath } = currentRoute.value.query;
|
||||
const pathArr = formPath.split('/');
|
||||
|
||||
const tabStore = useMultipleTabStore();
|
||||
const formProps = ref(null);
|
||||
const formId = ref(currentRoute.value?.params?.id);
|
||||
|
||||
const { notification } = useMessage();
|
||||
const { t } = useI18n();
|
||||
const hash = location.hash||location.pathname;
|
||||
const mode = ref('read');
|
||||
if (hash.indexOf('createForm') > 0) {
|
||||
mode.value = 'create';
|
||||
} else if (hash.indexOf('updateForm') > 0) {
|
||||
mode.value = 'update';
|
||||
} else if (hash.indexOf('viewForm') > 0) {
|
||||
mode.value = 'view';
|
||||
}
|
||||
|
||||
dynamicComponent.value = defineAsyncComponent({
|
||||
loader: () => import(`./../../views/${pathArr[0]}/${pathArr[1]}/components/Form.vue`)
|
||||
});
|
||||
|
||||
function onFormMounted(_formProps) {
|
||||
formProps.value = _formProps;
|
||||
setFormType();
|
||||
}
|
||||
|
||||
async function setFormType() {
|
||||
const _mode = mode.value;
|
||||
if (_mode === 'create') {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
if (_mode === 'view') {
|
||||
await formRef.value.setDisabledForm();
|
||||
}
|
||||
await formRef.value.setFormDataFromId(formId.value);
|
||||
}
|
||||
|
||||
function close() {
|
||||
tabStore.closeTab(currentRoute.value, router);
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const saveSuccess = await saveModal();
|
||||
if (saveSuccess) {
|
||||
notification.success({
|
||||
message: 'Tip',
|
||||
description: formType.value === '0' ? t('新增成功!') : t('修改成功!')
|
||||
}); //提示消息
|
||||
formRef.value.resetFields();
|
||||
setTimeout(() => {
|
||||
bus.emit(FORM_LIST_MODIFIED, { path: formPath, mode });
|
||||
close();
|
||||
}, 1000);
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveModal() {
|
||||
let saveSuccess = false;
|
||||
const _mode = mode.value;
|
||||
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 (_mode === 'create') {
|
||||
saveSuccess = await formRef.value.add(values);
|
||||
} else {
|
||||
saveSuccess = await formRef.value.update({ values, rowId: formId.value });
|
||||
}
|
||||
return saveSuccess;
|
||||
} catch (error) { }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('saveModal Error: ', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-bg-wrap {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.top-toolbar {
|
||||
min-height: 44px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
120
src/views/secondDev/processTasksPage.vue
Normal file
120
src/views/secondDev/processTasksPage.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<PageWrapper contentClass="flex" contentFullHeight dense fixedHeight>
|
||||
<component :is="data.componentName" />
|
||||
</PageWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, shallowRef, defineAsyncComponent, unref, ref } from 'vue';
|
||||
import { PageWrapper } from '/@/components/Page';
|
||||
import ToDoTasks from '/@/views/workflow/task/components/processTasks/ToDoTasksV2.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { currentRoute } = useRouter();
|
||||
const tabStore = useMultipleTabStore();
|
||||
|
||||
const FlowLaunch = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/FlowLaunch.vue')
|
||||
});
|
||||
|
||||
const MyProcess = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/components/processTasks/MyProcessV2.vue')
|
||||
});
|
||||
const TaskDone = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/components/processTasks/TaskDoneV2.vue')
|
||||
});
|
||||
|
||||
const RecycleBin = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/components/processTasks/RecycleBin.vue')
|
||||
});
|
||||
|
||||
const MyCirculation = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/components/processTasks/MyCirculation.vue')
|
||||
});
|
||||
|
||||
const Drafts = defineAsyncComponent({
|
||||
loader: () => import('../workflow/task/components/processTasks/DraftsV2.vue')
|
||||
});
|
||||
const componentByType: Map<string, any> = new Map([
|
||||
['ToDoTasks', ToDoTasks],
|
||||
['FlowLaunch', FlowLaunch],
|
||||
['TaskDone', TaskDone],
|
||||
['MyProcess', MyProcess],
|
||||
['RecycleBin', RecycleBin],
|
||||
['MyCirculation', MyCirculation],
|
||||
['Drafts', Drafts]
|
||||
]);
|
||||
|
||||
const treeData = [
|
||||
{
|
||||
key: 'ToDoTasks',
|
||||
id: 'ToDoTasks',
|
||||
name: t('待办任务'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'FlowLaunch',
|
||||
id: 'FlowLaunch',
|
||||
name: t('发起流程'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'TaskDone',
|
||||
id: 'TaskDone',
|
||||
name: t('已办任务'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'MyProcess',
|
||||
id: 'MyProcess',
|
||||
name: t('我的流程'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'MyCirculation',
|
||||
id: 'MyCirculation',
|
||||
name: t('我的传阅'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'Drafts',
|
||||
id: 'Drafts',
|
||||
name: t('草稿箱'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
},
|
||||
{
|
||||
key: 'RecycleBin',
|
||||
id: 'RecycleBin',
|
||||
name: t('回收站'),
|
||||
icon: 'ant-design:profile-outlined'
|
||||
}
|
||||
];
|
||||
const selectedKeys = ref(['ToDoTasks']);
|
||||
const query = unref(currentRoute).query;
|
||||
let id = 'ToDoTasks';
|
||||
const lHash = location.hash||location.pathname;
|
||||
if (lHash.indexOf('/draft') > 0) {
|
||||
id = 'Drafts';
|
||||
} else if (lHash.indexOf('/myProcess') > 0) {
|
||||
id = 'MyProcess';
|
||||
} else if (lHash.indexOf('/done') > 0) {
|
||||
id = 'TaskDone';
|
||||
}
|
||||
let data = reactive({
|
||||
componentName: shallowRef(ToDoTasks)
|
||||
});
|
||||
if (id) {
|
||||
selectedKeys.value = [id.toString()];
|
||||
handleSelect([id]);
|
||||
}
|
||||
|
||||
function handleSelect(componentRow) {
|
||||
if (componentRow.length > 0 && componentRow[0]) {
|
||||
data.componentName = componentByType.has(componentRow[0]) ? componentByType.get(componentRow[0]) : ToDoTasks;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user