Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3
commit
efbc51659b
|
@ -106,6 +106,11 @@ export const copyTask = async (data: any) => {
|
|||
return await request.put({ url: '/bpm/task/copy', data })
|
||||
}
|
||||
|
||||
// 撤回
|
||||
export const withdrawTask = async (taskId: string) => {
|
||||
return await request.put({ url: '/bpm/task/withdraw', params: { taskId } })
|
||||
}
|
||||
|
||||
// 获取我的待办任务
|
||||
export const myTodoTask = async (processInstanceId: string) => {
|
||||
return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
|
||||
|
|
|
@ -4,7 +4,9 @@ export interface MailLogVO {
|
|||
id: number
|
||||
userId: number
|
||||
userType: number
|
||||
toMail: string
|
||||
toMails: string[]
|
||||
ccMails?: string[]
|
||||
bccMails?: string[]
|
||||
accountId: number
|
||||
fromMail: string
|
||||
templateId: number
|
||||
|
|
|
@ -14,7 +14,9 @@ export interface MailTemplateVO {
|
|||
}
|
||||
|
||||
export interface MailSendReqVO {
|
||||
mail: string
|
||||
toMails: string[]
|
||||
ccMails?: string[]
|
||||
bccMails?: string[]
|
||||
templateCode: string
|
||||
templateParams: Map<String, Object>
|
||||
}
|
||||
|
@ -46,7 +48,10 @@ export const deleteMailTemplate = async (id: number) => {
|
|||
|
||||
// 批量删除邮件模版
|
||||
export const deleteMailTemplateList = async (ids: number[]) => {
|
||||
return await request.delete({ url: '/system/mail-template/delete-list', params: { ids: ids.join(',') } })
|
||||
return await request.delete({
|
||||
url: '/system/mail-template/delete-list',
|
||||
params: { ids: ids.join(',') }
|
||||
})
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
:group="{ name: 'component', pull: 'clone', put: false }"
|
||||
:clone="handleCloneComponent"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
:force-fallback="false"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<draggable
|
||||
v-model="pageComponents"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
:force-fallback="false"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序 </el-text>
|
||||
<VueDraggable
|
||||
:list="formData"
|
||||
:force-fallback="true"
|
||||
:force-fallback="false"
|
||||
:animation="200"
|
||||
handle=".drag-icon"
|
||||
class="m-t-8px"
|
||||
|
|
|
@ -236,7 +236,7 @@
|
|||
<el-divider />
|
||||
<div>
|
||||
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||
<el-button @click="closeDrawer">取 消</el-button>
|
||||
<el-button @click="cancelConfig">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
@ -467,6 +467,13 @@ const saveConfig = async () => {
|
|||
return true
|
||||
}
|
||||
|
||||
/** 取消配置 */
|
||||
const cancelConfig = () => {
|
||||
// 恢复原来的配置
|
||||
currentNode.value.triggerSetting = originalSetting
|
||||
closeDrawer()
|
||||
}
|
||||
|
||||
/** 获取节点展示内容 */
|
||||
const getShowText = (): string => {
|
||||
let showText = ''
|
||||
|
@ -498,7 +505,7 @@ const getShowText = (): string => {
|
|||
/** 显示触发器节点配置, 由父组件传过来 */
|
||||
const showTriggerNodeConfig = (node: SimpleFlowNode) => {
|
||||
nodeName.value = node.name
|
||||
originalSetting = node.triggerSetting ? JSON.parse(JSON.stringify(node.triggerSetting)) : {}
|
||||
originalSetting = cloneDeep(node.triggerSetting)
|
||||
if (node.triggerSetting) {
|
||||
configForm.value = {
|
||||
type: node.triggerSetting.type,
|
||||
|
|
|
@ -36,14 +36,15 @@
|
|||
* Verify 验证码组件
|
||||
* @description 分发验证码使用
|
||||
* */
|
||||
import { VerifyPoints, VerifySlide } from './Verify'
|
||||
import {VerifyPictureWord, VerifyPoints, VerifySlide} from './Verify'
|
||||
import { computed, ref, toRefs, watchEffect } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'Vue3Verify',
|
||||
components: {
|
||||
VerifySlide,
|
||||
VerifyPoints
|
||||
VerifyPoints,
|
||||
VerifyPictureWord
|
||||
},
|
||||
props: {
|
||||
captchaType: {
|
||||
|
@ -118,6 +119,10 @@ export default {
|
|||
}
|
||||
watchEffect(() => {
|
||||
switch (captchaType.value) {
|
||||
case 'pictureWord':
|
||||
verifyType.value = '3'
|
||||
componentType.value = 'VerifyPictureWord'
|
||||
break
|
||||
case 'blockPuzzle':
|
||||
verifyType.value = '2'
|
||||
componentType.value = 'VerifySlide'
|
||||
|
@ -438,4 +443,4 @@ export default {
|
|||
content: ' ';
|
||||
inset: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<div style="position: relative">
|
||||
<div class="verify-img-out">
|
||||
<div
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
height: setSize.imgHeight,
|
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
|
||||
'margin-bottom': vSpace + 'px'
|
||||
}"
|
||||
class="verify-img-panel"
|
||||
>
|
||||
<div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh">
|
||||
<i class="iconfont icon-refresh"></i>
|
||||
</div>
|
||||
<img
|
||||
@click="refresh"
|
||||
ref="canvas"
|
||||
:src="'data:image/png;base64,' + verificationCodeImg"
|
||||
alt=""
|
||||
style="display: block; width: 100%; height: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
color: barAreaColor,
|
||||
'border-color': barAreaBorderColor
|
||||
// 'line-height': barSize.height
|
||||
}"
|
||||
class="verify-bar-area"
|
||||
>
|
||||
<div class="verify-msg">{{ text }}</div>
|
||||
<div
|
||||
:style="{
|
||||
'line-height': barSize.height
|
||||
}"
|
||||
>
|
||||
<input class="verify-input" type="text" v-model="userCode" />
|
||||
</div>
|
||||
<button type="button" class="verify-btn" @click="submit" :disabled="checking">{{
|
||||
t('captcha.verify')
|
||||
}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup type="text/babel">
|
||||
/**
|
||||
* VerifyPictureWord
|
||||
* @description 输入文字
|
||||
* */
|
||||
import { resetSize } from '../utils/util'
|
||||
import { aesEncrypt } from '../utils/ase'
|
||||
import { getCode, reqCheck } from '@/api/login'
|
||||
import { getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 弹出式 pop,固定 fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType: {
|
||||
type: String
|
||||
},
|
||||
// 间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { mode, captchaType } = toRefs(props)
|
||||
const { proxy } = getCurrentInstance()
|
||||
let secretKey = ref(''), // 后端返回的ase加密秘钥
|
||||
userCode = ref(''), // 用户输入的验证码 暂存至pointJson,无需加密
|
||||
verificationCodeImg = ref(''), // 后端获取到的背景图片
|
||||
backToken = ref(''), // 后端返回的token值
|
||||
setSize = reactive({
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
}),
|
||||
text = ref(''),
|
||||
barAreaColor = ref('#000'),
|
||||
barAreaBorderColor = ref('#ddd'),
|
||||
showRefresh = ref(true),
|
||||
// bindingClick = ref(true)
|
||||
checking = ref(false)
|
||||
|
||||
const init = () => {
|
||||
// 加载页面
|
||||
getPicture()
|
||||
nextTick(() => {
|
||||
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
|
||||
setSize.imgHeight = imgHeight
|
||||
setSize.imgWidth = imgWidth
|
||||
setSize.barHeight = barHeight
|
||||
setSize.barWidth = barWidth
|
||||
proxy.$parent.$emit('ready', proxy)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
// 禁止拖拽
|
||||
init()
|
||||
proxy.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const canvas = ref(null)
|
||||
|
||||
const submit = () => {
|
||||
checking.value = true
|
||||
// 发送后端请求
|
||||
const captchaVerification = secretKey.value
|
||||
? aesEncrypt(backToken.value + '---' + userCode.value, secretKey.value)
|
||||
: backToken.value + '---' + userCode.value
|
||||
let data = {
|
||||
captchaType: captchaType.value,
|
||||
pointJson: userCode.value,
|
||||
token: backToken.value
|
||||
}
|
||||
reqCheck(data).then((res) => {
|
||||
if (res.repCode === '0000') {
|
||||
barAreaColor.value = '#4cae4c'
|
||||
barAreaBorderColor.value = '#5cb85c'
|
||||
text.value = t('captcha.success')
|
||||
// bindingClick.value = false
|
||||
if (mode.value === 'pop') {
|
||||
setTimeout(() => {
|
||||
proxy.$parent.clickShow = false
|
||||
refresh()
|
||||
}, 1500)
|
||||
}
|
||||
proxy.$parent.$emit('success', { captchaVerification })
|
||||
} else {
|
||||
proxy.$parent.$emit('error', proxy)
|
||||
barAreaColor.value = '#d9534f'
|
||||
barAreaBorderColor.value = '#d9534f'
|
||||
text.value = t('captcha.fail')
|
||||
setTimeout(() => {
|
||||
refresh()
|
||||
}, 700)
|
||||
}
|
||||
checking.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const refresh = async function () {
|
||||
barAreaColor.value = '#000'
|
||||
barAreaBorderColor.value = '#ddd'
|
||||
checking.value = false
|
||||
|
||||
userCode.value = ''
|
||||
|
||||
await getPicture()
|
||||
showRefresh.value = true
|
||||
}
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
const getPicture = async () => {
|
||||
let data = {
|
||||
captchaType: captchaType.value
|
||||
}
|
||||
const res = await getCode(data)
|
||||
if (res.repCode === '0000') {
|
||||
verificationCodeImg.value = res.repData.originalImageBase64
|
||||
backToken.value = res.repData.token
|
||||
secretKey.value = res.repData.secretKey
|
||||
text.value = t('captcha.code')
|
||||
} else {
|
||||
text.value = res.repMsg
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,5 @@
|
|||
import VerifySlide from './VerifySlide.vue'
|
||||
import VerifyPoints from './VerifyPoints.vue'
|
||||
import VerifyPictureWord from './VerifyPictureWord.vue'
|
||||
|
||||
export { VerifySlide, VerifyPoints }
|
||||
export { VerifySlide, VerifyPoints, VerifyPictureWord }
|
|
@ -146,9 +146,11 @@ export default {
|
|||
invalidTenantName:"Invalid Tenant Name"
|
||||
},
|
||||
captcha: {
|
||||
verify: 'Verify',
|
||||
verification: 'Please complete security verification',
|
||||
slide: 'Swipe right to complete verification',
|
||||
point: 'Please click',
|
||||
code: 'Please enter the verification code',
|
||||
success: 'Verification succeeded',
|
||||
fail: 'verification failed'
|
||||
},
|
||||
|
@ -457,4 +459,4 @@ export default {
|
|||
btn_zoom_out: 'Zoom out',
|
||||
preview: 'Preivew'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -147,9 +147,11 @@ export default {
|
|||
invalidTenantName: '无效的租户名称'
|
||||
},
|
||||
captcha: {
|
||||
verify: '验证',
|
||||
verification: '请完成安全验证',
|
||||
slide: '向右滑动完成验证',
|
||||
point: '请依次点击',
|
||||
code: '请输入验证码',
|
||||
success: '验证成功',
|
||||
fail: '验证失败'
|
||||
},
|
||||
|
@ -453,4 +455,4 @@ export default {
|
|||
preview: '预览'
|
||||
},
|
||||
'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
* 针对 https://github.com/xaboy/form-create-designer 封装的工具类
|
||||
*/
|
||||
import { isRef } from 'vue'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
|
||||
// 编码表单 Conf
|
||||
export const encodeConf = (designerRef: object) => {
|
||||
|
@ -24,7 +25,7 @@ export const encodeFields = (designerRef: object) => {
|
|||
export const decodeFields = (fields: string[]) => {
|
||||
const rule: object[] = []
|
||||
fields.forEach((item) => {
|
||||
rule.push(JSON.parse(item))
|
||||
rule.push(formCreate.parseJson(item))
|
||||
})
|
||||
return rule
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ export const decodeFields = (fields: string[]) => {
|
|||
// 设置表单的 Conf 和 Fields,适用 FcDesigner 场景
|
||||
export const setConfAndFields = (designerRef: object, conf: string, fields: string) => {
|
||||
// @ts-ignore
|
||||
designerRef.value.setOption(JSON.parse(conf))
|
||||
designerRef.value.setOption(formCreate.parseJson(conf))
|
||||
// @ts-ignore
|
||||
designerRef.value.setRule(decodeFields(fields))
|
||||
}
|
||||
|
@ -49,154 +50,10 @@ export const setConfAndFields2 = (
|
|||
detailPreview = detailPreview.value
|
||||
}
|
||||
|
||||
// 修复所有函数类型(解决设计器保存后函数变成字符串的问题)。例如说:
|
||||
// https://t.zsxq.com/rADff
|
||||
// https://t.zsxq.com/ZfbGt
|
||||
// https://t.zsxq.com/mHOoj
|
||||
// https://t.zsxq.com/BSylB
|
||||
const option = JSON.parse(conf)
|
||||
const rule = decodeFields(fields)
|
||||
// 🔧 修复所有函数类型 - 解决设计器保存后函数变成字符串的问题
|
||||
const fixFunctions = (obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
// 检查是否是函数相关的属性
|
||||
if (isFunctionProperty(key)) {
|
||||
// 如果不是函数类型,重新构建为函数
|
||||
if (typeof obj[key] !== 'function') {
|
||||
obj[key] = createDefaultFunction(key)
|
||||
}
|
||||
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
// 递归处理嵌套对象
|
||||
fixFunctions(obj[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// 判断是否是函数属性
|
||||
const isFunctionProperty = (key: string): boolean => {
|
||||
const functionKeys = [
|
||||
'beforeFetch', // 请求前处理
|
||||
'afterFetch', // 请求后处理
|
||||
'onSubmit', // 表单提交
|
||||
'onReset', // 表单重置
|
||||
'onChange', // 值变化
|
||||
'onInput', // 输入事件
|
||||
'onClick', // 点击事件
|
||||
'onFocus', // 获取焦点
|
||||
'onBlur', // 失去焦点
|
||||
'onMounted', // 组件挂载
|
||||
'onCreated', // 组件创建
|
||||
'onReload', // 重新加载
|
||||
'remoteMethod', // 远程搜索方法
|
||||
'parseFunc', // 解析函数
|
||||
'validator', // 验证器
|
||||
'asyncValidator', // 异步验证器
|
||||
'formatter', // 格式化函数
|
||||
'parser', // 解析函数
|
||||
'beforeUpload', // 上传前处理
|
||||
'onSuccess', // 成功回调
|
||||
'onError', // 错误回调
|
||||
'onProgress', // 进度回调
|
||||
'onPreview', // 预览回调
|
||||
'onRemove', // 移除回调
|
||||
'onExceed', // 超出限制回调
|
||||
'filterMethod', // 过滤方法
|
||||
'sortMethod', // 排序方法
|
||||
'loadData', // 加载数据
|
||||
'renderContent', // 渲染内容
|
||||
'render' // 渲染函数
|
||||
]
|
||||
// 检查是否以函数相关前缀开头
|
||||
const functionPrefixes = ['on', 'before', 'after', 'handle']
|
||||
return functionKeys.includes(key) || functionPrefixes.some((prefix) => key.startsWith(prefix))
|
||||
}
|
||||
// 根据函数名创建默认函数
|
||||
const createDefaultFunction = (key: string): Function => {
|
||||
switch (key) {
|
||||
case 'beforeFetch':
|
||||
return (config: any) => {
|
||||
// 添加 Token 认证头。例如说:
|
||||
// https://t.zsxq.com/hK3FO
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
Authorization: 'Bearer ' + token
|
||||
}
|
||||
}
|
||||
// 添加通用请求头
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
// 添加时间戳防止缓存
|
||||
config.params = {
|
||||
...config.params,
|
||||
_t: Date.now()
|
||||
}
|
||||
return config
|
||||
}
|
||||
case 'afterFetch':
|
||||
return (data: any) => {
|
||||
return data
|
||||
}
|
||||
case 'onSubmit':
|
||||
return (_formData: any) => {
|
||||
return true
|
||||
}
|
||||
case 'onReset':
|
||||
return () => {
|
||||
return true
|
||||
}
|
||||
case 'onChange':
|
||||
return (_value: any, _oldValue: any) => {}
|
||||
case 'remoteMethod':
|
||||
return (query: string) => {
|
||||
console.log('remoteMethod被调用:', query)
|
||||
}
|
||||
case 'parseFunc':
|
||||
return (data: any) => {
|
||||
// 默认解析逻辑:如果是数组直接返回,否则尝试获取list属性
|
||||
if (Array.isArray(data)) {
|
||||
return data
|
||||
}
|
||||
return data?.list || data?.data || []
|
||||
}
|
||||
case 'validator':
|
||||
return (_rule: any, _value: any, callback: Function) => {
|
||||
callback()
|
||||
}
|
||||
case 'beforeUpload':
|
||||
return (_file: any) => {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
// 通用默认函数
|
||||
return (...args: any[]) => {
|
||||
// 对于事件处理函数,返回true表示继续执行
|
||||
if (key.startsWith('on') || key.startsWith('handle')) {
|
||||
return true
|
||||
}
|
||||
// 对于其他函数,返回第一个参数(通常是数据传递)
|
||||
return args[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
// 修复 option 中的所有函数
|
||||
fixFunctions(option)
|
||||
// 修复 rule 中的所有函数(包括组件的 props)
|
||||
if (Array.isArray(rule)) {
|
||||
rule.forEach((item: any) => {
|
||||
fixFunctions(item)
|
||||
})
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
detailPreview.option = option
|
||||
detailPreview.option = formCreate.parseJson(conf)
|
||||
// @ts-ignore
|
||||
detailPreview.rule = rule
|
||||
detailPreview.rule = decodeFields(fields)
|
||||
|
||||
if (value) {
|
||||
// @ts-ignore
|
||||
|
|
|
@ -185,7 +185,7 @@ const { push } = useRouter()
|
|||
const permissionStore = usePermissionStore()
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ 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 captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||
|
||||
const validatePass2 = (_rule, value, callback) => {
|
||||
if (value === '') {
|
||||
|
|
|
@ -47,10 +47,7 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="24"
|
||||
class="px-10px mt-[-20px] mb-[-20px]"
|
||||
>
|
||||
<el-col :span="24" class="px-10px mt-[-20px] mb-[-20px]">
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="6">
|
||||
|
@ -177,7 +174,7 @@ const permissionStore = usePermissionStore()
|
|||
const redirect = ref<string>('')
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ const permissionStore = usePermissionStore()
|
|||
const redirect = ref<string>('')
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
||||
|
||||
|
|
|
@ -11,6 +11,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">审批人权限</el-text>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox v-model="modelData.allowWithdrawTask" label="允许审批人撤回任务" />
|
||||
<div class="ml-22px">
|
||||
<el-text type="info"> 审批人可撤回正在审批节点的前一节点 </el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modelData.processIdRule" class="mb-20px">
|
||||
<template #label>
|
||||
<el-text size="large" tag="b">流程编码</el-text>
|
||||
|
@ -232,34 +243,6 @@ import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/co
|
|||
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
|
||||
|
||||
const modelData = defineModel<any>()
|
||||
const formFields = ref<string[]>([])
|
||||
|
||||
const props = defineProps({
|
||||
// 流程表单 ID
|
||||
modelFormId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 监听 modelFormId 变化
|
||||
watch(
|
||||
() => props.modelFormId,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
const form = await FormApi.getForm(newVal);
|
||||
formFields.value = form?.fields;
|
||||
} else {
|
||||
// 如果 modelFormId 为空,清空表单字段
|
||||
formFields.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
// 暴露给子组件使用
|
||||
provide('formFields', formFields)
|
||||
|
||||
/** 自定义 ID 流程编码 */
|
||||
const timeOptions = ref([
|
||||
|
@ -374,10 +357,10 @@ const handleTaskAfterTriggerEnableChange = (val: boolean | string | number) => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 表单选项 */
|
||||
const formField = ref<Array<{ field: string; title: string }>>([])
|
||||
/** 已解析表单字段 */
|
||||
const formFields = ref<Array<{ field: string; title: string }>>([])
|
||||
const formFieldOptions4Title = computed(() => {
|
||||
let cloneFormField = formField.value.map((item) => {
|
||||
let cloneFormField = formFields.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field
|
||||
|
@ -399,7 +382,7 @@ const formFieldOptions4Title = computed(() => {
|
|||
return cloneFormField
|
||||
})
|
||||
const formFieldOptions4Summary = computed(() => {
|
||||
return formField.value.map((item) => {
|
||||
return formFields.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field
|
||||
|
@ -407,6 +390,11 @@ const formFieldOptions4Summary = computed(() => {
|
|||
})
|
||||
})
|
||||
|
||||
/** 未解析的表单字段 */
|
||||
const unParsedFormFields = ref<string[]>([])
|
||||
/** 暴露给子组件 HttpRequestSetting 使用 */
|
||||
provide('formFields', unParsedFormFields)
|
||||
|
||||
/** 兼容以前未配置更多设置的流程 */
|
||||
const initData = () => {
|
||||
if (!modelData.value.processIdRule) {
|
||||
|
@ -445,6 +433,9 @@ const initData = () => {
|
|||
if (modelData.value.taskAfterTriggerSetting) {
|
||||
taskAfterTriggerEnable.value = true
|
||||
}
|
||||
if (modelData.value.allowWithdrawTask) {
|
||||
modelData.value.allowWithdrawTask = false
|
||||
}
|
||||
}
|
||||
defineExpose({ initData })
|
||||
|
||||
|
@ -456,13 +447,15 @@ watch(
|
|||
const data = await FormApi.getForm(newFormId)
|
||||
const result: Array<{ field: string; title: string }> = []
|
||||
if (data.fields) {
|
||||
unParsedFormFields.value = data.fields
|
||||
data.fields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
formField.value = result
|
||||
formFields.value = result
|
||||
} else {
|
||||
formField.value = []
|
||||
formFields.value = []
|
||||
unParsedFormFields.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
|
|
@ -77,10 +77,7 @@
|
|||
|
||||
<!-- 第四步:更多设置 -->
|
||||
<div v-show="currentStep === 3" class="mx-auto w-700px">
|
||||
<ExtraSettings
|
||||
ref="extraSettingsRef"
|
||||
v-model="formData"
|
||||
:model-form-id="formData.formId"/>
|
||||
<ExtraSettings ref="extraSettingsRef" v-model="formData" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,7 +173,8 @@ const formData: any = ref({
|
|||
summarySetting: {
|
||||
enable: false,
|
||||
summary: []
|
||||
}
|
||||
},
|
||||
allowWithdrawTask: false
|
||||
})
|
||||
|
||||
// 流程数据
|
||||
|
|
|
@ -51,8 +51,10 @@
|
|||
>
|
||||
<div class="ml-10px -mt-15px -mb-35px">
|
||||
<ProcessInstanceTimeline
|
||||
ref="nextAssigneesTimelineRef"
|
||||
:activity-nodes="nextAssigneesActivityNode"
|
||||
:show-status-icon="false"
|
||||
:enable-approve-user-select="true"
|
||||
@select-user-confirm="selectNextAssigneesConfirm"
|
||||
/>
|
||||
</div>
|
||||
|
@ -571,6 +573,7 @@ const approveFormRef = ref<FormInstance>()
|
|||
const signRef = ref()
|
||||
const approveSignFormRef = ref()
|
||||
const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 下一个审批节点信息
|
||||
const nextAssigneesTimelineRef = ref() // 下一个节点审批人时间线组件的引用
|
||||
const approveReasonForm = reactive({
|
||||
reason: '',
|
||||
signPicUrl: '',
|
||||
|
@ -717,6 +720,10 @@ const closePopover = (type: string, formRef: FormInstance | undefined) => {
|
|||
}
|
||||
popOverVisible.value[type] = false
|
||||
nextAssigneesActivityNode.value = []
|
||||
// 清理 Timeline 组件中的自定义审批人数据
|
||||
if (nextAssigneesTimelineRef.value) {
|
||||
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
|
||||
|
@ -729,6 +736,7 @@ const initNextAssigneesFormField = async () => {
|
|||
processVariablesStr: JSON.stringify(variables)
|
||||
})
|
||||
if (data && data.length > 0) {
|
||||
const customApproveUsersData: Record<string, any[]> = {} // 用于收集需要设置到 Timeline 组件的自定义审批人数据
|
||||
data.forEach((node: any) => {
|
||||
if (
|
||||
// 情况一:当前节点没有审批人,并且是发起人自选
|
||||
|
@ -740,7 +748,18 @@ const initNextAssigneesFormField = async () => {
|
|||
) {
|
||||
nextAssigneesActivityNode.value.push(node)
|
||||
}
|
||||
|
||||
// 如果节点有 candidateUsers,设置到 customApproveUsers 中
|
||||
if (node.candidateUsers && node.candidateUsers.length > 0) {
|
||||
customApproveUsersData[node.id] = node.candidateUsers
|
||||
}
|
||||
})
|
||||
|
||||
// 将 candidateUsers 设置到 Timeline 组件中
|
||||
await nextTick() // 等待下一个 tick,确保 Timeline 组件已经渲染
|
||||
if (nextAssigneesTimelineRef.value && Object.keys(customApproveUsersData).length > 0) {
|
||||
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers(customApproveUsersData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -803,6 +822,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
await TaskApi.approveTask(data)
|
||||
popOverVisible.value.approve = false
|
||||
nextAssigneesActivityNode.value = []
|
||||
// 清理 Timeline 组件中的自定义审批人数据
|
||||
if (nextAssigneesTimelineRef.value) {
|
||||
nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
|
||||
}
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
// 审批不通过数据
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
>
|
||||
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
|
||||
<div
|
||||
v-if="showStatusIcon"
|
||||
v-if="props.showStatusIcon"
|
||||
class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
|
||||
>
|
||||
|
@ -55,13 +55,13 @@
|
|||
class="flex flex-wrap gap2 items-center"
|
||||
v-if="
|
||||
isEmpty(activity.tasks) &&
|
||||
isEmpty(activity.candidateUsers) &&
|
||||
(CandidateStrategy.START_USER_SELECT === activity.candidateStrategy ||
|
||||
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy)
|
||||
((CandidateStrategy.START_USER_SELECT === activity.candidateStrategy &&
|
||||
isEmpty(activity.candidateUsers)) ||
|
||||
(props.enableApproveUserSelect &&
|
||||
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy))
|
||||
"
|
||||
>
|
||||
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
|
||||
|
||||
<el-tooltip content="添加用户" placement="left">
|
||||
<el-button
|
||||
class="!px-6px"
|
||||
|
@ -119,7 +119,7 @@
|
|||
</template>
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
|
||||
v-if="props.showStatusIcon && onlyStatusIconShow.includes(task.status)"
|
||||
class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2[task.status]?.color }"
|
||||
>
|
||||
|
@ -165,7 +165,7 @@
|
|||
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
v-if="showStatusIcon"
|
||||
v-if="props.showStatusIcon"
|
||||
class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2['-1']?.color }"
|
||||
>
|
||||
|
@ -198,13 +198,15 @@ import transactorSvg from '@/assets/svgs/bpm/transactor.svg'
|
|||
import childProcessSvg from '@/assets/svgs/bpm/child-process.svg'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
|
||||
showStatusIcon?: boolean // 是否显示头像右下角状态图标
|
||||
enableApproveUserSelect?: boolean // 是否开启审批人自选功能
|
||||
}>(),
|
||||
{
|
||||
showStatusIcon: true // 默认值为 true
|
||||
showStatusIcon: true, // 默认值为 true
|
||||
enableApproveUserSelect: false // 默认值为 false
|
||||
}
|
||||
)
|
||||
const { push } = useRouter() // 路由
|
||||
|
@ -341,4 +343,19 @@ const handleChildProcess = (activity: any) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 设置自定义审批人 */
|
||||
const setCustomApproveUsers = (activityId: string, users: any[]) => {
|
||||
customApproveUsers.value[activityId] = users || []
|
||||
}
|
||||
|
||||
/** 批量设置多个节点的自定义审批人 */
|
||||
const batchSetCustomApproveUsers = (data: Record<string, any[]>) => {
|
||||
Object.keys(data).forEach((activityId) => {
|
||||
customApproveUsers.value[activityId] = data[activityId] || []
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers })
|
||||
</script>
|
||||
|
|
|
@ -184,8 +184,9 @@
|
|||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<el-table-column align="center" label="操作" fixed="right" width="130">
|
||||
<template #default="scope">
|
||||
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -209,6 +210,7 @@ import * as DefinitionApi from '@/api/bpm/definition'
|
|||
defineOptions({ name: 'BpmDoneTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
@ -262,6 +264,14 @@ const handleAudit = (row: any) => {
|
|||
})
|
||||
}
|
||||
|
||||
/** 测回按钮 */
|
||||
const handleWithdraw = (row: any) => {
|
||||
TaskApi.withdrawTask(row.id).then(() => {
|
||||
message.success('撤回成功')
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
|
|
|
@ -13,12 +13,34 @@
|
|||
<el-descriptions-item label="模版发送人名称">
|
||||
{{ detailData.templateNickname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户信息">
|
||||
{{ detailData.toMail }}
|
||||
<el-descriptions-item label="接收用户">
|
||||
<span v-if="detailData.userType && detailData.userId">
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
|
||||
({{ detailData.userId }})
|
||||
</span>
|
||||
<span v-else>无</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="接收信息">
|
||||
<div>
|
||||
<div v-if="detailData.toMails && detailData.toMails.length > 0">
|
||||
收件:
|
||||
<span v-for="(mail, index) in detailData.toMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < detailData.toMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="detailData.ccMails && detailData.ccMails.length > 0">
|
||||
抄送:
|
||||
<span v-for="(mail, index) in detailData.ccMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < detailData.ccMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="detailData.bccMails && detailData.bccMails.length > 0">
|
||||
密送:
|
||||
<span v-for="(mail, index) in detailData.bccMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < detailData.bccMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="邮件标题">
|
||||
{{ detailData.templateTitle }}
|
||||
|
@ -58,7 +80,7 @@ defineOptions({ name: 'SystemMailLogDetail' })
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref() // 详情数据
|
||||
const accountList = ref([]) // 邮箱账号列表
|
||||
const accountList = ref<MailAccountApi.MailAccountVO[]>([]) // 邮箱账号列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: MailLogApi.MailLogVO) => {
|
||||
|
|
|
@ -119,12 +119,36 @@
|
|||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="接收邮箱" align="center" prop="toMail" width="200">
|
||||
<el-table-column label="接收用户" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.toMail }}</div>
|
||||
<div v-if="scope.row.userType && scope.row.userId">
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
|
||||
{{ '(' + scope.row.userId + ')' }}
|
||||
<div>{{ '(' + scope.row.userId + ')' }}</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="接收信息" align="center" width="300">
|
||||
<template #default="scope">
|
||||
<div class="text-left">
|
||||
<div v-if="scope.row.toMails && scope.row.toMails.length > 0">
|
||||
收件:
|
||||
<span v-for="(mail, index) in scope.row.toMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < scope.row.toMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="scope.row.ccMails && scope.row.ccMails.length > 0">
|
||||
抄送:
|
||||
<span v-for="(mail, index) in scope.row.ccMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < scope.row.ccMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="scope.row.bccMails && scope.row.bccMails.length > 0">
|
||||
密送:
|
||||
<span v-for="(mail, index) in scope.row.bccMails" :key="mail">
|
||||
{{ mail }}<span v-if="index < scope.row.bccMails.length - 1">、</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -185,15 +209,15 @@ const queryParams = reactive({
|
|||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
toMail: '',
|
||||
accountId: null,
|
||||
templateId: null,
|
||||
sendStatus: null,
|
||||
userId: null,
|
||||
userType: null,
|
||||
accountId: undefined,
|
||||
templateId: undefined,
|
||||
sendStatus: undefined,
|
||||
userId: undefined,
|
||||
userType: undefined,
|
||||
sendTime: []
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const accountList = ref([]) // 邮箱账号列表
|
||||
const accountList = ref<MailAccountApi.MailAccountVO[]>([]) // 邮箱账号列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
|
|
|
@ -10,8 +10,26 @@
|
|||
<el-form-item label="模板内容" prop="content">
|
||||
<Editor :model-value="formData.content" height="150px" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="收件邮箱" prop="mail">
|
||||
<el-input v-model="formData.mail" placeholder="请输入收件邮箱" />
|
||||
<el-form-item label="收件邮箱" prop="toMails">
|
||||
<el-input-tag
|
||||
v-model="formData.toMails"
|
||||
placeholder="请输入收件邮箱,多个邮箱用回车分隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送邮箱" prop="ccMails">
|
||||
<el-input-tag
|
||||
v-model="formData.ccMails"
|
||||
placeholder="请输入抄送邮箱,多个邮箱用回车分隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密送邮箱" prop="bccMails">
|
||||
<el-input-tag
|
||||
v-model="formData.bccMails"
|
||||
placeholder="请输入密送邮箱,多个邮箱用回车分隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-for="param in formData.params"
|
||||
|
@ -43,12 +61,13 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加
|
|||
const formData = ref({
|
||||
content: '',
|
||||
params: {},
|
||||
mail: '',
|
||||
toMails: [],
|
||||
ccMails: [],
|
||||
bccMails: [],
|
||||
templateCode: '',
|
||||
templateParams: new Map()
|
||||
})
|
||||
const formRules = reactive({
|
||||
mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
|
||||
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
|
||||
templateParams: {}
|
||||
})
|
||||
|
@ -105,7 +124,9 @@ const resetForm = () => {
|
|||
formData.value = {
|
||||
content: '',
|
||||
params: {},
|
||||
mail: '',
|
||||
toMails: [],
|
||||
ccMails: [],
|
||||
bccMails: [],
|
||||
templateCode: '',
|
||||
templateParams: new Map()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue