Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
This commit is contained in:
@@ -59,6 +59,8 @@
|
||||
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 三方登录 -->
|
||||
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 忘记密码 -->
|
||||
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
@@ -73,7 +75,7 @@ import { useAppStore } from '@/store/modules/app'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
|
||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
|
||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
||||
|
||||
defineOptions({ name: 'Login' })
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
v-if="loginData.captchaEnable === 'true'"
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
|
||||
278
src/views/Login/components/ForgetPasswordForm.vue
Normal file
278
src/views/Login/components/ForgetPasswordForm.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formSmsResetPassword"
|
||||
:model="resetPasswordData"
|
||||
:rules="rules"
|
||||
class="login-form"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<!-- 租户名 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
|
||||
<el-input
|
||||
v-model="resetPasswordData.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 手机号 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="mobile">
|
||||
<el-input
|
||||
v-model="resetPasswordData.mobile"
|
||||
:placeholder="t('login.mobileNumberPlaceholder')"
|
||||
:prefix-icon="iconCellphone"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
mode="pop"
|
||||
@success="getSmsCode"
|
||||
/>
|
||||
<!-- 验证码 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="code">
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="24">
|
||||
<el-input
|
||||
v-model="resetPasswordData.code"
|
||||
:placeholder="t('login.codePlaceholder')"
|
||||
:prefix-icon="iconCircleCheck"
|
||||
>
|
||||
<template #append>
|
||||
<span
|
||||
v-if="mobileCodeTimer <= 0"
|
||||
class="getMobileCode"
|
||||
style="cursor: pointer"
|
||||
@click="getCode"
|
||||
>
|
||||
{{ t('login.getSmsCode') }}
|
||||
</span>
|
||||
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
||||
{{ mobileCodeTimer }}秒后可重新获取
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- </el-button> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="password">
|
||||
<InputPassword
|
||||
v-model="resetPasswordData.password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
style="width: 100%"
|
||||
strength="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="check_password">
|
||||
<InputPassword
|
||||
v-model="resetPasswordData.check_password"
|
||||
:placeholder="t('login.checkPassword')"
|
||||
style="width: 100%"
|
||||
strength="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 登录按钮 / 返回按钮 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.resetPassword')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="resetPassword()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.backLogin')"
|
||||
class="w-[100%]"
|
||||
@click="handleBackLogin()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
import { sendSmsCode, smsResetPassword } from '@/api/login'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
import { ElLoading } from 'element-plus'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import * as LoginApi from '@/api/login'
|
||||
defineOptions({ name: 'ForgetPasswordForm' })
|
||||
const verify = ref()
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const { currentRoute, push } = useRouter()
|
||||
const formSmsResetPassword = ref()
|
||||
const loginLoading = ref(false)
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||
const { validForm } = useFormValid(formSmsResetPassword)
|
||||
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
const validatePass2 = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== resetPasswordData.password) {
|
||||
callback(new Error('两次输入密码不一致!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
|
||||
mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
min: 4,
|
||||
max: 16,
|
||||
validator: validatePass2,
|
||||
trigger: 'blur',
|
||||
message: '密码长度为4到16位'
|
||||
}
|
||||
],
|
||||
check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
|
||||
code: [required]
|
||||
}
|
||||
|
||||
const resetPasswordData = reactive({
|
||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
tenantName: '',
|
||||
username: '',
|
||||
password: '',
|
||||
check_password: '',
|
||||
mobile: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const smsVO = reactive({
|
||||
tenantName: '',
|
||||
mobile: '',
|
||||
captchaVerification: '',
|
||||
scene: 23
|
||||
})
|
||||
const mobileCodeTimer = ref(0)
|
||||
const redirect = ref<string>('')
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接发送验证码
|
||||
if (resetPasswordData.captchaEnable === 'false') {
|
||||
await getSmsCode({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行发送验证码
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
|
||||
const getSmsCode = async (params) => {
|
||||
if (resetPasswordData.tenantEnable === 'true') {
|
||||
await getTenantId()
|
||||
}
|
||||
smsVO.captchaVerification = params.captchaVerification
|
||||
smsVO.mobile = resetPasswordData.mobile
|
||||
await sendSmsCode(smsVO).then(async () => {
|
||||
message.success(t('login.SmsSendMsg'))
|
||||
// 设置倒计时
|
||||
mobileCodeTimer.value = 60
|
||||
let msgTimer = setInterval(() => {
|
||||
mobileCodeTimer.value = mobileCodeTimer.value - 1
|
||||
if (mobileCodeTimer.value <= 0) {
|
||||
clearInterval(msgTimer)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const getTenantId = async () => {
|
||||
if (resetPasswordData.tenantEnable === 'true') {
|
||||
const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
|
||||
if (res == null) {
|
||||
message.error(t('login.invalidTenantName'))
|
||||
throw t('login.invalidTenantName')
|
||||
}
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
const resetPassword = async () => {
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
await getTenantId()
|
||||
loginLoading.value = true
|
||||
await smsResetPassword(resetPasswordData)
|
||||
.then(async () => {
|
||||
message.success(t('login.resetPasswordSuccess'))
|
||||
setLoginState(LoginStateEnum.LOGIN)
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.smsbtn {
|
||||
margin-top: 33px;
|
||||
}
|
||||
</style>
|
||||
@@ -59,7 +59,13 @@
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col :offset="6" :span="12">
|
||||
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link>
|
||||
<el-link
|
||||
style="float: right"
|
||||
type="primary"
|
||||
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
|
||||
>
|
||||
{{ t('login.forgetPassword') }}
|
||||
</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
@@ -76,6 +82,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
v-if="loginData.captchaEnable === 'true'"
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
@@ -241,7 +248,7 @@ const getTenantByWebsite = async () => {
|
||||
}
|
||||
const loading = ref() // ElLoading.service 返回的实例
|
||||
// 登录
|
||||
const handleLogin = async (params) => {
|
||||
const handleLogin = async (params: any) => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
await getTenantId()
|
||||
@@ -273,7 +280,7 @@ const handleLogin = async (params) => {
|
||||
if (redirect.value.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
await push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
@@ -313,8 +320,7 @@ const doSocialLogin = async (type: number) => {
|
||||
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
||||
|
||||
// 进行跳转
|
||||
const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||
window.location.href = res
|
||||
window.location.href = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||
}
|
||||
}
|
||||
watch(
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
v-if="registerData.captchaEnable === 'true'"
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
|
||||
@@ -4,5 +4,6 @@ import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import RegisterForm from './RegisterForm.vue'
|
||||
import QrCodeForm from './QrCodeForm.vue'
|
||||
import SSOLoginVue from './SSOLogin.vue'
|
||||
import ForgetPasswordForm from './ForgetPasswordForm.vue'
|
||||
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm }
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型列表 -->
|
||||
<el-collapse-transition>
|
||||
<div v-show="isExpand">
|
||||
@@ -90,7 +91,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
|
||||
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||
全部可见
|
||||
@@ -110,7 +111,7 @@
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表单信息" prop="formType" min-width="200">
|
||||
<el-table-column label="表单信息" prop="formType" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
||||
@@ -162,16 +163,6 @@
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
@@ -249,7 +240,7 @@ import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
@@ -337,25 +328,6 @@ const handleChangeState = async (row: any) => {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row: any) => {
|
||||
if (row.type == BpmModelType.BPMN) {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
} else {
|
||||
push({
|
||||
name: 'SimpleModelDesign',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
@@ -496,7 +468,14 @@ const handleDeleteCategory = async () => {
|
||||
/** 添加流程模型弹窗 */
|
||||
const modelFormRef = ref()
|
||||
const openModelForm = (type: string, id?: number) => {
|
||||
modelFormRef.value.open(type, id)
|
||||
if (type === 'create') {
|
||||
push({ name: 'BpmModelCreate' })
|
||||
} else {
|
||||
push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
|
||||
|
||||
@@ -123,29 +123,69 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="谁可以发起" prop="startUserIds">
|
||||
<el-form-item label="谁可以发起" prop="startUserType">
|
||||
<el-select
|
||||
v-model="formData.startUserIds"
|
||||
multiple
|
||||
placeholder="请选择可发起人,默认(不选择)则所有人都可以发起"
|
||||
v-model="formData.startUserType"
|
||||
placeholder="请选择谁可以发起"
|
||||
@change="handleStartUserTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
<el-option label="全员" :value="0" />
|
||||
<el-option label="指定人员" :value="1" />
|
||||
<el-option label="均不可提交" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedStartUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveStartUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openStartUserSelect">
|
||||
<Icon icon="ep:plus" />选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程管理员" prop="managerUserIds">
|
||||
<el-select v-model="formData.managerUserIds" multiple placeholder="请选择流程管理员">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
<el-form-item label="流程管理员" prop="managerUserType">
|
||||
<el-select
|
||||
v-model="formData.managerUserType"
|
||||
placeholder="请选择流程管理员"
|
||||
@change="handleManagerUserTypeChange"
|
||||
>
|
||||
<el-option label="全员" :value="0" />
|
||||
<el-option label="指定人员" :value="1" />
|
||||
<el-option label="均不可提交" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedManagerUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveManagerUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openManagerUserSelect">
|
||||
<Icon icon="ep:plus" />选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -153,6 +193,7 @@
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
@@ -160,11 +201,12 @@ import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
|
||||
import { UserVO } from '@/api/system/user'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { FormVO } from '@/api/bpm/form'
|
||||
|
||||
defineOptions({ name: 'ModelForm' })
|
||||
|
||||
@@ -178,7 +220,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
const formData: any = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
key: '',
|
||||
@@ -191,6 +233,8 @@ const formData = ref({
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: '',
|
||||
visible: true,
|
||||
startUserType: undefined,
|
||||
managerUserType: undefined,
|
||||
startUserIds: [],
|
||||
managerUserIds: []
|
||||
})
|
||||
@@ -208,9 +252,13 @@ const formRules = reactive({
|
||||
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const formList = ref([]) // 流程表单的下拉框的数据
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
const formList = ref<FormVO[]>([]) // 流程表单的下拉框的数据
|
||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
|
||||
const userList = ref<UserVO[]>([]) // 用户列表
|
||||
const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
|
||||
const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
|
||||
const userSelectFormRef = ref() // 用户选择弹窗 ref
|
||||
const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: string) => {
|
||||
@@ -226,6 +274,19 @@ const open = async (type: string, id?: string) => {
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
// 加载数据时,根据已有的用户ID列表初始化已选用户
|
||||
if (formData.value.startUserIds?.length) {
|
||||
formData.value.startUserType = 1
|
||||
selectedStartUsers.value = userList.value.filter((user) =>
|
||||
formData.value.startUserIds.includes(user.id)
|
||||
)
|
||||
}
|
||||
if (formData.value.managerUserIds?.length) {
|
||||
formData.value.managerUserType = 1
|
||||
selectedManagerUsers.value = userList.value.filter((user) =>
|
||||
formData.value.managerUserIds.includes(user.id)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
formData.value.managerUserIds.push(userStore.getUser.id)
|
||||
}
|
||||
@@ -293,9 +354,87 @@ const resetForm = () => {
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: '',
|
||||
visible: true,
|
||||
startUserType: undefined,
|
||||
managerUserType: undefined,
|
||||
startUserIds: [],
|
||||
managerUserIds: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
selectedStartUsers.value = []
|
||||
selectedManagerUsers.value = []
|
||||
}
|
||||
|
||||
/** 处理发起人类型变化 */
|
||||
const handleStartUserTypeChange = (value: number) => {
|
||||
if (value !== 1) {
|
||||
selectedStartUsers.value = []
|
||||
formData.value.startUserIds = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理管理员类型变化 */
|
||||
const handleManagerUserTypeChange = (value: number) => {
|
||||
if (value !== 1) {
|
||||
selectedManagerUsers.value = []
|
||||
formData.value.managerUserIds = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开发起人选择 */
|
||||
const openStartUserSelect = () => {
|
||||
currentSelectType.value = 'start'
|
||||
userSelectFormRef.value.open(0, selectedStartUsers.value)
|
||||
}
|
||||
|
||||
/** 打开管理员选择 */
|
||||
const openManagerUserSelect = () => {
|
||||
currentSelectType.value = 'manager'
|
||||
userSelectFormRef.value.open(0, selectedManagerUsers.value)
|
||||
}
|
||||
|
||||
/** 处理用户选择确认 */
|
||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
|
||||
if (currentSelectType.value === 'start') {
|
||||
selectedStartUsers.value = users
|
||||
formData.value.startUserIds = users.map((u) => u.id)
|
||||
} else {
|
||||
selectedManagerUsers.value = users
|
||||
formData.value.managerUserIds = users.map((u) => u.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除发起人 */
|
||||
const handleRemoveStartUser = (user: UserVO) => {
|
||||
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
|
||||
formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
|
||||
}
|
||||
|
||||
/** 移除管理员 */
|
||||
const handleRemoveManagerUser = (user: UserVO) => {
|
||||
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
|
||||
formData.value.managerUserIds = formData.value.managerUserIds.filter(
|
||||
(id: number) => id !== user.id
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-gray-100 {
|
||||
background-color: #f5f7fa;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6e8eb;
|
||||
}
|
||||
|
||||
.ep-close {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<!-- 流程设计器,负责绘制流程等 -->
|
||||
<MyProcessDesigner
|
||||
key="designer"
|
||||
v-if="xmlString !== undefined"
|
||||
v-model="xmlString"
|
||||
:value="xmlString"
|
||||
v-bind="controlForm"
|
||||
@@ -11,12 +10,14 @@
|
||||
ref="processDesigner"
|
||||
@init-finished="initModeler"
|
||||
:additionalModel="controlForm.additionalModel"
|
||||
:model="model"
|
||||
@save="save"
|
||||
/>
|
||||
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||
<MyProcessPenal
|
||||
v-if="isModelerReady && modeler"
|
||||
key="penal"
|
||||
:bpmnModeler="modeler as any"
|
||||
:bpmnModeler="modeler"
|
||||
:prefix="controlForm.prefix"
|
||||
class="process-panel"
|
||||
:model="model"
|
||||
@@ -34,12 +35,26 @@ import * as ModelApi from '@/api/bpm/model'
|
||||
|
||||
defineOptions({ name: 'BpmModelEditor' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由的查询
|
||||
const props = defineProps<{
|
||||
modelId?: string
|
||||
modelKey?: string
|
||||
modelName?: string
|
||||
value?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success', 'init-finished'])
|
||||
const message = useMessage() // 国际化
|
||||
|
||||
const xmlString = ref(undefined) // BPMN XML
|
||||
const modeler = ref(null) // BPMN Modeler
|
||||
// 表单信息
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(20)
|
||||
provide('formFields', formFields)
|
||||
provide('formType', formType)
|
||||
|
||||
const xmlString = ref<string>('') // BPMN XML
|
||||
const modeler = shallowRef() // BPMN Modeler
|
||||
const processDesigner = ref()
|
||||
const isModelerReady = ref(false)
|
||||
const controlForm = ref({
|
||||
simulation: true,
|
||||
labelEditing: false,
|
||||
@@ -50,66 +65,215 @@ const controlForm = ref({
|
||||
})
|
||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
|
||||
|
||||
// 初始化 bpmnInstances
|
||||
const initBpmnInstances = () => {
|
||||
if (!modeler.value) return false
|
||||
try {
|
||||
const instances = {
|
||||
modeler: modeler.value,
|
||||
modeling: modeler.value.get('modeling'),
|
||||
moddle: modeler.value.get('moddle'),
|
||||
eventBus: modeler.value.get('eventBus'),
|
||||
bpmnFactory: modeler.value.get('bpmnFactory'),
|
||||
elementFactory: modeler.value.get('elementFactory'),
|
||||
elementRegistry: modeler.value.get('elementRegistry'),
|
||||
replace: modeler.value.get('replace'),
|
||||
selection: modeler.value.get('selection')
|
||||
}
|
||||
|
||||
// 检查所有实例是否都存在
|
||||
return Object.values(instances).every((instance) => instance)
|
||||
} catch (error) {
|
||||
console.error('初始化 bpmnInstances 失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 modeler */
|
||||
const initModeler = (item) => {
|
||||
setTimeout(() => {
|
||||
const initModeler = async (item) => {
|
||||
try {
|
||||
modeler.value = item
|
||||
}, 10)
|
||||
// 等待 modeler 初始化完成
|
||||
await nextTick()
|
||||
|
||||
// 确保 modeler 的所有实例都已经准备好
|
||||
if (initBpmnInstances()) {
|
||||
isModelerReady.value = true
|
||||
emit('init-finished')
|
||||
|
||||
// 初始化完成后,设置初始值
|
||||
if (props.modelId) {
|
||||
// 编辑模式
|
||||
const data = await ModelApi.getModel(props.modelId)
|
||||
model.value = {
|
||||
...data,
|
||||
bpmnXml: undefined // 清空 bpmnXml 属性
|
||||
}
|
||||
xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
|
||||
} else if (props.modelKey && props.modelName) {
|
||||
// 新建模式
|
||||
xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
|
||||
model.value = {
|
||||
key: props.modelKey,
|
||||
name: props.modelName
|
||||
} as ModelApi.ModelVO
|
||||
}
|
||||
|
||||
// 导入XML并刷新视图
|
||||
await nextTick()
|
||||
try {
|
||||
await modeler.value.importXML(xmlString.value)
|
||||
if (processDesigner.value?.refresh) {
|
||||
processDesigner.value.refresh()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入XML失败:', error)
|
||||
}
|
||||
} else {
|
||||
console.error('modeler 实例未完全初始化')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化 modeler 失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取默认的BPMN XML */
|
||||
const getDefaultBpmnXml = (key: string, name: string) => {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
|
||||
<process id="${key}" name="${name}" isExecutable="true" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram">
|
||||
<bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>`
|
||||
}
|
||||
|
||||
/** 添加/修改模型 */
|
||||
const save = async (bpmnXml: string) => {
|
||||
const data = {
|
||||
...model.value,
|
||||
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
|
||||
} as unknown as ModelApi.ModelVO
|
||||
// 提交
|
||||
if (data.id) {
|
||||
await ModelApi.updateModelBpmn(data)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await ModelApi.updateModelBpmn(data)
|
||||
message.success('新增成功')
|
||||
try {
|
||||
xmlString.value = bpmnXml
|
||||
if (props.modelId) {
|
||||
// 编辑模式
|
||||
const data = {
|
||||
...model.value,
|
||||
bpmnXml: bpmnXml
|
||||
} as unknown as ModelApi.ModelVO
|
||||
await ModelApi.updateModelBpmn(data)
|
||||
emit('success')
|
||||
} else {
|
||||
// 新建模式,直接返回XML
|
||||
emit('success', bpmnXml)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
message.error('保存失败')
|
||||
}
|
||||
// 跳转回去
|
||||
close()
|
||||
}
|
||||
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
// 监听 key、name 和 value 的变化
|
||||
watch(
|
||||
[() => props.modelKey, () => props.modelName, () => props.value],
|
||||
async ([newKey, newName, newValue]) => {
|
||||
if (!props.modelId && isModelerReady.value) {
|
||||
let shouldRefresh = false
|
||||
|
||||
if (newKey && newName) {
|
||||
const newXml = newValue || getDefaultBpmnXml(newKey, newName)
|
||||
if (newXml !== xmlString.value) {
|
||||
xmlString.value = newXml
|
||||
shouldRefresh = true
|
||||
}
|
||||
model.value = {
|
||||
...model.value,
|
||||
key: newKey,
|
||||
name: newName
|
||||
} as ModelApi.ModelVO
|
||||
} else if (newValue && newValue !== xmlString.value) {
|
||||
xmlString.value = newValue
|
||||
shouldRefresh = true
|
||||
}
|
||||
|
||||
if (shouldRefresh) {
|
||||
// 确保更新后重新渲染
|
||||
await nextTick()
|
||||
if (processDesigner.value?.refresh) {
|
||||
try {
|
||||
await modeler.value?.importXML(xmlString.value)
|
||||
processDesigner.value.refresh()
|
||||
} catch (error) {
|
||||
console.error('导入XML失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 在组件卸载时清理
|
||||
onBeforeUnmount(() => {
|
||||
isModelerReady.value = false
|
||||
modeler.value = null
|
||||
// 清理全局实例
|
||||
const w = window as any
|
||||
if (w.bpmnInstances) {
|
||||
w.bpmnInstances = null
|
||||
}
|
||||
})
|
||||
|
||||
/** 获取 XML 字符串 */
|
||||
const saveXML = async () => {
|
||||
if (!modeler.value) {
|
||||
return { xml: xmlString.value }
|
||||
}
|
||||
try {
|
||||
const result = await modeler.value.saveXML({ format: true })
|
||||
xmlString.value = result.xml
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('获取XML失败:', error)
|
||||
return { xml: xmlString.value }
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const modelId = query.modelId as unknown as number
|
||||
if (!modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
return
|
||||
/** 获取SVG字符串 */
|
||||
const saveSVG = async () => {
|
||||
if (!modeler.value) {
|
||||
return { svg: undefined }
|
||||
}
|
||||
// 查询模型
|
||||
const data = await ModelApi.getModel(modelId)
|
||||
if (!data.bpmnXml) {
|
||||
// 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
|
||||
data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
|
||||
<process id="${data.key}" name="${data.name}" isExecutable="true" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram">
|
||||
<bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>`
|
||||
try {
|
||||
return await modeler.value.saveSVG()
|
||||
} catch (error) {
|
||||
console.error('获取SVG失败:', error)
|
||||
return { svg: undefined }
|
||||
}
|
||||
model.value = {
|
||||
...data,
|
||||
bpmnXml: undefined // 清空 bpmnXml 属性
|
||||
}
|
||||
|
||||
/** 刷新视图 */
|
||||
const refresh = async () => {
|
||||
if (processDesigner.value?.refresh && modeler.value) {
|
||||
try {
|
||||
await modeler.value.importXML(xmlString.value)
|
||||
processDesigner.value.refresh()
|
||||
} catch (error) {
|
||||
console.error('刷新视图失败:', error)
|
||||
}
|
||||
}
|
||||
xmlString.value = data.bpmnXml
|
||||
}
|
||||
|
||||
// 暴露必要的属性和方法给父组件
|
||||
defineExpose({
|
||||
modeler,
|
||||
isModelerReady,
|
||||
saveXML,
|
||||
saveSVG,
|
||||
refresh
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.process-panel__container {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
right: 60px;
|
||||
top: 172px;
|
||||
right: 70px;
|
||||
}
|
||||
</style>
|
||||
|
||||
301
src/views/bpm/model/form/BasicInfo.vue
Normal file
301
src/views/bpm/model/form/BasicInfo.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
|
||||
<el-form-item label="流程标识" prop="key" class="mb-20px">
|
||||
<div class="flex items-center">
|
||||
<el-input
|
||||
class="!w-440px"
|
||||
v-model="modelData.key"
|
||||
:disabled="!!modelData.id"
|
||||
placeholder="请输入流标标识"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
:content="modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name" class="mb-20px">
|
||||
<el-input
|
||||
v-model="modelData.name"
|
||||
:disabled="!!modelData.id"
|
||||
clearable
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" class="mb-20px">
|
||||
<el-select
|
||||
class="!w-full"
|
||||
v-model="modelData.category"
|
||||
clearable
|
||||
placeholder="请选择流程分类"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程图标" prop="icon" class="mb-20px">
|
||||
<UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description" class="mb-20px">
|
||||
<el-input v-model="modelData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程类型" prop="type" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.type">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否可见" prop="visible" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.visible">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
|
||||
<el-select
|
||||
v-model="modelData.startUserType"
|
||||
placeholder="请选择谁可以发起"
|
||||
@change="handleStartUserTypeChange"
|
||||
>
|
||||
<el-option label="全员" :value="0" />
|
||||
<el-option label="指定人员" :value="1" />
|
||||
<el-option label="均不可提交" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedStartUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveStartUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openStartUserSelect">
|
||||
<Icon icon="ep:plus" />选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程管理员" prop="managerUserType" class="mb-20px">
|
||||
<el-select
|
||||
v-model="modelData.managerUserType"
|
||||
placeholder="请选择流程管理员"
|
||||
@change="handleManagerUserTypeChange"
|
||||
>
|
||||
<el-option label="全员" :value="0" />
|
||||
<el-option label="指定人员" :value="1" />
|
||||
<el-option label="均不可提交" :value="2" />
|
||||
</el-select>
|
||||
<div v-if="modelData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="user in selectedManagerUsers"
|
||||
:key="user.id"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
<Icon
|
||||
icon="ep:close"
|
||||
class="ml-2 cursor-pointer hover:text-red-500"
|
||||
@click="handleRemoveManagerUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openManagerUserSelect">
|
||||
<Icon icon="ep:plus" />选择人员
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { UserVO } from '@/api/system/user'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
categoryList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
userList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref()
|
||||
const selectedStartUsers = ref<UserVO[]>([])
|
||||
const selectedManagerUsers = ref<UserVO[]>([])
|
||||
const userSelectFormRef = ref()
|
||||
const currentSelectType = ref<'start' | 'manager'>('start')
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
||||
icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 初始化选中的用户
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal.startUserIds?.length) {
|
||||
selectedStartUsers.value = props.userList.filter((user: UserVO) =>
|
||||
newVal.startUserIds.includes(user.id)
|
||||
) as UserVO[]
|
||||
}
|
||||
if (newVal.managerUserIds?.length) {
|
||||
selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
|
||||
newVal.managerUserIds.includes(user.id)
|
||||
) as UserVO[]
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 打开发起人选择 */
|
||||
const openStartUserSelect = () => {
|
||||
currentSelectType.value = 'start'
|
||||
userSelectFormRef.value.open(0, selectedStartUsers.value)
|
||||
}
|
||||
|
||||
/** 打开管理员选择 */
|
||||
const openManagerUserSelect = () => {
|
||||
currentSelectType.value = 'manager'
|
||||
userSelectFormRef.value.open(0, selectedManagerUsers.value)
|
||||
}
|
||||
|
||||
/** 处理用户选择确认 */
|
||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
|
||||
if (currentSelectType.value === 'start') {
|
||||
selectedStartUsers.value = users
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
startUserIds: users.map((u) => u.id)
|
||||
})
|
||||
} else {
|
||||
selectedManagerUsers.value = users
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
managerUserIds: users.map((u) => u.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理发起人类型变化 */
|
||||
const handleStartUserTypeChange = (value: number) => {
|
||||
if (value !== 1) {
|
||||
selectedStartUsers.value = []
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
startUserIds: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理管理员类型变化 */
|
||||
const handleManagerUserTypeChange = (value: number) => {
|
||||
if (value !== 1) {
|
||||
selectedManagerUsers.value = []
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
managerUserIds: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除发起人 */
|
||||
const handleRemoveStartUser = (user: UserVO) => {
|
||||
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
|
||||
})
|
||||
}
|
||||
|
||||
/** 移除管理员 */
|
||||
const handleRemoveManagerUser = (user: UserVO) => {
|
||||
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
|
||||
emit('update:modelValue', {
|
||||
...modelData.value,
|
||||
managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
|
||||
})
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
await formRef.value?.validate()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-gray-100 {
|
||||
background-color: #f5f7fa;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6e8eb;
|
||||
}
|
||||
|
||||
.ep-close {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
137
src/views/bpm/model/form/FormDesign.vue
Normal file
137
src/views/bpm/model/form/FormDesign.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
|
||||
<el-form-item label="表单类型" prop="formType" class="mb-20px">
|
||||
<el-radio-group v-model="modelData.formType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
|
||||
<el-select v-model="modelData.formId" clearable style="width: 100%">
|
||||
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
|
||||
<el-input
|
||||
v-model="modelData.formCustomCreatePath"
|
||||
placeholder="请输入表单提交路由"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
|
||||
<el-input
|
||||
v-model="modelData.formCustomViewPath"
|
||||
placeholder="请输入表单查看的组件地址"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<!-- 表单预览 -->
|
||||
<div
|
||||
v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
|
||||
class="mt-20px"
|
||||
>
|
||||
<div class="flex items-center mb-15px">
|
||||
<div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
|
||||
<span class="text-15px font-bold">表单预览</span>
|
||||
</div>
|
||||
<form-create
|
||||
v-model="formPreview.formData"
|
||||
:rule="formPreview.rule"
|
||||
:option="formPreview.option"
|
||||
/>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
formList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref()
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 表单预览数据
|
||||
const formPreview = ref({
|
||||
formData: {},
|
||||
rule: [],
|
||||
option: {
|
||||
submitBtn: false,
|
||||
resetBtn: false,
|
||||
formData: {}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听表单ID变化,加载表单数据
|
||||
watch(
|
||||
() => modelData.value.formId,
|
||||
async (newFormId) => {
|
||||
if (newFormId && modelData.value.formType === 10) {
|
||||
const data = await FormApi.getForm(newFormId)
|
||||
setConfAndFields2(formPreview.value, data.conf, data.fields)
|
||||
// 设置只读
|
||||
formPreview.value.rule.forEach((item: any) => {
|
||||
item.props = { ...item.props, disabled: true }
|
||||
})
|
||||
} else {
|
||||
formPreview.value.rule = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const rules = {
|
||||
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
|
||||
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
|
||||
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
|
||||
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
await formRef.value?.validate()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
235
src/views/bpm/model/form/ProcessDesign.vue
Normal file
235
src/views/bpm/model/form/ProcessDesign.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<!-- BPMN设计器 -->
|
||||
<template v-if="modelData.type === BpmModelType.BPMN">
|
||||
<BpmModelEditor
|
||||
v-if="showDesigner"
|
||||
:model-id="modelData.id"
|
||||
:model-key="modelData.key"
|
||||
:model-name="modelData.name"
|
||||
:value="currentBpmnXml"
|
||||
ref="bpmnEditorRef"
|
||||
@success="handleDesignSuccess"
|
||||
@init-finished="handleEditorInit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Simple设计器 -->
|
||||
<template v-else>
|
||||
<SimpleModelDesign
|
||||
v-if="showDesigner"
|
||||
:model-id="modelData.id"
|
||||
:model-key="modelData.key"
|
||||
:model-name="modelData.name"
|
||||
:start-user-ids="modelData.startUserIds"
|
||||
:value="currentSimpleModel"
|
||||
ref="simpleEditorRef"
|
||||
@success="handleDesignSuccess"
|
||||
@init-finished="handleEditorInit"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import BpmModelEditor from '../editor/index.vue'
|
||||
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const bpmnEditorRef = ref()
|
||||
const simpleEditorRef = ref()
|
||||
const isEditorInitialized = ref(false)
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 保存当前的流程XML或数据
|
||||
const currentBpmnXml = ref('')
|
||||
const currentSimpleModel = ref('')
|
||||
|
||||
// 初始化或更新当前的XML数据
|
||||
const initOrUpdateXmlData = () => {
|
||||
if (modelData.value) {
|
||||
if (modelData.value.type === BpmModelType.BPMN) {
|
||||
currentBpmnXml.value = modelData.value.bpmnXml || ''
|
||||
} else {
|
||||
currentSimpleModel.value = modelData.value.simpleModel || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听modelValue的变化,更新数据
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
if (newVal.type === BpmModelType.BPMN) {
|
||||
if (newVal.bpmnXml && newVal.bpmnXml !== currentBpmnXml.value) {
|
||||
currentBpmnXml.value = newVal.bpmnXml
|
||||
// 如果编辑器已经初始化,刷新视图
|
||||
if (isEditorInitialized.value && bpmnEditorRef.value?.refresh) {
|
||||
nextTick(() => {
|
||||
bpmnEditorRef.value.refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newVal.simpleModel && newVal.simpleModel !== currentSimpleModel.value) {
|
||||
currentSimpleModel.value = newVal.simpleModel
|
||||
// 如果编辑器已经初始化,刷新视图
|
||||
if (isEditorInitialized.value && simpleEditorRef.value?.refresh) {
|
||||
nextTick(() => {
|
||||
simpleEditorRef.value.refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
/** 编辑器初始化完成的回调 */
|
||||
const handleEditorInit = async () => {
|
||||
isEditorInitialized.value = true
|
||||
|
||||
// 等待下一个tick,确保编辑器已经准备好
|
||||
await nextTick()
|
||||
|
||||
// 初始化完成后,设置初始值
|
||||
if (modelData.value.type === BpmModelType.BPMN) {
|
||||
if (modelData.value.bpmnXml) {
|
||||
currentBpmnXml.value = modelData.value.bpmnXml
|
||||
if (bpmnEditorRef.value?.refresh) {
|
||||
await nextTick()
|
||||
bpmnEditorRef.value.refresh()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (modelData.value.simpleModel) {
|
||||
currentSimpleModel.value = modelData.value.simpleModel
|
||||
if (simpleEditorRef.value?.refresh) {
|
||||
await nextTick()
|
||||
simpleEditorRef.value.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前流程数据 */
|
||||
const getProcessData = async () => {
|
||||
try {
|
||||
if (modelData.value.type === BpmModelType.BPMN) {
|
||||
if (!bpmnEditorRef.value || !isEditorInitialized.value) {
|
||||
return currentBpmnXml.value || undefined
|
||||
}
|
||||
const { xml } = await bpmnEditorRef.value.saveXML()
|
||||
if (xml) {
|
||||
currentBpmnXml.value = xml
|
||||
return xml
|
||||
}
|
||||
} else {
|
||||
if (!simpleEditorRef.value || !isEditorInitialized.value) {
|
||||
return currentSimpleModel.value || undefined
|
||||
}
|
||||
const flowData = await simpleEditorRef.value.getCurrentFlowData()
|
||||
if (flowData) {
|
||||
currentSimpleModel.value = flowData
|
||||
return flowData
|
||||
}
|
||||
}
|
||||
return modelData.value.type === BpmModelType.BPMN
|
||||
? currentBpmnXml.value
|
||||
: currentSimpleModel.value
|
||||
} catch (error) {
|
||||
console.error('获取流程数据失败:', error)
|
||||
return modelData.value.type === BpmModelType.BPMN
|
||||
? currentBpmnXml.value
|
||||
: currentSimpleModel.value
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
try {
|
||||
// 获取最新的流程数据
|
||||
const processData = await getProcessData()
|
||||
if (!processData) {
|
||||
throw new Error('请设计流程')
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理设计器保存成功 */
|
||||
const handleDesignSuccess = async (data?: any) => {
|
||||
if (data) {
|
||||
if (modelData.value.type === BpmModelType.BPMN) {
|
||||
currentBpmnXml.value = data
|
||||
} else {
|
||||
currentSimpleModel.value = data
|
||||
}
|
||||
|
||||
// 创建新的对象以触发响应式更新
|
||||
const newModelData = {
|
||||
...modelData.value,
|
||||
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
|
||||
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
|
||||
}
|
||||
|
||||
// 使用emit更新父组件的数据
|
||||
await nextTick()
|
||||
emit('update:modelValue', newModelData)
|
||||
emit('success', data)
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否显示设计器 */
|
||||
const showDesigner = computed(() => {
|
||||
return Boolean(modelData.value?.key && modelData.value?.name)
|
||||
})
|
||||
|
||||
// 组件创建时初始化数据
|
||||
onMounted(() => {
|
||||
initOrUpdateXmlData()
|
||||
})
|
||||
|
||||
// 组件卸载前保存数据
|
||||
onBeforeUnmount(async () => {
|
||||
try {
|
||||
// 获取并保存最新的流程数据
|
||||
const data = await getProcessData()
|
||||
if (data) {
|
||||
// 创建新的对象以触发响应式更新
|
||||
const newModelData = {
|
||||
...modelData.value,
|
||||
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
|
||||
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
|
||||
}
|
||||
|
||||
// 使用emit更新父组件的数据
|
||||
await nextTick()
|
||||
emit('update:modelValue', newModelData)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存数据失败:', error)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
getProcessData
|
||||
})
|
||||
</script>
|
||||
439
src/views/bpm/model/form/index.vue
Normal file
439
src/views/bpm/model/form/index.vue
Normal file
@@ -0,0 +1,439 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="mx-auto">
|
||||
<!-- 头部导航栏 -->
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
|
||||
>
|
||||
<!-- 左侧标题 -->
|
||||
<div class="w-200px flex items-center overflow-hidden">
|
||||
<Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
|
||||
<span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
|
||||
{{ formData.name || '创建流程' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<div class="flex-1 flex items-center justify-center h-full">
|
||||
<div class="w-400px flex items-center justify-between h-full">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="flex items-center cursor-pointer mx-15px relative h-full"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
|
||||
: 'text-gray-500'
|
||||
]"
|
||||
@click="handleStepClick(index)"
|
||||
>
|
||||
<div
|
||||
class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'bg-[#3473ff] text-white border-[#3473ff]'
|
||||
: 'border-gray-300 bg-white text-gray-500'
|
||||
]"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<div class="w-200px flex items-center justify-end gap-2">
|
||||
<el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
|
||||
<el-button type="primary" @click="handleSave">保 存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="mt-50px">
|
||||
<!-- 第一步:基本信息 -->
|
||||
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
||||
<BasicInfo
|
||||
v-model="formData"
|
||||
:categoryList="categoryList"
|
||||
:userList="userList"
|
||||
ref="basicInfoRef"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:表单设计 -->
|
||||
<div v-if="currentStep === 1" class="mx-auto w-560px">
|
||||
<FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
|
||||
</div>
|
||||
|
||||
<!-- 第三步:流程设计 -->
|
||||
<ProcessDesign
|
||||
v-if="currentStep === 2"
|
||||
v-model="formData"
|
||||
ref="processDesignRef"
|
||||
@success="handleDesignSuccess"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
|
||||
import BasicInfo from './BasicInfo.vue'
|
||||
import FormDesign from './FormDesign.vue'
|
||||
import ProcessDesign from './ProcessDesign.vue'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
|
||||
const router = useRouter()
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const userStore = useUserStoreWithOut()
|
||||
|
||||
// 组件引用
|
||||
const basicInfoRef = ref()
|
||||
const formDesignRef = ref()
|
||||
const processDesignRef = ref()
|
||||
|
||||
/** 步骤校验函数 */
|
||||
const validateBasic = async () => {
|
||||
await basicInfoRef.value?.validate()
|
||||
}
|
||||
|
||||
/** 表单设计校验 */
|
||||
const validateForm = async () => {
|
||||
await formDesignRef.value?.validate()
|
||||
}
|
||||
|
||||
/** 流程设计校验 */
|
||||
const validateProcess = async () => {
|
||||
await processDesignRef.value?.validate()
|
||||
}
|
||||
|
||||
const currentStep = ref(0) // 步骤控制
|
||||
const steps = [
|
||||
{ title: '基本信息', validator: validateBasic },
|
||||
{ title: '表单设计', validator: validateForm },
|
||||
{ title: '流程设计', validator: validateProcess }
|
||||
]
|
||||
|
||||
// 表单数据
|
||||
const formData: any = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
key: '',
|
||||
category: undefined,
|
||||
icon: undefined,
|
||||
description: '',
|
||||
type: BpmModelType.BPMN,
|
||||
formType: BpmModelFormType.NORMAL,
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: '',
|
||||
visible: true,
|
||||
startUserType: undefined,
|
||||
managerUserType: undefined,
|
||||
startUserIds: [],
|
||||
managerUserIds: []
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const formList = ref([])
|
||||
const categoryList = ref([])
|
||||
const userList = ref<UserApi.UserVO[]>([])
|
||||
|
||||
/** 初始化数据 */
|
||||
const initData = async () => {
|
||||
const modelId = route.params.id as string
|
||||
if (modelId) {
|
||||
// 修改场景
|
||||
formData.value = await ModelApi.getModel(modelId)
|
||||
} else {
|
||||
// 新增场景
|
||||
formData.value.managerUserIds.push(userStore.getUser.id)
|
||||
}
|
||||
|
||||
// 获取表单列表
|
||||
formList.value = await FormApi.getFormSimpleList()
|
||||
// 获取分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
|
||||
/** 校验所有步骤数据是否完整 */
|
||||
const validateAllSteps = async () => {
|
||||
try {
|
||||
// 基本信息校验
|
||||
await basicInfoRef.value?.validate()
|
||||
if (!formData.value.key || !formData.value.name || !formData.value.category) {
|
||||
currentStep.value = 0
|
||||
throw new Error('请完善基本信息')
|
||||
}
|
||||
|
||||
// 表单设计校验
|
||||
await formDesignRef.value?.validate()
|
||||
if (formData.value.formType === 10 && !formData.value.formId) {
|
||||
currentStep.value = 1
|
||||
throw new Error('请选择流程表单')
|
||||
}
|
||||
if (
|
||||
formData.value.formType === 20 &&
|
||||
(!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
|
||||
) {
|
||||
currentStep.value = 1
|
||||
throw new Error('请完善自定义表单信息')
|
||||
}
|
||||
|
||||
// 流程设计校验
|
||||
// 如果已经有流程数据,则不需要重新校验
|
||||
if (!formData.value.bpmnXml && !formData.value.simpleModel) {
|
||||
// 如果当前不在第三步,需要先保存当前步骤数据
|
||||
if (currentStep.value !== 2) {
|
||||
await steps[currentStep.value].validator()
|
||||
// 切换到第三步
|
||||
currentStep.value = 2
|
||||
// 等待组件渲染完成
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
// 校验流程设计
|
||||
await processDesignRef.value?.validate()
|
||||
const processData = await processDesignRef.value?.getProcessData()
|
||||
if (!processData) {
|
||||
throw new Error('请设计流程')
|
||||
}
|
||||
|
||||
// 保存流程数据
|
||||
if (formData.value.type === BpmModelType.BPMN) {
|
||||
formData.value.bpmnXml = processData
|
||||
formData.value.simpleModel = null
|
||||
} else {
|
||||
formData.value.bpmnXml = null
|
||||
formData.value.simpleModel = processData
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存操作 */
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 保存前校验所有步骤的数据
|
||||
await validateAllSteps()
|
||||
|
||||
// 更新表单数据
|
||||
const modelData = {
|
||||
...formData.value
|
||||
}
|
||||
|
||||
// 如果当前在第三步,获取最新的流程设计数据
|
||||
if (currentStep.value === 2) {
|
||||
const processData = await processDesignRef.value?.getProcessData()
|
||||
if (processData) {
|
||||
if (formData.value.type === BpmModelType.BPMN) {
|
||||
modelData.bpmnXml = processData
|
||||
modelData.simpleModel = null
|
||||
} else {
|
||||
modelData.bpmnXml = null
|
||||
modelData.simpleModel = processData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (formData.value.id) {
|
||||
// 修改场景
|
||||
await ModelApi.updateModel(modelData)
|
||||
// 询问是否发布流程
|
||||
try {
|
||||
await message.confirm('修改流程成功,是否发布流程?')
|
||||
// 用户点击确认,执行发布
|
||||
await handleDeploy()
|
||||
} catch {
|
||||
// 用户点击取消,停留在当前页面
|
||||
}
|
||||
} else {
|
||||
// 新增场景
|
||||
formData.value.id = await ModelApi.createModel(modelData)
|
||||
message.success('新增成功')
|
||||
try {
|
||||
await message.confirm('创建流程成功,是否继续编辑?')
|
||||
// 用户点击继续编辑,跳转到编辑页面
|
||||
await nextTick()
|
||||
// 先删除当前页签
|
||||
delView(unref(router.currentRoute))
|
||||
// 跳转到编辑页面
|
||||
await router.push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id: formData.value.id }
|
||||
})
|
||||
} catch {
|
||||
// 先删除当前页签
|
||||
delView(unref(router.currentRoute))
|
||||
// 用户点击返回列表
|
||||
await router.push({ name: 'BpmModel' })
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error)
|
||||
message.warning(error.message || '请完善所有步骤的必填信息')
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布操作 */
|
||||
const handleDeploy = async () => {
|
||||
try {
|
||||
// 修改场景下直接发布,新增场景下需要先确认
|
||||
if (!formData.value.id) {
|
||||
await message.confirm('是否确认发布该流程?')
|
||||
}
|
||||
|
||||
// 校验所有步骤
|
||||
await validateAllSteps()
|
||||
|
||||
// 更新表单数据
|
||||
const modelData = {
|
||||
...formData.value
|
||||
}
|
||||
|
||||
// 如果当前在第三步,获取最新的流程设计数据
|
||||
if (currentStep.value === 2) {
|
||||
const processData = await processDesignRef.value?.getProcessData()
|
||||
if (processData) {
|
||||
if (formData.value.type === BpmModelType.BPMN) {
|
||||
modelData.bpmnXml = processData
|
||||
modelData.simpleModel = null
|
||||
} else {
|
||||
modelData.bpmnXml = null
|
||||
modelData.simpleModel = processData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 先保存所有数据
|
||||
if (formData.value.id) {
|
||||
await ModelApi.updateModel(modelData)
|
||||
} else {
|
||||
const result = await ModelApi.createModel(modelData)
|
||||
formData.value.id = result.id
|
||||
}
|
||||
|
||||
// 发布
|
||||
await ModelApi.deployModel(formData.value.id)
|
||||
message.success('发布成功')
|
||||
// 返回列表页
|
||||
await router.push({ name: 'BpmModel' })
|
||||
} catch (error: any) {
|
||||
console.error('发布失败:', error)
|
||||
message.warning(error.message || '发布失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 步骤切换处理 */
|
||||
const handleStepClick = async (index: number) => {
|
||||
try {
|
||||
// 如果是切换到第三步(流程设计),需要校验key和name
|
||||
if (index === 2) {
|
||||
if (!formData.value.key || !formData.value.name) {
|
||||
message.warning('请先填写流程标识和流程名称')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前步骤的数据
|
||||
if (currentStep.value === 2) {
|
||||
const processData = await processDesignRef.value?.getProcessData()
|
||||
if (processData) {
|
||||
if (formData.value.type === BpmModelType.BPMN) {
|
||||
formData.value.bpmnXml = processData
|
||||
formData.value.simpleModel = null
|
||||
} else {
|
||||
formData.value.bpmnXml = null
|
||||
formData.value.simpleModel = processData
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 只有在向后切换时才进行校验
|
||||
if (index > currentStep.value) {
|
||||
if (typeof steps[currentStep.value].validator === 'function') {
|
||||
await steps[currentStep.value].validator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换步骤
|
||||
currentStep.value = index
|
||||
|
||||
// 如果切换到流程设计步骤,等待组件渲染完成后刷新设计器
|
||||
if (index === 2) {
|
||||
await nextTick()
|
||||
// 等待更长时间确保组件完全初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
if (processDesignRef.value?.refresh) {
|
||||
await processDesignRef.value.refresh()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('步骤切换失败:', error)
|
||||
message.warning('请先完善当前步骤必填信息')
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理设计器保存成功 */
|
||||
const handleDesignSuccess = (bpmnXml?: string) => {
|
||||
if (bpmnXml) {
|
||||
formData.value.bpmnXml = bpmnXml
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回列表页 */
|
||||
const handleBack = () => {
|
||||
// 先删除当前页签
|
||||
delView(unref(router.currentRoute))
|
||||
// 跳转到列表页
|
||||
router.push({ name: 'BpmModel' })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await initData()
|
||||
})
|
||||
|
||||
// 添加组件卸载前的清理代码
|
||||
onBeforeUnmount(() => {
|
||||
// 清理所有的引用
|
||||
basicInfoRef.value = null
|
||||
formDesignRef.value = null
|
||||
processDesignRef.value = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #3473ff;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: #3473ff;
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border-color: #3473ff;
|
||||
}
|
||||
</style>
|
||||
@@ -106,6 +106,7 @@ import CategoryDraggableModel from './CategoryDraggableModel.vue'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const { push } = useRouter()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const isCategorySorting = ref(false) // 是否 category 正处于排序状态
|
||||
@@ -124,7 +125,14 @@ const handleQuery = () => {
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
if (type === 'create') {
|
||||
push({ name: 'BpmModelCreate' })
|
||||
} else {
|
||||
push({
|
||||
name: 'BpmModelUpdate',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
<template>
|
||||
<doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
|
||||
<doc-alert
|
||||
title="流程设计器(钉钉、飞书)"
|
||||
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
|
||||
/>
|
||||
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
|
||||
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="请输入流程标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:model:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
|
||||
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.icon" class="h-32px w-32px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||
全部可见
|
||||
</el-text>
|
||||
<el-text v-else-if="scope.row.startUsers.length == 1">
|
||||
{{ scope.row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === 20"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.processDefinition">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
<el-tag
|
||||
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||
type="warning"
|
||||
class="ml-10px"
|
||||
>
|
||||
已停用
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="!align-middle ml-5px"
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
||||
>
|
||||
<el-button type="primary" link>更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
command="handleDefinitionList"
|
||||
v-if="checkPermi(['bpm:process-definition:query'])"
|
||||
>
|
||||
历史
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleChangeState"
|
||||
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleDelete"
|
||||
v-if="checkPermi(['bpm:model:delete'])"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改流程 -->
|
||||
<ModelForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: undefined,
|
||||
name: undefined,
|
||||
category: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ModelApi.getModelList(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleDefinitionList':
|
||||
handleDefinitionList(row)
|
||||
break
|
||||
case 'handleDelete':
|
||||
handleDelete(row)
|
||||
break
|
||||
case 'handleChangeState':
|
||||
handleChangeState(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(row.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row: any) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
const newState = state === 1 ? 2 : 1
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
debugger
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, newState)
|
||||
message.success(statusState + '成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row: any) => {
|
||||
if (row.type == BpmModelType.BPMN) {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
} else {
|
||||
push({
|
||||
name: 'SimpleModelDesign',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是否可以操作 */
|
||||
const isManagerUser = (row: any) => {
|
||||
const userId = userStore.getUser.id
|
||||
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="表单填写" name="form">
|
||||
<div class="form-scroll-area">
|
||||
<div class="form-scroll-area" v-loading="processInstanceStartLoading">
|
||||
<el-scrollbar>
|
||||
<el-row>
|
||||
<el-col :span="17">
|
||||
@@ -75,7 +75,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import {
|
||||
CandidateStrategy,
|
||||
NodeId,
|
||||
FieldPermissionType
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
|
||||
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
|
||||
@@ -90,7 +94,7 @@ const props = defineProps<{
|
||||
selectProcessDefinition: any
|
||||
}>()
|
||||
const emit = defineEmits(['cancel'])
|
||||
|
||||
const processInstanceStartLoading = ref(false) // 流程实例发起中
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
@@ -129,8 +133,10 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
|
||||
}
|
||||
}
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
|
||||
await nextTick()
|
||||
fApi.value?.btn.show(false) // 隐藏提交按钮
|
||||
|
||||
// 获取流程审批信息
|
||||
await getApprovalDetail(row)
|
||||
|
||||
@@ -152,7 +158,12 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
|
||||
/** 获取审批详情 */
|
||||
const getApprovalDetail = async (row: any) => {
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getApprovalDetail({ processDefinitionId: row.id })
|
||||
// TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效)
|
||||
const data = await ProcessInstanceApi.getApprovalDetail({
|
||||
processDefinitionId: row.id,
|
||||
activityId: NodeId.START_USER_NODE_ID
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
message.error('查询不到审批详情信息!')
|
||||
return
|
||||
@@ -170,15 +181,43 @@ const getApprovalDetail = async (row: any) => {
|
||||
|
||||
// 获取审批节点,显示 Timeline 的数据
|
||||
activityNodes.value = data.activityNodes
|
||||
// 获取表单字段权限
|
||||
const formFieldsPermission = data.formFieldsPermission
|
||||
// 设置表单字段权限
|
||||
if (formFieldsPermission) {
|
||||
Object.keys(formFieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, formFieldsPermission[item])
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单权限
|
||||
*/
|
||||
const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === FieldPermissionType.READ) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(false, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.hidden(true, field)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
if (!fApi.value || !props.selectProcessDefinition) {
|
||||
return
|
||||
}
|
||||
// 流程表单校验
|
||||
await fApi.value.validate()
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
@@ -191,7 +230,7 @@ const submitForm = async () => {
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
processInstanceStartLoading.value = true
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: props.selectProcessDefinition.id,
|
||||
@@ -206,7 +245,7 @@ const submitForm = async () => {
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
processInstanceStartLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="approveFormRef"
|
||||
:model="approveReasonForm"
|
||||
:rules="approveReasonRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
|
||||
@@ -38,17 +38,17 @@
|
||||
</el-card>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="approveReasonForm.reason"
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
|
||||
<el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
|
||||
{{ getButtonDisplayName(OperationButtonType.APPROVE) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.approve = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -72,35 +72,24 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="rejectFormRef"
|
||||
:model="rejectReasonForm"
|
||||
:rules="rejectReasonRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForm.value"
|
||||
v-model:api="approveFormFApi"
|
||||
:option="approveForm.option"
|
||||
:rule="approveForm.rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="rejectReasonForm.reason"
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
|
||||
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
|
||||
{{ getButtonDisplayName(OperationButtonType.REJECT) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.reject = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -124,14 +113,14 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="copyFormRef"
|
||||
:model="copyForm"
|
||||
:rules="copyFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select
|
||||
v-model="genericForm.copyUserIds"
|
||||
v-model="copyForm.copyUserIds"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
multiple
|
||||
@@ -147,7 +136,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送意见" prop="copyReason">
|
||||
<el-input
|
||||
v-model="genericForm.copyReason"
|
||||
v-model="copyForm.copyReason"
|
||||
clearable
|
||||
placeholder="请输入抄送意见"
|
||||
type="textarea"
|
||||
@@ -158,13 +147,13 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handleCopy">
|
||||
{{ getButtonDisplayName(OperationButtonType.COPY) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.copy = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('copy', copyFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<!-- 【转交】按钮 -->
|
||||
<!-- 【转办】按钮 -->
|
||||
<el-popover
|
||||
:visible="popOverVisible.transfer"
|
||||
placement="top-start"
|
||||
@@ -182,13 +171,13 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="transferFormRef"
|
||||
:model="transferForm"
|
||||
:rules="transferFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="新审批人" prop="assigneeUserId">
|
||||
<el-select v-model="genericForm.assigneeUserId" clearable style="width: 100%">
|
||||
<el-select v-model="transferForm.assigneeUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
@@ -199,7 +188,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="transferForm.reason"
|
||||
clearable
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
@@ -210,7 +199,7 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
|
||||
{{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.transfer = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('transfer', transferFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -234,13 +223,13 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="delegateFormRef"
|
||||
:model="delegateForm"
|
||||
:rules="delegateFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="接收人" prop="delegateUserId">
|
||||
<el-select v-model="genericForm.delegateUserId" clearable style="width: 100%">
|
||||
<el-select v-model="delegateForm.delegateUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
@@ -251,7 +240,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="delegateForm.reason"
|
||||
clearable
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
@@ -262,7 +251,7 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
|
||||
{{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.delegate = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('delegate', delegateFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -286,13 +275,13 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="addSignFormRef"
|
||||
:model="addSignForm"
|
||||
:rules="addSignFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="加签处理人" prop="addSignUserIds">
|
||||
<el-select v-model="genericForm.addSignUserIds" multiple clearable style="width: 100%">
|
||||
<el-select v-model="addSignForm.addSignUserIds" multiple clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
@@ -303,7 +292,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="addSignForm.reason"
|
||||
clearable
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
@@ -317,7 +306,7 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
|
||||
向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.addSign = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('addSign', addSignFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -340,13 +329,13 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="deleteSignFormRef"
|
||||
:model="deleteSignForm"
|
||||
:rules="deleteSignFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="减签人员" prop="deleteSignTaskId">
|
||||
<el-select v-model="genericForm.deleteSignTaskId" clearable style="width: 100%">
|
||||
<el-select v-model="deleteSignForm.deleteSignTaskId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in runningTask.children"
|
||||
:key="item.id"
|
||||
@@ -357,7 +346,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="reason">
|
||||
<el-input
|
||||
v-model="genericForm.reason"
|
||||
v-model="deleteSignForm.reason"
|
||||
clearable
|
||||
placeholder="请输入审批意见"
|
||||
type="textarea"
|
||||
@@ -368,7 +357,7 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
|
||||
减签
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.deleteSign = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -383,7 +372,7 @@
|
||||
v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
|
||||
>
|
||||
<template #reference>
|
||||
<div @click="openReturnPopover" class="hover-bg-gray-100 rounded-xl p-6px">
|
||||
<div @click="openPopover('return')" class="hover-bg-gray-100 rounded-xl p-6px">
|
||||
<Icon :size="14" icon="ep:back" />
|
||||
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
|
||||
</div>
|
||||
@@ -392,13 +381,13 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="returnFormRef"
|
||||
:model="returnForm"
|
||||
:rules="returnFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
|
||||
<el-select v-model="genericForm.targetTaskDefinitionKey" clearable style="width: 100%">
|
||||
<el-select v-model="returnForm.targetTaskDefinitionKey" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnList"
|
||||
:key="item.taskDefinitionKey"
|
||||
@@ -409,7 +398,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="退回理由" prop="returnReason">
|
||||
<el-input
|
||||
v-model="genericForm.returnReason"
|
||||
v-model="returnForm.returnReason"
|
||||
clearable
|
||||
placeholder="请输入退回理由"
|
||||
type="textarea"
|
||||
@@ -420,7 +409,7 @@
|
||||
<el-button :disabled="formLoading" type="primary" @click="handleReturn()">
|
||||
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.return = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('return', returnFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -445,15 +434,15 @@
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="genericForm"
|
||||
:rules="genericRule"
|
||||
ref="cancelFormRef"
|
||||
:model="cancelForm"
|
||||
:rules="cancelFormRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="取消理由" prop="cancelReason">
|
||||
<span class="text-#878c93 text-12px"> 取消后,该审批流程将自动结束</span>
|
||||
<el-input
|
||||
v-model="genericForm.cancelReason"
|
||||
v-model="cancelForm.cancelReason"
|
||||
clearable
|
||||
placeholder="请输入取消理由"
|
||||
type="textarea"
|
||||
@@ -462,9 +451,9 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :disabled="formLoading" type="primary" @click="handleCancel()">
|
||||
取消
|
||||
确认
|
||||
</el-button>
|
||||
<el-button @click="popOverVisible.cancel = false"> 取消 </el-button>
|
||||
<el-button @click="closePropover('cancel', cancelFormRef)"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -488,26 +477,29 @@ import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import {
|
||||
OperationButtonType,
|
||||
OPERATION_BUTTON_NAME
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { BpmProcessInstanceStatus } from '@/utils/constants'
|
||||
|
||||
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
defineOptions({ name: 'ProcessInstanceBtnContainer' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const props = defineProps({
|
||||
processInstance: propTypes.object, // 流程实例信息
|
||||
processDefinition: propTypes.object, // 流程定义信息
|
||||
userOptions: propTypes.any
|
||||
})
|
||||
|
||||
const props = defineProps< {
|
||||
processInstance: any, // 流程实例信息
|
||||
processDefinition: any, // 流程定义信息
|
||||
userOptions: UserApi.UserVO[],
|
||||
normalForm: any, // 流程表单 formCreate
|
||||
normalFormApi: any, // 流程表单 formCreate Api
|
||||
writableFields: string[] // 流程表单可以编辑的字段
|
||||
}>()
|
||||
|
||||
const formLoading = ref(false) // 表单加载中
|
||||
const popOverVisible = ref({
|
||||
@@ -525,21 +517,99 @@ const returnList = ref([] as any) // 退回节点
|
||||
|
||||
// ========== 审批信息 ==========
|
||||
const runningTask = ref<any>() // 运行中的任务
|
||||
const genericForm = ref<any>({}) // 通用表单
|
||||
const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
|
||||
const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
|
||||
const formRef = ref()
|
||||
const genericRule = reactive({
|
||||
|
||||
// 审批通过意见表单
|
||||
const approveFormRef = ref<FormInstance>()
|
||||
const approveReasonForm = reactive({
|
||||
reason: ''
|
||||
})
|
||||
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }],
|
||||
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
|
||||
copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }],
|
||||
})
|
||||
// 拒绝表单
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
const rejectReasonForm = reactive({
|
||||
reason: ''
|
||||
})
|
||||
const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
// 抄送表单
|
||||
const copyFormRef = ref<FormInstance>()
|
||||
const copyForm = reactive({
|
||||
copyUserIds: [],
|
||||
copyReason: ''
|
||||
})
|
||||
const copyFormRule = reactive<FormRules<typeof copyForm>>({
|
||||
copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// 转办表单
|
||||
const transferFormRef = ref<FormInstance>()
|
||||
const transferForm = reactive({
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const transferFormRule = reactive<FormRules<typeof transferForm>>({
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
// 委派表单
|
||||
const delegateFormRef = ref<FormInstance>()
|
||||
const delegateForm = reactive({
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
// 加签表单
|
||||
const addSignFormRef = ref<FormInstance>()
|
||||
const addSignForm = reactive({
|
||||
addSignUserIds: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
|
||||
addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
// 减签表单
|
||||
const deleteSignFormRef = ref<FormInstance>()
|
||||
const deleteSignForm = reactive({
|
||||
deleteSignTaskId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
|
||||
deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
|
||||
targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }]
|
||||
}) // 表单校验规则
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
// 退回表单
|
||||
const returnFormRef = ref<FormInstance>()
|
||||
const returnForm = reactive({
|
||||
targetTaskDefinitionKey: undefined,
|
||||
returnReason: ''
|
||||
})
|
||||
const returnFormRule = reactive<FormRules<typeof returnForm>>({
|
||||
targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }],
|
||||
returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 取消表单
|
||||
const cancelFormRef = ref<FormInstance>()
|
||||
const cancelForm = reactive({
|
||||
cancelReason: ''
|
||||
})
|
||||
const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
|
||||
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||
watch(
|
||||
@@ -553,43 +623,57 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
/** 弹出退回气泡卡 */
|
||||
const openReturnPopover = async () => {
|
||||
returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可退回的节点')
|
||||
return
|
||||
}
|
||||
await openPopover('return')
|
||||
}
|
||||
|
||||
/** 弹出气泡卡 */
|
||||
const openPopover = async (type: string) => {
|
||||
if (type === 'approve') {
|
||||
// 校验流程表单
|
||||
const valid = await validateNormalForm();
|
||||
if (!valid) {
|
||||
message.warning('表单校验不通过,请先完善表单!!')
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (type === 'return') {
|
||||
// 获取退回节点
|
||||
returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可退回的节点')
|
||||
return
|
||||
}
|
||||
}
|
||||
Object.keys(popOverVisible.value).forEach((item) => {
|
||||
popOverVisible.value[item] = item === type
|
||||
})
|
||||
await nextTick()
|
||||
formRef.value.resetFields()
|
||||
// await nextTick()
|
||||
// formRef.value.resetFields()
|
||||
}
|
||||
|
||||
/** 关闭气泡卡 */
|
||||
const closePropover = (type: string, formRef: FormInstance | undefined) => {
|
||||
if (formRef) {
|
||||
formRef.resetFields()
|
||||
}
|
||||
popOverVisible.value[type] = false
|
||||
}
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (pass: boolean) => {
|
||||
const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const genericFormRef = proxy.$refs['formRef']
|
||||
// 1.2 校验表单
|
||||
const elForm = unref(genericFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: genericForm.value.reason
|
||||
}
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
await formRef.validate()
|
||||
if (pass) {
|
||||
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
// 获取修改的流程变量, 暂时只支持流程表单
|
||||
const variables = getUpdatedProcessInstanceVaiables();
|
||||
// 审批通过数据
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: approveReasonForm.reason,
|
||||
variables // 审批通过, 把修改的字段值赋于流程实例变量
|
||||
}
|
||||
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
||||
const formCreateApi = approveFormFApi.value
|
||||
if (Object.keys(formCreateApi)?.length > 0) {
|
||||
await formCreateApi.validate()
|
||||
@@ -600,11 +684,18 @@ const handleAudit = async (pass: boolean) => {
|
||||
popOverVisible.value.approve = false
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
// 审批不通过数据
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: rejectReasonForm.reason,
|
||||
}
|
||||
await TaskApi.rejectTask(data)
|
||||
popOverVisible.value.reject = false
|
||||
message.success('审批不通过成功')
|
||||
}
|
||||
// 2.2 加载最新数据
|
||||
// 重置表单
|
||||
formRef.resetFields()
|
||||
// 加载最新数据
|
||||
reload()
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
@@ -615,19 +706,17 @@ const handleAudit = async (pass: boolean) => {
|
||||
const handleCopy = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const copyFormRef = proxy.$refs['formRef']
|
||||
// 1. 校验表单
|
||||
const elForm = unref(copyFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!copyFormRef.value) return
|
||||
await copyFormRef.value.validate()
|
||||
// 2. 提交抄送
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: genericForm.value.copyReason,
|
||||
copyUserIds: genericForm.value.copyUserIds
|
||||
reason: copyForm.copyReason,
|
||||
copyUserIds:copyForm.copyUserIds
|
||||
}
|
||||
await TaskApi.copyTask(data)
|
||||
copyFormRef.value.resetFields()
|
||||
popOverVisible.value.copy = false
|
||||
message.success('操作成功')
|
||||
} finally {
|
||||
@@ -639,20 +728,17 @@ const handleCopy = async () => {
|
||||
const handleTransfer = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const transferFormRef = proxy.$refs['formRef']
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(transferFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!transferFormRef.value) return
|
||||
await transferFormRef.value.validate()
|
||||
// 1.2 提交转交
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: genericForm.value.reason,
|
||||
assigneeUserId: genericForm.value.assigneeUserId
|
||||
reason: transferForm.reason,
|
||||
assigneeUserId: transferForm.assigneeUserId
|
||||
}
|
||||
|
||||
await TaskApi.transferTask(data)
|
||||
transferFormRef.value.resetFields()
|
||||
popOverVisible.value.transfer = false
|
||||
message.success('操作成功')
|
||||
// 2. 加载最新数据
|
||||
@@ -666,21 +752,20 @@ const handleTransfer = async () => {
|
||||
const handleDelegate = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const deletegateFormRef = proxy.$refs['formRef']
|
||||
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(deletegateFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!delegateFormRef.value) return
|
||||
await delegateFormRef.value.validate()
|
||||
// 1.2 处理委派
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: genericForm.value.reason,
|
||||
delegateUserId: genericForm.value.delegateUserId
|
||||
reason: delegateForm.reason,
|
||||
delegateUserId: delegateForm.delegateUserId
|
||||
}
|
||||
|
||||
await TaskApi.delegateTask(data)
|
||||
popOverVisible.value.delegate = false
|
||||
delegateFormRef.value.resetFields()
|
||||
message.success('操作成功')
|
||||
// 2. 加载最新数据
|
||||
reload()
|
||||
@@ -693,21 +778,19 @@ const handleDelegate = async () => {
|
||||
const handlerAddSign = async (type: string) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const transferFormRef = proxy.$refs['formRef']
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(transferFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!addSignFormRef.value) return
|
||||
await addSignFormRef.value.validate()
|
||||
// 1.2 提交加签
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
type,
|
||||
reason: genericForm.value.reason,
|
||||
userIds: genericForm.value.addSignUserIds
|
||||
reason: addSignForm.reason,
|
||||
userIds: addSignForm.addSignUserIds
|
||||
}
|
||||
await TaskApi.signCreateTask(data)
|
||||
message.success('操作成功')
|
||||
addSignFormRef.value.resetFields()
|
||||
popOverVisible.value.addSign = false
|
||||
// 2 加载最新数据
|
||||
reload()
|
||||
@@ -720,21 +803,19 @@ const handlerAddSign = async (type: string) => {
|
||||
const handleReturn = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const returnFormRef = proxy.$refs['formRef']
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(returnFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!returnFormRef.value) return
|
||||
await returnFormRef.value.validate()
|
||||
// 1.2 提交退回
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: genericForm.value.returnReason,
|
||||
targetTaskDefinitionKey: genericForm.value.targetTaskDefinitionKey
|
||||
reason: returnForm.returnReason,
|
||||
targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
|
||||
}
|
||||
|
||||
await TaskApi.returnTask(data)
|
||||
popOverVisible.value.return = false
|
||||
returnFormRef.value.resetFields()
|
||||
message.success('操作成功')
|
||||
// 2 重新加载数据
|
||||
reload()
|
||||
@@ -747,19 +828,17 @@ const handleReturn = async () => {
|
||||
const handleCancel = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const cancelFormRef = proxy.$refs['formRef']
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(cancelFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!cancelFormRef.value) return
|
||||
await cancelFormRef.value.validate()
|
||||
// 1.2 提交取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(
|
||||
props.processInstance.id,
|
||||
genericForm.value.cancelReason
|
||||
cancelForm.cancelReason
|
||||
)
|
||||
popOverVisible.value.return = false
|
||||
message.success('操作成功')
|
||||
cancelFormRef.value.resetFields()
|
||||
// 2 重新加载数据
|
||||
reload()
|
||||
} finally {
|
||||
@@ -786,19 +865,17 @@ const getDeleteSignUserLabel = (task: any): string => {
|
||||
const handlerDeleteSign = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const deleteFormRef = proxy.$refs['formRef']
|
||||
// 1.1 校验表单
|
||||
const elForm = unref(deleteFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
if (!deleteSignFormRef.value) return
|
||||
await deleteSignFormRef.value.validate()
|
||||
// 1.2 提交减签
|
||||
const data = {
|
||||
id: genericForm.value.deleteSignTaskId,
|
||||
reason: genericForm.value.reason
|
||||
id: deleteSignForm.deleteSignTaskId,
|
||||
reason: deleteSignForm.reason
|
||||
}
|
||||
await TaskApi.signDeleteTask(data)
|
||||
message.success('减签成功')
|
||||
deleteSignFormRef.value.resetFields()
|
||||
popOverVisible.value.deleteSign = false
|
||||
// 2 加载最新数据
|
||||
reload()
|
||||
@@ -852,7 +929,6 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
|
||||
}
|
||||
|
||||
const loadTodoTask = (task: any) => {
|
||||
genericForm.value = {}
|
||||
approveForm.value = {}
|
||||
approveFormFApi.value = {}
|
||||
runningTask.value = task
|
||||
@@ -866,6 +942,30 @@ const loadTodoTask = (task: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 校验流程表单 */
|
||||
const validateNormalForm = async () => {
|
||||
if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
|
||||
let valid = true
|
||||
try {
|
||||
await props.normalFormApi?.validate()
|
||||
} catch {
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
|
||||
const getUpdatedProcessInstanceVaiables = ()=> {
|
||||
const variables = {}
|
||||
props.writableFields.forEach( (field) => {
|
||||
const fieldValue = props.normalFormApi.getValue(field)
|
||||
variables[field] = fieldValue;
|
||||
})
|
||||
return variables
|
||||
}
|
||||
|
||||
defineExpose({ loadTodoTask })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
|
||||
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}-${index}`">
|
||||
<!-- 第一行:节点名称、时间 -->
|
||||
<div class="flex w-full">
|
||||
<div class="font-bold"> {{ activity.name }}</div>
|
||||
@@ -113,7 +113,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<teleport defer :to="`#activity-task-${activity.id}`">
|
||||
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
||||
<div
|
||||
v-if="
|
||||
task.reason &&
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
class="form-box flex flex-col mb-30px flex-1"
|
||||
>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processDefinition?.formType === 10">
|
||||
<el-col v-if="processDefinition?.formType === BpmModelFormType.NORMAL">
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
@@ -58,7 +58,7 @@
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processDefinition?.formType === 20">
|
||||
<div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,6 +116,9 @@
|
||||
:process-instance="processInstance"
|
||||
:process-definition="processDefinition"
|
||||
:userOptions="userOptions"
|
||||
:normal-form="detailForm"
|
||||
:normal-form-api="fApi"
|
||||
:writable-fields="writableFields"
|
||||
@success="refresh"
|
||||
/>
|
||||
</div>
|
||||
@@ -126,7 +129,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { BpmModelType, BpmModelFormType } from '@/utils/constants'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
@@ -171,6 +174,8 @@ const detailForm = ref({
|
||||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
const writableFields: Array<string> = [] // 表单可以编辑的字段
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
getApprovalDetail()
|
||||
@@ -202,11 +207,12 @@ const getApprovalDetail = async () => {
|
||||
processDefinition.value = data.processDefinition
|
||||
|
||||
// 设置表单信息
|
||||
if (processDefinition.value.formType === 10) {
|
||||
if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
|
||||
// 获取表单字段权限
|
||||
const formFieldsPermission = data.formFieldsPermission
|
||||
|
||||
if (detailForm.value.rule.length > 0) {
|
||||
// 清空可编辑字段为空
|
||||
writableFields.splice(0)
|
||||
if (detailForm.value.rule?.length > 0) {
|
||||
// 避免刷新 form-create 显示不了
|
||||
detailForm.value.value = processInstance.value.formVariables
|
||||
} else {
|
||||
@@ -271,6 +277,8 @@ const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(false, field)
|
||||
// 加入可以编辑的字段
|
||||
writableFields.push(field)
|
||||
}
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
//@ts-ignore
|
||||
@@ -314,6 +322,7 @@ $process-header-height: 194px;
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
display: flex;
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
@@ -323,7 +332,6 @@ $process-header-height: 194px;
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.box-card) {
|
||||
|
||||
@@ -25,13 +25,14 @@
|
||||
</el-form-item>
|
||||
|
||||
<!-- TODO @ tuituji:style 可以使用 unocss -->
|
||||
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
|
||||
<!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 -->
|
||||
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
|
||||
<!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 RE:done & to check-->
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
@@ -42,21 +43,38 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 高级筛选 -->
|
||||
<!-- TODO @ tuituji:style 可以使用 unocss -->
|
||||
<el-form-item :style="{ position: 'absolute', right: '0px' }">
|
||||
<el-button v-popover="popoverRef" v-click-outside="onClickOutside" :icon="List">
|
||||
高级筛选
|
||||
</el-button>
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
trigger="click"
|
||||
virtual-triggering
|
||||
:visible="showPopover"
|
||||
persistent
|
||||
:width="400"
|
||||
:show-arrow="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button @click="showPopover = !showPopover">
|
||||
<Icon icon="ep:plus" class="mr-5px" />高级筛选
|
||||
</el-button>
|
||||
</template>
|
||||
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
@@ -86,21 +104,6 @@
|
||||
class="!w-390px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" class="bold-label" label-position="top" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
@@ -112,8 +115,13 @@
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- TODO tuituiji:参考钉钉,1)按照清空、取消、确认排序。2)右对齐。3)确认增加 primary -->
|
||||
<el-form-item class="bold-label" label-position="top">
|
||||
<el-button @click="handleQuery"> 确认</el-button>
|
||||
<el-button @click="showPopover = false"> 取消</el-button>
|
||||
<el-button @click="resetQuery"> 清空</el-button>
|
||||
</el-form-item>
|
||||
</el-popover>
|
||||
<!-- TODO @tuituji:这里应该有确认,和取消、清空搜索条件,三个按钮。 -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@@ -130,7 +138,7 @@
|
||||
fixed="left"
|
||||
/>
|
||||
<!-- TODO @芋艿:摘要 -->
|
||||
<!-- TODO @tuituji:流程状态。可见需求文档里 -->
|
||||
<!-- TODO tuituiji:参考钉钉;1)审批中时,展示审批任务;2)非审批中,展示状态 -->
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
@@ -198,8 +206,7 @@
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。
|
||||
import { List } from '@element-plus/icons-vue'
|
||||
// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。 RE:done & to check
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
@@ -241,6 +248,8 @@ const getList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const showPopover = ref(false)
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
@@ -273,7 +282,7 @@ const handleCreate = async (row?: ProcessInstanceVO) => {
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
const handleDetail = (row: ProcessInstanceVO) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
@@ -283,7 +292,7 @@ const handleDetail = (row) => {
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
const handleCancel = async (row: ProcessInstanceVO) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
@@ -298,15 +307,6 @@ const handleCancel = async (row) => {
|
||||
await getList()
|
||||
}
|
||||
|
||||
// TODO @tuituji:这个 import 是不是没用哈?
|
||||
import { ClickOutside as vClickOutside } from 'element-plus'
|
||||
|
||||
// TODO @tuituji:onClickAdvancedSearch。方法名叫这个,会更好一些哇?打开高级搜索。
|
||||
const popoverRef = ref()
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '20px 16px' }">
|
||||
<SimpleProcessDesigner :model-id="modelId" @success="close" />
|
||||
<SimpleProcessDesigner
|
||||
:model-id="modelId"
|
||||
:model-key="modelKey"
|
||||
:model-name="modelName"
|
||||
:value="currentValue"
|
||||
@success="handleSuccess"
|
||||
@init-finished="handleInit"
|
||||
:start-user-ids="startUserIds"
|
||||
ref="designerRef"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -9,11 +18,138 @@ import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/
|
||||
defineOptions({
|
||||
name: 'SimpleModelDesign'
|
||||
})
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由的查询
|
||||
const modelId = query.modelId as string
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
|
||||
const props = defineProps<{
|
||||
modelId?: string
|
||||
modelKey?: string
|
||||
modelName?: string
|
||||
value?: string
|
||||
startUserIds?: number[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success', 'init-finished'])
|
||||
const designerRef = ref()
|
||||
const isInitialized = ref(false)
|
||||
const currentValue = ref('')
|
||||
|
||||
// 初始化或更新当前值
|
||||
const initOrUpdateValue = async () => {
|
||||
console.log('initOrUpdateValue', props.value)
|
||||
if (props.value) {
|
||||
currentValue.value = props.value
|
||||
// 如果设计器已经初始化,立即加载数据
|
||||
if (isInitialized.value && designerRef.value) {
|
||||
try {
|
||||
await designerRef.value.loadProcessData(props.value)
|
||||
await nextTick()
|
||||
if (designerRef.value.refresh) {
|
||||
await designerRef.value.refresh()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载流程数据失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听属性变化
|
||||
watch(
|
||||
[() => props.modelKey, () => props.modelName, () => props.value],
|
||||
async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
|
||||
if (designerRef.value && isInitialized.value) {
|
||||
try {
|
||||
if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
|
||||
await designerRef.value.updateModel(newKey, newName)
|
||||
}
|
||||
if (newValue && newValue !== oldValue) {
|
||||
currentValue.value = newValue
|
||||
await designerRef.value.loadProcessData(newValue)
|
||||
await nextTick()
|
||||
if (designerRef.value.refresh) {
|
||||
await designerRef.value.refresh()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新流程数据失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
// 初始化完成回调
|
||||
const handleInit = async () => {
|
||||
try {
|
||||
isInitialized.value = true
|
||||
emit('init-finished')
|
||||
|
||||
// 等待下一个tick,确保设计器已经准备好
|
||||
await nextTick()
|
||||
|
||||
// 初始化完成后,设置初始值
|
||||
if (props.modelKey && props.modelName) {
|
||||
await designerRef.value.updateModel(props.modelKey, props.modelName)
|
||||
}
|
||||
if (props.value) {
|
||||
currentValue.value = props.value
|
||||
await designerRef.value.loadProcessData(props.value)
|
||||
// 再次刷新确保数据正确加载
|
||||
await nextTick()
|
||||
if (designerRef.value.refresh) {
|
||||
await designerRef.value.refresh()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化流程数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 修改成功回调
|
||||
const handleSuccess = (data?: any) => {
|
||||
console.warn('handleSuccess', data)
|
||||
if (data && data !== currentValue.value) {
|
||||
currentValue.value = data
|
||||
emit('success', data)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前流程数据 */
|
||||
const getCurrentFlowData = async () => {
|
||||
try {
|
||||
if (designerRef.value) {
|
||||
const data = await designerRef.value.getCurrentFlowData()
|
||||
if (data) {
|
||||
currentValue.value = data
|
||||
}
|
||||
return data
|
||||
}
|
||||
return currentValue.value || undefined
|
||||
} catch (error) {
|
||||
console.error('获取流程数据失败:', error)
|
||||
return currentValue.value || undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 组件创建时初始化数据
|
||||
onMounted(() => {
|
||||
initOrUpdateValue()
|
||||
})
|
||||
|
||||
// 组件卸载前保存数据
|
||||
onBeforeUnmount(async () => {
|
||||
try {
|
||||
const data = await getCurrentFlowData()
|
||||
if (data) {
|
||||
emit('success', data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存数据失败:', error)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
getCurrentFlowData,
|
||||
refresh: () => designerRef.value?.refresh?.()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-form-item label="" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
@@ -25,27 +25,96 @@
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 高级筛选 -->
|
||||
<el-form-item :style="{ position: 'absolute', right: '0px' }">
|
||||
<el-popover
|
||||
:visible="showPopover"
|
||||
persistent
|
||||
:width="400"
|
||||
:show-arrow="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button @click="showPopover = !showPopover" >
|
||||
<Icon icon="ep:plus" class="mr-5px" />高级筛选
|
||||
</el-button>
|
||||
|
||||
</template>
|
||||
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程发起人"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="bold-label" label-position="top">
|
||||
<el-button @click="handleQuery"> 确认</el-button>
|
||||
<el-button @click="showPopover = false"> 取消</el-button>
|
||||
<el-button @click="resetQuery"> 清空</el-button>
|
||||
</el-form-item>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
@@ -110,9 +179,10 @@
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
@@ -125,9 +195,13 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
|
||||
const showPopover = ref(false)
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
@@ -165,7 +239,8 @@ const handleAudit = (row: any) => {
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-form-item label="" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
@@ -25,27 +25,79 @@
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 高级筛选 -->
|
||||
<el-form-item :style="{ position: 'absolute', right: '0px' }">
|
||||
<el-popover
|
||||
:visible="showPopover"
|
||||
persistent
|
||||
:width="400"
|
||||
:show-arrow="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button @click="showPopover = !showPopover" >
|
||||
<Icon icon="ep:plus" class="mr-5px" />高级筛选
|
||||
</el-button>
|
||||
|
||||
</template>
|
||||
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程发起人"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="bold-label" label-position="top">
|
||||
<el-button @click="handleQuery"> 确认</el-button>
|
||||
<el-button @click="showPopover = false"> 取消</el-button>
|
||||
<el-button @click="resetQuery"> 清空</el-button>
|
||||
</el-form-item>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
@@ -95,6 +147,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
@@ -107,9 +160,11 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
@@ -123,6 +178,8 @@ const getList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const showPopover = ref(false)
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
@@ -147,7 +204,8 @@ const handleAudit = (row: any) => {
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalProductPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
:formatter="erpPriceInputFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -123,7 +123,7 @@
|
||||
disabled
|
||||
v-model="formData.totalPrice"
|
||||
placeholder="请输入商机金额"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
:formatter="erpPriceInputFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -142,7 +142,7 @@ import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import BusinessProductForm from './components/BusinessProductForm.vue'
|
||||
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalProductPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
:formatter="erpPriceInputFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -181,7 +181,7 @@
|
||||
disabled
|
||||
v-model="formData.totalPrice"
|
||||
placeholder="请输入商机金额"
|
||||
:formatter="erpPriceTableColumnFormattere"
|
||||
:formatter="erpPriceInputFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -199,7 +199,7 @@ import * as ContractApi from '@/api/crm/contract'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import ContractProductForm from '@/views/crm/contract/components/ContractProductForm.vue'
|
||||
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="产品单价" fixed="right" min-width="120">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
|
||||
<el-form-item
|
||||
:prop="`${$index}.productPrice`"
|
||||
:rules="formRules.productPrice"
|
||||
class="mb-0px!"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="row.productPrice"
|
||||
controls-position="right"
|
||||
@@ -153,6 +157,7 @@ const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
|
||||
productPrice: [{ required: true, message: '产品单价不能为空', trigger: 'blur' }],
|
||||
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref([]) // 表单 Ref
|
||||
|
||||
@@ -135,7 +135,8 @@ const makeTemplate = () => {
|
||||
|
||||
/** 复制 **/
|
||||
const copy = async (text: string) => {
|
||||
const { copy, copied, isSupported } = useClipboard({ source: text })
|
||||
const textToCopy = JSON.stringify(text, null, 2)
|
||||
const { copy, copied, isSupported } = useClipboard({ source: textToCopy })
|
||||
if (!isSupported) {
|
||||
message.error(t('common.copyError'))
|
||||
} else {
|
||||
@@ -149,17 +150,18 @@ const copy = async (text: string) => {
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (code) => {
|
||||
const highlightedCode = (code: string) => {
|
||||
// 处理语言和代码
|
||||
let language = 'json'
|
||||
if (formType.value === 2) {
|
||||
language = 'xml'
|
||||
}
|
||||
// debugger
|
||||
if (!isString(code)) {
|
||||
code = JSON.stringify(code)
|
||||
code = JSON.stringify(code, null, 2)
|
||||
}
|
||||
// 高亮
|
||||
const result = hljs.highlight(language, code, true)
|
||||
const result = hljs.highlight(code, { language: language, ignoreIllegals: true })
|
||||
return result.value || ' '
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="copyToClipboard(scope.row.url)">
|
||||
复制链接
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@@ -172,6 +175,13 @@ const openForm = () => {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
/** 复制到剪贴板方法 */
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('复制成功')
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
|
||||
@@ -151,7 +151,6 @@ import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivit
|
||||
import BargainActivityForm from './BargainActivityForm.vue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { fenToYuanFormat } from '@/utils/formatter'
|
||||
import { closeBargainActivity } from '@/api/mall/promotion/bargain/bargainActivity'
|
||||
|
||||
defineOptions({ name: 'PromotionBargainActivity' })
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
<!-- 排行列表 -->
|
||||
<el-table v-loading="loading" :data="list" @sort-change="handleSortChange">
|
||||
<el-table-column label="商品ID" prop="spuId" min-width="70" />
|
||||
<el-table-column label="商品 ID" prop="spuId" min-width="70" />
|
||||
<el-table-column label="商品图片" align="center" prop="picUrl" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-image
|
||||
@@ -27,7 +27,13 @@
|
||||
<el-table-column label="加购件数" prop="cartCount" min-width="105" sortable="custom" />
|
||||
<el-table-column label="下单件数" prop="orderCount" min-width="105" sortable="custom" />
|
||||
<el-table-column label="支付件数" prop="orderPayCount" min-width="105" sortable="custom" />
|
||||
<el-table-column label="支付金额" prop="orderPayPrice" min-width="105" sortable="custom" />
|
||||
<el-table-column
|
||||
label="支付金额"
|
||||
prop="orderPayPrice"
|
||||
min-width="105"
|
||||
sortable="custom"
|
||||
:formatter="fenToYuanFormat"
|
||||
/>
|
||||
<el-table-column label="收藏数" prop="favoriteCount" min-width="90" sortable="custom" />
|
||||
<el-table-column
|
||||
label="访客-支付转化率(%)"
|
||||
@@ -50,6 +56,7 @@
|
||||
import { ProductStatisticsApi, ProductStatisticsVO } from '@/api/mall/statistics/product'
|
||||
import { CardTitle } from '@/components/Card'
|
||||
import { buildSortingField } from '@/utils'
|
||||
import { fenToYuanFormat } from '@/utils/formatter'
|
||||
|
||||
/** 商品排行 */
|
||||
defineOptions({ name: 'ProductRank' })
|
||||
|
||||
@@ -144,7 +144,7 @@ const accountId = inject<number>('accountId')
|
||||
|
||||
// ========== 文件上传 ==========
|
||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
||||
const editorConfig = createEditorConfig(UPLOAD_URL, accountId)
|
||||
const editorConfig = createEditorConfig(UPLOAD_URL, unref(accountId))
|
||||
|
||||
// v-model=newsList
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'GoView' })
|
||||
|
||||
const src = 'http://127.0.0.1:3000'
|
||||
const src = ref(import.meta.env.VITE_GOVIEW_URL)
|
||||
</script>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<template #default="{ height, width }">
|
||||
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
||||
<el-table-v2
|
||||
v-loading="loading"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:width="width"
|
||||
@@ -31,7 +32,7 @@
|
||||
<AreaForm ref="formRef" />
|
||||
</template>
|
||||
<script setup lang="tsx">
|
||||
import type { Column } from 'element-plus'
|
||||
import { Column } from 'element-plus'
|
||||
import AreaForm from './AreaForm.vue'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
|
||||
@@ -40,7 +41,7 @@ defineOptions({ name: 'SystemArea' })
|
||||
// 表格的 column 字段
|
||||
const columns: Column[] = [
|
||||
{
|
||||
dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id
|
||||
dataKey: 'id', // 需要渲染当前列的数据字段
|
||||
title: '编号', // 显示在单元格表头的文本
|
||||
width: 400, // 当前列的宽度,必须设置
|
||||
fixed: true, // 是否固定列
|
||||
@@ -52,14 +53,17 @@ const columns: Column[] = [
|
||||
width: 200
|
||||
}
|
||||
]
|
||||
// 表格的数据
|
||||
const list = ref([])
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 表格的数据
|
||||
|
||||
/**
|
||||
* 获得数据列表
|
||||
*/
|
||||
/** 获得数据列表 */
|
||||
const getList = async () => {
|
||||
list.value = await AreaApi.getAreaTree()
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = await AreaApi.getAreaTree()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
|
||||
@@ -53,10 +53,6 @@
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button plain type="danger" @click="toggleExpandAll">
|
||||
<Icon class="mr-5px" icon="ep:sort" />
|
||||
展开/折叠
|
||||
</el-button>
|
||||
<el-button plain @click="refreshMenu">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
刷新菜单缓存
|
||||
@@ -67,65 +63,22 @@
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-if="refreshTable"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:default-expand-all="isExpandAll"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
|
||||
<el-table-column align="center" label="图标" prop="icon" width="100">
|
||||
<template #default="scope">
|
||||
<Icon :icon="scope.row.icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sort" width="60" />
|
||||
<el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
|
||||
<el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
|
||||
<el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
|
||||
<el-table-column label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
class="ml-4px"
|
||||
v-model="scope.row.status"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
:active-value="CommonStatusEnum.ENABLE"
|
||||
:inactive-value="CommonStatusEnum.DISABLE"
|
||||
:loading="menuStatusUpdating[scope.row.id]"
|
||||
@change="(val) => handleStatusChanged(scope.row, val as number)"
|
||||
<div style="height: 700px">
|
||||
<!-- AutoResizer 自动调节大小 -->
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
||||
<el-table-v2
|
||||
v-loading="loading"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:width="width"
|
||||
:height="height"
|
||||
expand-column-key="name"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['system:menu:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['system:menu:create']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('create', undefined, scope.row.id)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['system:menu:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-auto-resizer>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
@@ -138,6 +91,10 @@ import * as MenuApi from '@/api/system/menu'
|
||||
import { MenuVO } from '@/api/system/menu'
|
||||
import MenuForm from './MenuForm.vue'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { h } from 'vue'
|
||||
import { Column, ElButton } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { hasPermission } from '@/directives/permission/hasPermi'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemMenu' })
|
||||
@@ -146,6 +103,101 @@ const { wsCache } = useCache()
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 表格的 column 字段
|
||||
const columns: Column[] = [
|
||||
{
|
||||
dataKey: 'name',
|
||||
title: '菜单名称',
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
dataKey: 'icon',
|
||||
title: '图标',
|
||||
width: 150,
|
||||
cellRenderer: ({ rowData }) => {
|
||||
return h(Icon, {
|
||||
icon: rowData.icon
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
dataKey: 'sort',
|
||||
title: '排序',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
dataKey: 'permission',
|
||||
title: '权限标识',
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
dataKey: 'component',
|
||||
title: '组件路径',
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
dataKey: 'componentName',
|
||||
title: '组件名称',
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
dataKey: 'status',
|
||||
title: '状态',
|
||||
width: 160,
|
||||
cellRenderer: ({ rowData }) => {
|
||||
return h(ElSwitch, {
|
||||
modelValue: rowData.status,
|
||||
activeValue: CommonStatusEnum.ENABLE,
|
||||
inactiveValue: CommonStatusEnum.DISABLE,
|
||||
loading: menuStatusUpdating.value[rowData.id],
|
||||
disabled: !hasPermission(['system:menu:update']),
|
||||
onChange: (val) => handleStatusChanged(rowData, val as number)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
dataKey: 'operation',
|
||||
title: '操作',
|
||||
width: 200,
|
||||
cellRenderer: ({ rowData }) => {
|
||||
return h(
|
||||
'div',
|
||||
[
|
||||
hasPermission(['system:menu:update']) &&
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
link: true,
|
||||
type: 'primary',
|
||||
onClick: () => openForm('update', rowData.id)
|
||||
},
|
||||
'修改'
|
||||
),
|
||||
hasPermission(['system:menu:create']) &&
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
link: true,
|
||||
type: 'primary',
|
||||
onClick: () => openForm('create', undefined, rowData.id)
|
||||
},
|
||||
'新增'
|
||||
),
|
||||
hasPermission(['system:menu:delete']) &&
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
link: true,
|
||||
type: 'danger',
|
||||
onClick: () => handleDelete(rowData.id)
|
||||
},
|
||||
'删除'
|
||||
)
|
||||
].filter(Boolean)
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<any>([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
@@ -153,8 +205,6 @@ const queryParams = reactive({
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const isExpandAll = ref(false) // 是否展开,默认全部折叠
|
||||
const refreshTable = ref(true) // 重新渲染表格状态
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@@ -184,15 +234,6 @@ const openForm = (type: string, id?: number, parentId?: number) => {
|
||||
formRef.value.open(type, id, parentId)
|
||||
}
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = () => {
|
||||
refreshTable.value = false
|
||||
isExpandAll.value = !isExpandAll.value
|
||||
nextTick(() => {
|
||||
refreshTable.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 刷新菜单缓存按钮操作 */
|
||||
const refreshMenu = async () => {
|
||||
try {
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
:disabled="!checkPermi(['system:user:update'])"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
Reference in New Issue
Block a user