Skip to content

Commit 528ebf3

Browse files
committed
Merge branch 'feature/bpm-n' of https://gitee.com/LesanOuO/yudao-ui-admin-vue3 into feature/bpm
# Conflicts: # src/components/SimpleProcessDesignerV2/src/consts.ts
2 parents 3f9408b + 65eee15 commit 528ebf3

File tree

6 files changed

+520
-135
lines changed

6 files changed

+520
-135
lines changed

src/components/ESign/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import ESign from './src/ESign.vue'
2+
3+
export { ESign }

src/components/ESign/src/ESign.vue

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
<template>
2+
<div style="position: relative">
3+
<canvas
4+
ref="canvasRef"
5+
@mousedown="mouseDown"
6+
@mousemove="mouseMove"
7+
@mouseup="mouseUp"
8+
@touchstart="touchStart"
9+
@touchmove="touchMove"
10+
@touchend="touchEnd"
11+
style="border: 1px solid lightgrey; max-width: 100%; display: block"
12+
>
13+
</canvas>
14+
15+
<el-button
16+
style="position: absolute; bottom: 20px; right: 10px"
17+
type="primary"
18+
text
19+
size="small"
20+
@click="reset"
21+
>
22+
<Icon icon="ep:delete" class="mr-5px" />清除
23+
</el-button>
24+
</div>
25+
</template>
26+
27+
<script lang="ts" setup>
28+
import { propTypes } from '@/utils/propTypes'
29+
30+
defineOptions({ name: 'ESign' })
31+
32+
const emits = defineEmits(['update:bgColor'])
33+
const props = defineProps({
34+
// 画布宽度,即导出图片的宽度
35+
width: propTypes.number.def(900),
36+
// 画布高度,即导出图片的高度
37+
height: propTypes.number.def(400),
38+
// 画笔粗细
39+
lineWidth: propTypes.number.def(10),
40+
// 画笔颜色
41+
lineColor: propTypes.string.def('#000000'),
42+
// 画布背景色,为空时画布背景透明
43+
bgColor: propTypes.string.def(''),
44+
// 是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
45+
isCrop: propTypes.bool.def(false),
46+
// 清空画布时是否同时清空设置的背景色
47+
isClearBgColor: propTypes.bool.def(true),
48+
// 生成图片格式
49+
format: propTypes.string.def('image/png'),
50+
// 生成图片质量,0 到 1
51+
quality: propTypes.number.def(1)
52+
})
53+
const canvasRef = ref()
54+
const hasDrew = ref(false)
55+
const resultImg = ref('')
56+
const points = ref<any>([])
57+
const canvasTxt = ref()
58+
const startX = ref(0)
59+
const startY = ref(0)
60+
const isDrawing = ref(false)
61+
const sratio = ref(1)
62+
63+
const ratio = computed(() => {
64+
return props.height / props.width
65+
})
66+
const stageInfo = computed(() => {
67+
return canvasRef.value.getBoundingClientRect()
68+
})
69+
const bgColor = computed(() => {
70+
return props.bgColor ? props.bgColor : 'rgba(255, 255, 255, 0)'
71+
})
72+
73+
watch(
74+
() => bgColor.value,
75+
() => {
76+
if (canvasRef.value) {
77+
canvasRef.value.style.background = bgColor.value
78+
}
79+
},
80+
{
81+
immediate: true
82+
}
83+
)
84+
85+
const resizeHandler = () => {
86+
const canvas = canvasRef.value
87+
canvas.style.width = props.width + 'px'
88+
const realw = parseFloat(window.getComputedStyle(canvas).width)
89+
canvas.style.height = ratio.value * realw + 'px'
90+
canvasTxt.value = canvas.getContext('2d')
91+
canvasTxt.value.scale(1 * sratio.value, 1 * sratio.value)
92+
sratio.value = realw / props.width
93+
canvasTxt.value.scale(1 / sratio.value, 1 / sratio.value)
94+
}
95+
// For PC
96+
const mouseDown = (e) => {
97+
e.preventDefault()
98+
isDrawing.value = true
99+
hasDrew.value = true
100+
let obj = {
101+
x: e.offsetX,
102+
y: e.offsetY
103+
}
104+
drawStart(obj)
105+
}
106+
const mouseMove = (e) => {
107+
e.preventDefault()
108+
if (isDrawing.value) {
109+
let obj = {
110+
x: e.offsetX,
111+
y: e.offsetY
112+
}
113+
drawMove(obj)
114+
}
115+
}
116+
const mouseUp = (e) => {
117+
e.preventDefault()
118+
let obj = {
119+
x: e.offsetX,
120+
y: e.offsetY
121+
}
122+
drawEnd(obj)
123+
isDrawing.value = false
124+
}
125+
// For Mobile
126+
const touchStart = (e) => {
127+
e.preventDefault()
128+
hasDrew.value = true
129+
if (e.touches.length === 1) {
130+
let obj = {
131+
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
132+
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
133+
}
134+
drawStart(obj)
135+
}
136+
}
137+
const touchMove = (e) => {
138+
e.preventDefault()
139+
if (e.touches.length === 1) {
140+
let obj = {
141+
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
142+
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
143+
}
144+
drawMove(obj)
145+
}
146+
}
147+
const touchEnd = (e) => {
148+
e.preventDefault()
149+
if (e.touches.length === 1) {
150+
let obj = {
151+
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
152+
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
153+
}
154+
drawEnd(obj)
155+
}
156+
}
157+
// 绘制
158+
const drawStart = (obj) => {
159+
startX.value = obj.x
160+
startY.value = obj.y
161+
canvasTxt.value.beginPath()
162+
canvasTxt.value.moveTo(startX.value, startY.value)
163+
canvasTxt.value.lineTo(obj.x, obj.y)
164+
canvasTxt.value.lineCap = 'round'
165+
canvasTxt.value.lineJoin = 'round'
166+
canvasTxt.value.lineWidth = props.lineWidth * sratio.value
167+
canvasTxt.value.stroke()
168+
canvasTxt.value.closePath()
169+
points.value.push(obj)
170+
}
171+
const drawMove = (obj) => {
172+
canvasTxt.value.beginPath()
173+
canvasTxt.value.moveTo(startX.value, startY.value)
174+
canvasTxt.value.lineTo(obj.x, obj.y)
175+
canvasTxt.value.strokeStyle = props.lineColor
176+
canvasTxt.value.lineWidth = props.lineWidth * sratio.value
177+
canvasTxt.value.lineCap = 'round'
178+
canvasTxt.value.lineJoin = 'round'
179+
canvasTxt.value.stroke()
180+
canvasTxt.value.closePath()
181+
startY.value = obj.y
182+
startX.value = obj.x
183+
points.value.push(obj)
184+
}
185+
const drawEnd = (obj) => {
186+
canvasTxt.value.beginPath()
187+
canvasTxt.value.moveTo(startX.value, startY.value)
188+
canvasTxt.value.lineTo(obj.x, obj.y)
189+
canvasTxt.value.lineCap = 'round'
190+
canvasTxt.value.lineJoin = 'round'
191+
canvasTxt.value.stroke()
192+
canvasTxt.value.closePath()
193+
points.value.push(obj)
194+
points.value.push({ x: -1, y: -1 })
195+
}
196+
// 生成
197+
const generate = (options) => {
198+
let imgFormat = options && options.format ? options.format : props.format
199+
let imgQuality = options && options.quality ? options.quality : props.quality
200+
const pm = new Promise((resolve, reject) => {
201+
if (!hasDrew.value) {
202+
reject(`Warning: Not Signned!`)
203+
return
204+
}
205+
let resImgData = canvasTxt.value.getImageData(
206+
0,
207+
0,
208+
canvasRef.value.width,
209+
canvasRef.value.height
210+
)
211+
canvasTxt.value.globalCompositeOperation = 'destination-over'
212+
canvasTxt.value.fillStyle = bgColor.value
213+
canvasTxt.value.fillRect(0, 0, canvasRef.value.width, canvasRef.value.height)
214+
resultImg.value = canvasRef.value.toDataURL(imgFormat, imgQuality)
215+
canvasTxt.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
216+
canvasTxt.value.putImageData(resImgData, 0, 0)
217+
canvasTxt.value.globalCompositeOperation = 'source-over'
218+
if (props.isCrop) {
219+
const crop_area = getCropArea(resImgData.data)
220+
let crop_canvas = document.createElement('canvas')
221+
const crop_ctx = crop_canvas.getContext('2d')
222+
crop_canvas.width = crop_area[2] - crop_area[0]
223+
crop_canvas.height = crop_area[3] - crop_area[1]
224+
const crop_imgData = canvasTxt.value.getImageData(...crop_area)
225+
crop_ctx.globalCompositeOperation = 'destination-over'
226+
crop_ctx.putImageData(crop_imgData, 0, 0)
227+
crop_ctx.fillStyle = bgColor.value
228+
crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height)
229+
resultImg.value = crop_canvas.toDataURL(imgFormat, imgQuality)
230+
}
231+
resolve(resultImg.value)
232+
})
233+
return pm
234+
}
235+
const reset = () => {
236+
canvasTxt.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
237+
if (props.isClearBgColor) {
238+
emits('update:bgColor', '')
239+
canvasRef.value.style.background = 'rgba(255, 255, 255, 0)'
240+
}
241+
points.value = []
242+
hasDrew.value = false
243+
resultImg.value = ''
244+
}
245+
const getCropArea = (imgData) => {
246+
let topX = canvasRef.value.width
247+
let btmX = 0
248+
let topY = canvasRef.value.height
249+
let btnY = 0
250+
for (let i = 0; i < canvasRef.value.width; i++) {
251+
for (let j = 0; j < canvasRef.value.height; j++) {
252+
let pos = (i + canvasRef.value.width * j) * 4
253+
if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {
254+
btnY = Math.max(j, btnY)
255+
btmX = Math.max(i, btmX)
256+
topY = Math.min(j, topY)
257+
topX = Math.min(i, topX)
258+
}
259+
}
260+
}
261+
topX++
262+
btmX++
263+
topY++
264+
btnY++
265+
const data = [topX, topY, btmX, btnY]
266+
return data
267+
}
268+
269+
defineExpose({
270+
generate
271+
})
272+
onBeforeMount(() => {
273+
window.addEventListener('resize', resizeHandler)
274+
})
275+
onBeforeUnmount(() => {
276+
window.removeEventListener('resize', resizeHandler)
277+
})
278+
onMounted(() => {
279+
canvasRef.value.height = props.height
280+
canvasRef.value.width = props.width
281+
canvasRef.value.style.background = bgColor.value
282+
resizeHandler()
283+
// 在画板以外松开鼠标后冻结画笔
284+
document.onmouseup = () => {
285+
isDrawing.value = false
286+
}
287+
})
288+
</script>

src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,13 @@ const addNode = (type: number) => {
128128
},
129129
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
130130
childNode: props.childNode,
131-
createTaskListener: {
131+
taskCreateListener: {
132+
enable: false
133+
},
134+
taskAssignListener: {
135+
enable: false
136+
},
137+
taskCompleteListener: {
132138
enable: false
133139
}
134140
}

src/components/SimpleProcessDesignerV2/src/consts.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ export interface SimpleFlowNode {
9898
// 审批节点的审批人与发起人相同时,对应的处理类型
9999
assignStartUserHandlerType?: number
100100
// 创建任务监听器
101-
createTaskListener?: ListenerHandler
101+
taskCreateListener?: ListenerHandler
102+
// 创建任务监听器
103+
taskAssignListener?: ListenerHandler
104+
// 创建任务监听器
105+
taskCompleteListener?: ListenerHandler
102106
// 条件类型
103107
conditionType?: ConditionType
104108
// 条件表达式
@@ -236,15 +240,25 @@ export type AssignEmptyHandler = {
236240
*/
237241
export type ListenerHandler = {
238242
enable: boolean
239-
path: string
240-
header: ListenerMap[]
241-
body: ListenerMap[]
243+
path?: string
244+
header?: ListenerMap[]
245+
body?: ListenerMap[]
242246
}
243247
export type ListenerMap = {
244248
key: string
245249
type: number
246250
value: string
247251
}
252+
export enum ListenerMapTypeEnum {
253+
/**
254+
* 固定值
255+
*/
256+
FIXED_VALUE = 1,
257+
/**
258+
* 表单
259+
*/
260+
FROM_FORM = 2
261+
}
248262
export const LISTENER_MAP_TYPES = [
249263
{
250264
value: 1,

src/components/SimpleProcessDesignerV2/src/node.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
NODE_DEFAULT_NAME,
1515
AssignStartUserHandlerType,
1616
AssignEmptyHandlerType,
17-
FieldPermissionType
17+
FieldPermissionType,
18+
ListenerMap
1819
} from './consts'
1920
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
2021
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
@@ -136,6 +137,18 @@ export type UserTaskFormType = {
136137
timeDuration?: number
137138
maxRemindCount?: number
138139
buttonsSetting: any[]
140+
taskCreateListenerEnable?: boolean
141+
taskCreateListenerPath?: string
142+
taskCreateListenerHeader?: ListenerMap[]
143+
taskCreateListenerBody?: ListenerMap[]
144+
taskAssignListenerEnable?: boolean
145+
taskAssignListenerPath?: string
146+
taskAssignListenerHeader?: ListenerMap[]
147+
taskAssignListenerBody?: ListenerMap[]
148+
taskCompleteListenerEnable?: boolean
149+
taskCompleteListenerPath?: string
150+
taskCompleteListenerHeader?: ListenerMap[]
151+
taskCompleteListenerBody?: ListenerMap[]
139152
}
140153

141154
export type CopyTaskFormType = {

0 commit comments

Comments
 (0)