hrp-ui-base 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/components.cjs +1 -1
  2. package/dist/components.es.js +4 -4
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.es.js +83 -67
  5. package/dist/style.css +1 -1
  6. package/package.json +15 -4
  7. package/packages/approval-process/.eslintrc +7 -0
  8. package/packages/approval-process/README.md +130 -0
  9. package/packages/approval-process/index.ts +65 -0
  10. package/packages/approval-process/package.json +17 -0
  11. package/packages/approval-process/packages/components/choose-member/BO/departBo.ts +8 -0
  12. package/packages/approval-process/packages/components/choose-member/BO/memberBo.ts +14 -0
  13. package/packages/approval-process/packages/components/choose-member/BO/role.svg +1 -0
  14. package/packages/approval-process/packages/components/choose-member/BO/roleBo.ts +16 -0
  15. package/packages/approval-process/packages/components/choose-member/index.vue +497 -0
  16. package/packages/approval-process/packages/components/choose-member/styles/common.scss +134 -0
  17. package/packages/approval-process/packages/components/comment/assets/avatar.svg +1 -0
  18. package/packages/approval-process/packages/components/comment/assets/download.svg +4 -0
  19. package/packages/approval-process/packages/components/comment/assets/mp3.svg +11 -0
  20. package/packages/approval-process/packages/components/comment/assets/mp4.svg +11 -0
  21. package/packages/approval-process/packages/components/comment/assets/other.svg +11 -0
  22. package/packages/approval-process/packages/components/comment/assets/pdf.svg +11 -0
  23. package/packages/approval-process/packages/components/comment/assets/ppt.svg +11 -0
  24. package/packages/approval-process/packages/components/comment/assets/preview.svg +4 -0
  25. package/packages/approval-process/packages/components/comment/assets/remove.png +0 -0
  26. package/packages/approval-process/packages/components/comment/assets/word.svg +11 -0
  27. package/packages/approval-process/packages/components/comment/assets/xls.svg +11 -0
  28. package/packages/approval-process/packages/components/comment/index.vue +843 -0
  29. package/packages/approval-process/packages/components/comment/modules/previewMp.vue +54 -0
  30. package/packages/approval-process/packages/components/comment/styles/comment-style.scss +304 -0
  31. package/packages/approval-process/packages/components/image-cropper/index.vue +202 -0
  32. package/packages/approval-process/packages/components/sign/index.vue +766 -0
  33. package/packages/approval-process/packages/components/sign/modules/full-screen-sign.vue +20 -0
  34. package/packages/approval-process/packages/components/urge-dialog/index.vue +121 -0
  35. package/packages/approval-process/packages/components/vue-esign/index.vue +289 -0
  36. package/packages/approval-process/packages/flow/approve-component/approve-success.vue +82 -0
  37. package/packages/approval-process/packages/flow/approve-component/route-params-error.vue +49 -0
  38. package/packages/approval-process/packages/flow/approve-component/submit-success.vue +59 -0
  39. package/packages/approval-process/packages/flow/examine-approve-itr.vue +978 -0
  40. package/packages/approval-process/packages/flow/examine-approve.vue +1065 -0
  41. package/packages/approval-process/packages/flow/form-component/associated-approval-form/README.md +38 -0
  42. package/packages/approval-process/packages/flow/form-component/associated-approval-form/assets/avatar.svg +1 -0
  43. package/packages/approval-process/packages/flow/form-component/associated-approval-form/index.vue +159 -0
  44. package/packages/approval-process/packages/flow/form-component/associated-approval-form/modules/associated-approval-form-mobile.vue +297 -0
  45. package/packages/approval-process/packages/flow/form-component/associated-approval-form/modules/associated-approval-form-pc.vue +314 -0
  46. package/packages/approval-process/packages/flow/form-component/associated-approval-form/style/curd-area-mobile.scss +110 -0
  47. package/packages/approval-process/packages/flow/form-component/associated-approval-form/style/curd-area.scss +96 -0
  48. package/packages/approval-process/packages/flow/form-component/associated-approval-form/style/out-put.scss +48 -0
  49. package/packages/approval-process/packages/flow/form-component/show-sign.vue +27 -0
  50. package/packages/approval-process/packages/flow/styles/common.scss +134 -0
  51. package/packages/approval-process/packages/flow/styles/examine-approve-pc.scss +465 -0
  52. package/packages/approval-process/packages/flow/styles/examine-approve-phone.scss +293 -0
  53. package/packages/approval-process/packages/flow/styles/submit-approve-pc.scss +379 -0
  54. package/packages/approval-process/packages/flow/styles/submit-approve-phone.scss +337 -0
  55. package/packages/approval-process/packages/flow/submit-approve-itr.vue +623 -0
  56. package/packages/approval-process/packages/flow/submit-approve.vue +628 -0
  57. package/packages/approval-process/packages/flow/systemCom/BO/departBo.ts +8 -0
  58. package/packages/approval-process/packages/flow/systemCom/BO/memberBo.ts +14 -0
  59. package/packages/approval-process/packages/flow/systemCom/BO/role.svg +1 -0
  60. package/packages/approval-process/packages/flow/systemCom/BO/roleBo.ts +16 -0
  61. package/packages/approval-process/packages/flow/systemCom/choose-depart.vue +399 -0
  62. package/packages/approval-process/packages/flow/systemCom/choose-member-phone.vue +515 -0
  63. package/packages/approval-process/packages/flow/systemCom/choose-member.vue +565 -0
  64. package/packages/approval-process/packages/flow/systemCom/choose-role.vue +301 -0
  65. package/packages/approval-process/packages/styles/common.scss +134 -0
  66. package/packages/approval-process/packages/styles/flex-common.scss +118 -0
  67. package/packages/approval-process/packages/styles/flexCommon.scss +73 -0
  68. package/packages/approval-process/packages/styles/main.css +216 -0
  69. package/packages/approval-process/packages/styles/router-page.scss +371 -0
  70. package/packages/approval-process/packages/styles/style-set.ts +207 -0
  71. package/packages/approval-process/packages/types.d.ts +4 -0
  72. package/packages/approval-process/packages/utils/base.ts +15 -0
  73. package/packages/approval-process/packages/utils/crypto.ts +67 -0
  74. package/packages/approval-process/packages/utils/ddUtils.ts +56 -0
  75. package/packages/approval-process/packages/utils/debounce.ts +36 -0
  76. package/packages/approval-process/packages/utils/des.js +1107 -0
  77. package/packages/approval-process/packages/utils/download.ts +38 -0
  78. package/packages/approval-process/packages/utils/editor.ts +37 -0
  79. package/packages/approval-process/packages/utils/getEnv.ts +36 -0
  80. package/packages/approval-process/packages/utils/os.ts +20 -0
  81. package/packages/approval-process/packages/utils/path-util.ts +31 -0
  82. package/packages/approval-process/packages/utils/routerUtil.ts +304 -0
  83. package/packages/approval-process/packages/utils/string-utils.ts +13 -0
  84. package/packages/approval-process/packages/utils/throttle.ts +44 -0
  85. package/packages/approval-process/packages/utils/validate.ts +92 -0
  86. package/packages/approval-process/packages/utils/ws.ts +218 -0
  87. package/packages/approval-process/tsconfig.json +72 -0
  88. package/packages/approval-process/vite.config.ts +56 -0
  89. package/src/api/bms/flow/FlowCommentController.ts +50 -0
  90. package/src/api/bms/flow/FlowInstanceController.ts +167 -0
  91. package/src/api/bms/flow/FlowProcessController.ts +55 -0
  92. package/src/api/bms/flow/FlowSheetController.ts +27 -0
  93. package/src/api/bms/flow/FlowSignController.ts +76 -0
  94. package/src/api/bms/flow/bo/AssociatedApprovalBO.ts +15 -0
  95. package/src/api/bms/flow/bo/AssociatedApprovalVO.ts +35 -0
  96. package/src/api/bms/flow/bo/AuditBaseInfo.ts +39 -0
  97. package/src/api/bms/flow/bo/ButtonVo.ts +7 -0
  98. package/src/api/bms/flow/bo/DingCallbackBo.ts +13 -0
  99. package/src/api/bms/flow/bo/FlowCommentBO.ts +12 -0
  100. package/src/api/bms/flow/bo/FlowCommentVO.ts +23 -0
  101. package/src/api/bms/flow/bo/FlowInstanceBO.ts +14 -0
  102. package/src/api/bms/flow/bo/FlowNodeVO.ts +24 -0
  103. package/src/api/bms/flow/bo/FlowNoticeBO.ts +17 -0
  104. package/src/api/bms/flow/bo/FlowProcessBO.ts +13 -0
  105. package/src/api/bms/flow/bo/FlowProcessVO.ts +9 -0
  106. package/src/api/bms/flow/bo/FlowReplyVO.ts +23 -0
  107. package/src/api/bms/flow/bo/FlowSheetVO.ts +16 -0
  108. package/src/api/bms/flow/bo/InstanceBackBO.ts +9 -0
  109. package/src/api/bms/flow/bo/InstanceNodeBO.ts +13 -0
  110. package/src/api/bms/flow/bo/InstanceNodeVO.ts +21 -0
  111. package/src/api/bms/flow/bo/NodeShowVo.ts +19 -0
  112. package/src/api/bms/flow/bo/ReviewBO.ts +11 -0
  113. package/src/api/bms/flow/bo/SheetSearchBO.ts +11 -0
  114. package/src/api/bms/flow/bo/SystemVO.ts +7 -0
  115. package/src/api/bms/flow/bo/TransmitBO.ts +9 -0
  116. package/src/api/bms/flow/bo/UrgeFlowBO.ts +7 -0
  117. package/src/api/bms/flow/bo/UserBaseInfoVo.ts +13 -0
  118. package/src/api/bms/flow/bo/UserInfoVo.ts +44 -0
  119. package/src/api/bms/flow/bo/UserResultVo.ts +17 -0
  120. package/src/api/bms/flow/bo/ValueMapBo.ts +7 -0
  121. package/src/api/bms/home/HomeController.ts +106 -0
  122. package/src/api/bms/home/bo/CollectedMenuBo.ts +9 -0
  123. package/src/api/bms/home/bo/DeptBaseVo.ts +9 -0
  124. package/src/api/bms/home/bo/DomainVO.ts +13 -0
  125. package/src/api/bms/home/bo/FunPermission.ts +17 -0
  126. package/src/api/bms/home/bo/GroupVO.ts +10 -0
  127. package/src/api/bms/home/bo/HomeMenu.ts +22 -0
  128. package/src/api/bms/home/bo/HomeMenuVo.ts +11 -0
  129. package/src/api/bms/home/bo/PersonalizationBo.ts +17 -0
  130. package/src/api/bms/home/bo/PersonalizationVo.ts +19 -0
  131. package/src/api/bms/home/bo/PhoneMenu.ts +14 -0
  132. package/src/api/bms/home/bo/SearchMenuVo.ts +10 -0
  133. package/src/api/bms/home/bo/UserBaseInfoVo.ts +11 -0
  134. package/src/api/hrms/dept/bo/DeptBaseVo.ts +6 -0
  135. package/src/api/hrms/dept/bo/DeptTreeVo.ts +23 -0
  136. package/src/api/hrms/dept/bo/DeptUserVo.ts +9 -0
  137. package/src/api/hrms/dept/bo/DeptUsersVo.ts +10 -0
  138. package/src/api/hrms/dept/bo/DeptVo.ts +19 -0
  139. package/src/api/hrms/dept/bo/UserBaseInfoVo.ts +8 -0
  140. package/src/api/hrms/dept/deptController.ts +100 -0
  141. package/src/api/hrms/role/bo/RoleBaseVo.ts +6 -0
  142. package/src/api/hrms/role/bo/RoleGroupVo.ts +11 -0
  143. package/src/api/hrms/role/bo/RoleVo.ts +22 -0
  144. package/src/api/hrms/role/roleController.ts +19 -0
  145. package/src/api/hrms/user/bo/UserBaseInfoVo.ts +11 -0
  146. package/src/api/hrms/user/bo/UserCardVO.ts +14 -0
  147. package/src/api/hrms/user/bo/UserDeptBO.ts +13 -0
  148. package/src/api/hrms/user/bo/UserRoleBO.ts +11 -0
  149. package/src/api/hrms/user/bo/UserVO.ts +23 -0
  150. package/src/api/hrms/user/userController.ts +86 -0
  151. package/src/api/work-order-pc/flow/Bo/ValueMapBo.ts +7 -0
  152. package/src/api/work-order-pc/flow/ItrFlowProcessController.ts +42 -0
  153. package/src/api/work-order-pc/flow/Vo/FlowNodeVO.ts +24 -0
  154. package/src/api/work-order-pc/flow/Vo/UserInfoVo.ts +44 -0
  155. package/src/assets/process/agree.png +0 -0
  156. package/src/assets/process/defaultAvatar.png +0 -0
  157. package/src/assets/process/drawer/departManagerJump.png +0 -0
  158. package/src/assets/process/drawer/manyLevel1close.svg +10 -0
  159. package/src/assets/process/drawer/manyLevel1open.svg +7 -0
  160. package/src/assets/process/drawer/manyLevel2close.svg +7 -0
  161. package/src/assets/process/drawer/manyLevel2open.svg +7 -0
  162. package/src/assets/process/inProcess.png +0 -0
  163. package/src/assets/process/manyPeople.png +0 -0
  164. package/src/assets/process/manyPeople2.png +0 -0
  165. package/src/assets/process/reject.png +0 -0
  166. package/src/assets/process/revoke.png +0 -0
  167. package/src/components/annex-img-upload/index.vue +4 -4
  168. package/src/components/annex-img-upload/modules/previewMp.vue +1 -1
  169. package/src/components/annex-upload/index.vue +4 -4
  170. package/src/components/annex-upload/modules/previewMp.vue +1 -1
  171. package/src/components/annex-upload-weijian/index.vue +4 -4
  172. package/src/components/annex-upload-weijian/modules/previewMp.vue +1 -1
  173. package/src/components/list-search-content/components/search-date-range.vue +1 -1
  174. package/src/components/list-search-content/components/search-date-time-range.vue +1 -1
  175. package/src/components/list-search-content/components/search-date.vue +1 -1
  176. package/src/components/list-search-content/components/search-input-number.vue +1 -1
  177. package/src/components/list-search-content/components/search-input.vue +1 -1
  178. package/src/components/list-search-content/components/search-multiple_select.vue +1 -1
  179. package/src/components/list-search-content/components/search-select.vue +1 -1
  180. package/src/components/list-search-content/index.vue +6 -6
  181. package/src/components/list-search-content/modules/add-views-package-dialog.vue +3 -3
  182. package/src/components/list-search-content/modules/search-condition-area.vue +2 -2
  183. package/src/components/list-search-content/modules/views-package-manage-dialog.vue +5 -5
  184. package/src/index.ts +1 -0
  185. package/src/utils/dd-utils.ts +1 -1
  186. package/src/utils/get-dict.ts +2 -2
  187. package/src/utils/permit-utils.ts +2 -2
  188. package/src/api/bms/dict/bo/TotalDictSearchBO.ts +0 -8
  189. package/src/api/bms/file/FileExportController.ts +0 -18
  190. package/src/api/bms/file/bo/FileExportVO.ts +0 -17
@@ -0,0 +1,766 @@
1
+ <template>
2
+ <div class="flexColumnStart bigbox signComponentCommonBoxWeight">
3
+ <div v-if="!toSignStatus" class="flexRowStart signbox">
4
+ <div v-if="isSign" class="haveSign">
5
+ <div class="signImgBox">
6
+ <el-image style="width: 133px; height: 100px" :src="nowBindUrlMsg" fit="contain" v-if="nowBind" />
7
+ <!-- 未签名提示 -->
8
+ <div v-else class="no-sign-tip">
9
+ <svg class="tip-icon" viewBox="0 0 1024 1024" width="32" height="32">
10
+ <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="#d9d9d9"/>
11
+ <path d="M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm32 120h64c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-64c-4.4 0-8-3.6-8-8V464c0-4.4 3.6-8 8-8z" fill="#d9d9d9"/>
12
+ </svg>
13
+ <span class="tip-text">当前还未进行签字</span>
14
+ </div>
15
+ </div>
16
+ <el-button class="sign-btn" type="primary" @click="handleReSignClick">{{ nowBind ? "重签" : "去签字" }}</el-button>
17
+ </div>
18
+ <div v-else class="unHaveSign">
19
+ <div class="unHaveSignUp">
20
+ <div class="qrCodeImgBox">
21
+ <QrcodeVue :size="143" :value="codeContent" class="order-print-big-qrcode" ref="QrCodeRef" />
22
+ <!-- 已扫描状态覆盖层 -->
23
+ <div v-if="ifScanned" class="qrcode-status-overlay scanned">
24
+ <svg class="status-icon" viewBox="0 0 1024 1024" width="24" height="24">
25
+ <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" fill="#333333"/>
26
+ </svg>
27
+ <span class="status-text">已扫描</span>
28
+ </div>
29
+ <!-- 已过期状态覆盖层 -->
30
+ <div v-if="ifExpired" class="qrcode-status-overlay expired" @click="handleReSignClick">
31
+ <svg class="status-icon" viewBox="0 0 1024 1024" width="24" height="24">
32
+ <path d="M960 416V192l-73.056 73.056a447.712 447.712 0 0 0-373.6-201.088C265.92 63.968 65.312 264.544 65.312 512S265.92 960.032 513.344 960.032a448.064 448.064 0 0 0 415.232-279.488 38.368 38.368 0 1 0-70.272-29.568 371.36 371.36 0 0 1-344.96 232.064c-205.408 0-371.36-165.984-371.36-371.04 0-205.088 165.952-371.04 371.36-371.04 108.896 0 206.752 47.2 274.464 122.016L736 314.016h224a32 32 0 0 0 32-32z" fill="#b0b0b0"/>
33
+ </svg>
34
+ <span class="status-text">二维码已过期,点击刷新</span>
35
+ </div>
36
+ </div>
37
+ <div class="qrCodeBtnBox">
38
+ <el-button type="primary" size="default" @click="handleReturnClick">取消</el-button>
39
+ </div>
40
+ </div>
41
+ <div class="uploadContent">使用钉钉扫码签名或点击上传</div>
42
+ <div class="uploadContent">为保证签名识别效果最佳,请选择纯白色背景、减少阴影的图片</div>
43
+ <div class="upload-link">
44
+ <a href="javascript:;" @click="triggerFileInput">
45
+ 已有签名?点击上传
46
+ </a>
47
+ </div>
48
+ <!-- 隐藏的 file input -->
49
+ <input
50
+ type="file"
51
+ ref="fileInput"
52
+ style="display: none;"
53
+ accept=".jpg,.jpeg,.png,.bmp,.raw,.heif,.tiff,.gif"
54
+ @change="handleFileChange"
55
+ >
56
+ </div>
57
+ </div>
58
+ <FullScreenSign v-if="fullScreenVisibly"></FullScreenSign>
59
+ <!-- 图片裁剪弹框 -->
60
+ <ImageCropper ref="imageCropperRef" @success="handleCropSuccess"></ImageCropper>
61
+ </div>
62
+ </template>
63
+ <script lang="ts" setup>
64
+ import { ref, onMounted, watch, onUnmounted } from 'vue'
65
+ import { IsPc } from '../../utils/getEnv'
66
+
67
+ import { HttpUtil } from "hrp-ui-base";
68
+ import { getUploadFileMsg } from '../../utils/base'
69
+ import { ElMessage } from 'element-plus'
70
+ import ImageCropper from '../image-cropper/index.vue'
71
+ const props = defineProps({
72
+ // 输出外边的值 同时也是 传入上次的值 ,数据类型是url的字符串
73
+ modelValue: {
74
+ type: String,
75
+ require: false,
76
+ default: ''
77
+ },
78
+ // 类型
79
+ type: {
80
+ type: String,
81
+ require: false,
82
+ default: ''
83
+ },
84
+ // 展示的签字图片的宽度
85
+ width: {
86
+ type: Number,
87
+ require: false
88
+ },
89
+ // 展示的签字图片的高度
90
+ height: {
91
+ type: Number,
92
+ require: false
93
+ },
94
+ // 谁的签字,备注alt
95
+ signOwner: {
96
+ type: String,
97
+ require: false,
98
+ default: ''
99
+ },
100
+ disabled: {
101
+ type: Boolean,
102
+ require: false,
103
+ default: false
104
+ },
105
+ uploadImgId: {
106
+ type: String,
107
+ require: false,
108
+ default: ''
109
+ }
110
+ })
111
+ const emits = defineEmits(['update:modelValue', 'uploadUploadImgId'])
112
+
113
+
114
+ const nowBind = ref<string>('')
115
+ const nowBindUrlMsg = ref<string>('')
116
+ const toSignStatus = ref<boolean>(false)
117
+ const changeSignStatus = (status: boolean) => {
118
+ toSignStatus.value = status
119
+ reset()
120
+ }
121
+ const currentImgId = ref<string>('')
122
+ watch(() => { return props.uploadImgId }, () => {
123
+ currentImgId.value = props.uploadImgId
124
+
125
+ }, { deep: true })
126
+ var canvas: any = document.getElementById("myCanvas")
127
+ console.dir(canvas)
128
+ var firstDraw = true;
129
+ var ifSign = false;
130
+ const canvasHeight = ref<number>(375)
131
+ const canvasWidth = ref<number>(500)
132
+ function clean() {
133
+ ifSign = false
134
+ }
135
+ function reset() {
136
+ ifSign = false
137
+ }
138
+ const ResizeInitFun = () => {
139
+ if (window.innerWidth < 800) {
140
+ if (IsPc()) {
141
+ canvasWidth.value = (window.innerWidth - 60)
142
+ canvasHeight.value = ((window.innerWidth - 60) * 0.75)
143
+ } else {
144
+ canvasWidth.value = (window.innerWidth - 60)
145
+ canvasHeight.value = ((window.innerWidth - 60) * 0.75)
146
+ }
147
+
148
+ } else {
149
+ }
150
+ firstDraw = true
151
+ }
152
+ const cancelSign = () => {
153
+ clean()
154
+ changeSignStatus(false)
155
+ }
156
+ const fullScreenVisibly = ref<boolean>(false)
157
+
158
+ onMounted(() => {
159
+ ResizeInitFun()
160
+ window.removeEventListener("resize", ResizeInitFun)
161
+ window.addEventListener("resize", ResizeInitFun)
162
+ })
163
+ watch(() => { props.modelValue }, () => {
164
+ nowBind.value = props.modelValue
165
+ nowBindUrlMsg.value = props.modelValue
166
+ }, { deep: true, immediate: true })
167
+ watch(nowBind, () => {
168
+ isSign.value = true
169
+ })
170
+ import FileUploadController from "hrp-ui-base/api/bms/file/FileUploadController"
171
+ // import { useUserConfigStore } from "@/stores";
172
+ import QrcodeVue from 'qrcode.vue';
173
+ import FlowSignController from "hrp-ui-base/api/bms/flow/FlowSignController"
174
+ // const userStore = useUserConfigStore();
175
+
176
+ const pageId = ref<string>('01-01-04-001')
177
+ const uploadOss = async (file: any) => {
178
+ const fileData = file;
179
+ FileUploadController.upload(fileData, pageId.value).then((data: any) => {
180
+ if (data.code === 200 && data.res) {
181
+ emits('update:modelValue', data.res.url)
182
+ currentImgId.value = data.res.fileId
183
+ emits('uploadUploadImgId', currentImgId.value)
184
+ cancelSign()
185
+ isSign.value = true
186
+ handleReturnClick()
187
+ } else {
188
+ ElMessage.error("数据发生错误,请重新上传!")
189
+ }
190
+ }).catch((err) => {
191
+ ElMessage.error("oss上传发生错误,请重试!")
192
+ console.error(err)
193
+ }).finally(() => {
194
+
195
+ })
196
+ }
197
+ const isSign = ref<boolean> (true)
198
+ const handleReSignClick = async () =>
199
+ {
200
+ generateQrCode()
201
+ await updateQrCodeStatus("unscanned")
202
+ startQueryStatus()
203
+ isSign.value = false
204
+ }
205
+ const handleReturnClick = async () => {
206
+ await updateQrCodeStatus("expired")
207
+ clearInterval(qrCodeTimeRecord.value)
208
+ clearInterval(queryQrCodeTimeRecord.value)
209
+ isSign.value = true
210
+ }
211
+
212
+ const fileInput = ref<HTMLInputElement>()
213
+ const imageCropperRef = ref<any>()
214
+ // 允许的图片格式
215
+ const allowedTypes = ['image/jpeg','image/png','image/bmp','image/raw','image/heif','image/tiff','image/gif'];
216
+ const allowedExtensions = ['.jpg','.jpeg','.png','.bmp','.raw','.heif','.tiff','.gif'];
217
+ // 最大文件大小(2MB)
218
+ const maxSize = 5 * 1024 * 1024
219
+ // 触发文件选择
220
+ const triggerFileInput = () => {
221
+ if (!fileInput.value)return
222
+ fileInput.value.click()
223
+ }
224
+ const handleFileChange = async (event: any) => {
225
+ const file = event.target.files[0]
226
+
227
+ if (!file) return
228
+
229
+ // 验证文件类型
230
+ if (!allowedTypes.includes(file.type)) {
231
+ ElMessage.error(`仅支持 ${allowedExtensions.join(', ')} 格式`)
232
+ resetFileInput()
233
+ return
234
+ }
235
+ // 验证文件大小
236
+ if (file.size > maxSize) {
237
+ const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(1)
238
+ ElMessage.error(`文件大小不能超过 ${maxSizeMB}MB`)
239
+ resetFileInput()
240
+ return
241
+ }
242
+
243
+ try {
244
+ // 先处理图片(黑白化处理)
245
+ const processedBlob = await extractSignatureImageTransparent(file)
246
+
247
+ // 打开裁剪弹框,传入处理后的 Blob
248
+ imageCropperRef.value?.open(processedBlob)
249
+ } catch (e) {
250
+ console.error(e)
251
+ ElMessage.error('签名图片处理失败')
252
+ } finally {
253
+ resetFileInput()
254
+ }
255
+ }
256
+ // 重置 input
257
+ const resetFileInput = () => {
258
+ if (fileInput.value) {
259
+ fileInput.value.value = ''
260
+ }
261
+ }
262
+
263
+ // 裁剪成功回调
264
+ const handleCropSuccess = async (croppedBlob: Blob) => {
265
+ try {
266
+ // 将裁剪后的 Blob 转为 File 对象
267
+ const croppedFile = new File(
268
+ [croppedBlob],
269
+ '签名文件.png',
270
+ { type: 'image/png' }
271
+ )
272
+
273
+ // 上传裁剪后的图片
274
+ await uploadOss(croppedFile)
275
+ } catch (e) {
276
+ console.error(e)
277
+ ElMessage.error('签名图片上传失败')
278
+ }
279
+ }
280
+
281
+ const codeContent = ref<string>('')
282
+ const qrCodeUUID = ref<string>('')
283
+ const generateUUID = () => {
284
+ const timestamp = performance.now().toString(36)
285
+ const random = Math.random().toString(36).slice(2)
286
+ const random2 = Math.random().toString(36).slice(2)
287
+
288
+ return `${timestamp}-${random}-${random2}`.replace(/\./g, '')
289
+ }
290
+ const generateQrCode = () => {
291
+ ifScanned.value = false
292
+ ifExpired.value = false
293
+ qrCodeUUID.value = generateUUID()
294
+ const urlStr = `${window.location.origin}/mobile/login/index.html?host=${localStorage.getItem('TenantHost')}&url=${'/full-screen-sign-process'}?qrCodeUUID=${qrCodeUUID.value}`
295
+ // codeContent.value = 'http://172.16.14.36:5176/#/full-screen-sign-process?qrCodeUUID=' + qrCodeUUID.value
296
+ codeContent.value = urlStr
297
+ }
298
+
299
+ const qrCodeTimeRecord = ref<number>()
300
+ const startPolling = () => {
301
+ qrCodeTimeRecord.value = setInterval(async () => {
302
+ try {
303
+ const data = await FlowSignController.startPolling(qrCodeUUID.value)
304
+ if (data.code === 200 && data.res) {
305
+ clearInterval(qrCodeTimeRecord.value)
306
+ emits('update:modelValue', data.res.url)
307
+ currentImgId.value = data.res.fileId
308
+ emits('uploadUploadImgId', currentImgId.value)
309
+ }
310
+ } catch (e) {
311
+ console.log(false)
312
+ }
313
+ }, 2000)
314
+ }
315
+ const queryQrCodeTimeRecord = ref<number>()
316
+ const startQueryStatus = () => {
317
+ queryQrCodeTimeRecord.value = setInterval(() => {
318
+ FlowSignController.queryQrCodeStatus(qrCodeUUID.value).then(data => {
319
+ if (data.code === 200 && data.res === "scanned") {
320
+ clearInterval(queryQrCodeTimeRecord.value)
321
+ ifScanned.value = true
322
+ startPolling()
323
+ } else if (data.code === 200 && (data.res === null || data.res === "expired")) {
324
+ clearInterval(queryQrCodeTimeRecord.value)
325
+ ifExpired.value = true
326
+ ElMessage.error("二维码已过期,请刷新")
327
+ } else {
328
+ console.log("正在轮询二维码状态")
329
+ }
330
+ })
331
+ }, 2000)
332
+ }
333
+ const ifScanned = ref<boolean>(false)
334
+ const ifExpired = ref<boolean>()
335
+ const updateQrCodeStatus = (status: string) => {
336
+ if (status === 'scanned') {
337
+ ifScanned.value = true
338
+ } else if (status === 'expired') {
339
+ ifExpired.value = true
340
+ }
341
+ FlowSignController.updateQrCodeStatus(status, qrCodeUUID.value).then(data => {
342
+ if (data.code === 200 && data.res) {
343
+ console.log("二维码状态更新成功")
344
+ } else if (data.code === 200 && !data.res) {
345
+ ElMessage.error(data.msg || "二维码失效请刷新后重试")
346
+ }
347
+ })
348
+ }
349
+ const dialogClose = () => {
350
+ clearInterval(qrCodeTimeRecord.value)
351
+ clearInterval(queryQrCodeTimeRecord.value)
352
+ ifScanned.value = false
353
+ ifExpired.value = false
354
+ isSign.value = true
355
+ }
356
+
357
+ /**
358
+ * 将签名图片处理成:白色背景 + 黑色笔迹
359
+ */
360
+ const extractSignatureImageTransparent = (file: File): Promise<Blob> => {
361
+ return new Promise((resolve, reject) => {
362
+ const img = new Image()
363
+ img.onload = () => {
364
+ const canvas = document.createElement('canvas')
365
+ const ctx = canvas.getContext('2d')!
366
+
367
+ canvas.width = img.width
368
+ canvas.height = img.height
369
+ ctx.drawImage(img, 0, 0)
370
+
371
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
372
+ const data = imageData.data
373
+
374
+ // 计算灰度
375
+ const grays: number[] = []
376
+ let min = 255, max = 0
377
+ for (let i = 0; i < data.length; i += 4) {
378
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]
379
+ grays.push(gray)
380
+ min = Math.min(min, gray)
381
+ max = Math.max(max, gray)
382
+ }
383
+
384
+ const threshold = min + (max - min) * 0.35
385
+
386
+ // 设置 alpha 通道
387
+ for (let i = 0; i < data.length; i += 4) {
388
+ const gray = grays[i / 4]
389
+ if (gray > threshold) {
390
+ data[i + 3] = 0 // 背景透明
391
+ } else {
392
+ data[i] = data[i + 1] = data[i + 2] = 0 // 签名黑色
393
+ data[i + 3] = 255 // 签名不透明
394
+ }
395
+ }
396
+
397
+ enhanceContrast(data)
398
+ for (let i = 0; i < 2; i++) {
399
+ dilateAlpha(data, canvas.width, canvas.height, 1)
400
+ fillGapsAlpha(data, canvas.width, canvas.height)
401
+ }
402
+ removeHorizontalLines(data, canvas.width, canvas.height)
403
+ ctx.putImageData(imageData, 0, 0)
404
+
405
+ canvas.toBlob(
406
+ blob => blob ? resolve(blob) : reject(),
407
+ 'image/png',
408
+ 1
409
+ )
410
+ }
411
+
412
+ img.onerror = reject
413
+ img.src = URL.createObjectURL(file)
414
+ })
415
+ }
416
+
417
+ const dilateAlpha = (data: Uint8ClampedArray, width: number, height: number, radius = 1) => {
418
+ const copy = new Uint8ClampedArray(data)
419
+ const idx = (x: number, y: number) => (y * width + x) * 4
420
+
421
+ for (let y = radius; y < height - radius; y++) {
422
+ for (let x = radius; x < width - radius; x++) {
423
+ const center = idx(x, y)
424
+ if (copy[center + 3] === 255) continue
425
+
426
+ // 检查周围 radius 范围内是否有签名像素
427
+ let hasOpaqueNeighbor = false
428
+ for (let dy = -radius; dy <= radius && !hasOpaqueNeighbor; dy++) {
429
+ for (let dx = -radius; dx <= radius; dx++) {
430
+ const i = idx(x + dx, y + dy)
431
+ if (copy[i + 3] === 255) {
432
+ hasOpaqueNeighbor = true
433
+ break
434
+ }
435
+ }
436
+ }
437
+
438
+ if (hasOpaqueNeighbor) {
439
+ // 将 alpha 设为签名不透明,同时保持原颜色
440
+ data[center + 3] = 255
441
+ for (let c = 0; c < 3; c++) {
442
+ data[center + c] = copy[center + c] // 保持原色
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+
450
+ const fillGapsAlpha = (data: Uint8ClampedArray, width: number, height: number) => {
451
+ const copy = new Uint8ClampedArray(data)
452
+ const idx = (x: number, y: number) => (y * width + x) * 4
453
+
454
+ for (let y = 1; y < height - 1; y++) {
455
+ for (let x = 1; x < width - 1; x++) {
456
+ const i = idx(x, y)
457
+ if (copy[i + 3] === 255) continue // 空隙才处理
458
+
459
+ let opaqueCount = 0
460
+ let r = [0,0,0]
461
+ for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
462
+ const neighbor = idx(x + dx, y + dy)
463
+ if (copy[neighbor + 3] === 255) {
464
+ opaqueCount++
465
+ for (let c = 0; c < 3; c++) r[c] += copy[neighbor + c]
466
+ }
467
+ }
468
+
469
+ if (opaqueCount >= 2) { // 如果周围至少两个像素是签名,则填充
470
+ data[i + 3] = 255
471
+ for (let c = 0; c < 3; c++) data[i + c] = Math.round(r[c] / opaqueCount)
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ const enhanceContrast = (data: Uint8ClampedArray) => {
478
+ for (let i = 0; i < data.length; i += 4) {
479
+ if (data[i + 3] === 255) {
480
+ // gamma增强 + 轻微加粗效果
481
+ for (let c = 0; c < 3; c++) {
482
+ const val = data[i + c] / 255
483
+ data[i + c] = Math.min(255, Math.pow(val, 0.4) * 255) // gamma 0.4,比原先更深
484
+ }
485
+ }
486
+ }
487
+ }
488
+
489
+
490
+ const removeHorizontalLines = (data: Uint8ClampedArray, width: number, height: number) => {
491
+ const threshold = width * 0.7 // 横线长度阈值
492
+ const idx = (x: number, y: number) => (y * width + x) * 4
493
+
494
+ for (let y = 0; y < height; y++) {
495
+ let count = 0
496
+ let startX = 0
497
+ for (let x = 0; x < width; x++) {
498
+ if (data[idx(x, y) + 3] === 255) {
499
+ if (count === 0) startX = x
500
+ count++
501
+ } else {
502
+ if (count > threshold) {
503
+ // 认定为横线噪声,清除
504
+ for (let i = startX; i < x; i++) {
505
+ data[idx(i, y) + 3] = 0
506
+ }
507
+ }
508
+ count = 0
509
+ }
510
+ }
511
+ // 检查行末尾横线
512
+ if (count > threshold) {
513
+ for (let i = startX; i < width; i++) {
514
+ data[idx(i, y) + 3] = 0
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ defineExpose({
521
+ dialogClose: () => dialogClose()
522
+ })
523
+ onUnmounted(() => {
524
+ dialogClose()
525
+ })
526
+ </script>
527
+ <style scoped lang="scss">
528
+ @use '../../styles/common.scss';
529
+
530
+ .bigbox {
531
+ width: 100%;
532
+ }
533
+
534
+ .signbox {
535
+ margin-top: 8px;
536
+ width: 100%;
537
+ }
538
+
539
+ .haveSign {
540
+ display: flex;
541
+ justify-content: center;
542
+ align-items: flex-start;
543
+ width: 100%;
544
+ margin: 0 auto;
545
+ min-height: 197px;
546
+ text-align: center;
547
+ position: relative;
548
+ }
549
+
550
+ .sign-btn {
551
+ position: absolute;
552
+ right: calc(50% - 145px);
553
+ top: 72.5px;
554
+ margin-left: 16px;
555
+ }
556
+
557
+ .unHaveSign {
558
+ width: 100%;
559
+ margin: 0 auto;
560
+ min-height: 197px;
561
+ text-align: center;
562
+ position: relative;
563
+
564
+ .uploadContent {
565
+ margin-bottom: 5px;
566
+ font-size: 11.5px;
567
+ }
568
+
569
+ .upload-link {
570
+ font-weight: 500;
571
+ font-size: 10px;
572
+ color: #409eff;
573
+ text-decoration: none;
574
+ line-height: 1.5;
575
+ letter-spacing: 0.5px;
576
+ word-spacing: 2px;
577
+ text-transform: none;
578
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
579
+ cursor: pointer;
580
+ transition: all 0.3s ease;
581
+ }
582
+ }
583
+
584
+ .signImgBox,.qrCodeImgBox {
585
+ box-sizing: border-box;
586
+ width: 145px;
587
+ height: 145px;
588
+ // border: 1px solid gray;
589
+ overflow: hidden;
590
+ margin: 0 auto 15px;
591
+ position: relative;
592
+
593
+ // 当遮罩层显示时,对底层二维码应用强力视觉破坏
594
+ &:has(.qrcode-status-overlay) {
595
+ .order-print-big-qrcode {
596
+ filter: blur(8px) contrast(0.3) brightness(1.5) saturate(0);
597
+ opacity: 0.15;
598
+ transform: scale(1.2) rotate(5deg);
599
+ }
600
+ }
601
+ }
602
+
603
+ .signImgBox {
604
+ width: 133px;
605
+ height: 100px;
606
+ margin-top: 50px;
607
+ display: flex;
608
+ align-items: center;
609
+ justify-content: center;
610
+
611
+ // 未签名提示样式
612
+ .no-sign-tip {
613
+ display: flex;
614
+ flex-direction: column;
615
+ align-items: center;
616
+ justify-content: center;
617
+ width: 100%;
618
+ height: 100%;
619
+ background: #fafafa;
620
+ border-radius: 4px;
621
+ gap: 8px;
622
+
623
+ .tip-icon {
624
+ flex-shrink: 0;
625
+ }
626
+
627
+ .tip-text {
628
+ font-size: 12px;
629
+ color: #999;
630
+ text-align: center;
631
+ line-height: 1.4;
632
+ padding: 0 8px;
633
+ }
634
+ }
635
+ }
636
+
637
+ .unHaveSignUp {
638
+ display: flex;
639
+ justify-content: center;
640
+ position: relative;
641
+
642
+ .qrCodeBtnBox {
643
+ position: absolute;
644
+ right: calc(50% - 145px);
645
+ top: 72.5px;
646
+ margin-left: 16px;
647
+ display: flex;
648
+ flex-direction: column;
649
+ gap: 8px;
650
+
651
+ .el-button + .el-button {
652
+ margin-left: 0 !important;
653
+ }
654
+
655
+ .el-button {
656
+ min-width: 60px;
657
+ }
658
+ }
659
+
660
+ .uploadContent {
661
+ margin-top: 8px;
662
+ }
663
+ }
664
+
665
+ // 二维码状态覆盖层样式 - PC端简洁风格 + 强力防扫描
666
+ .qrcode-status-overlay {
667
+ position: absolute;
668
+ top: 0;
669
+ left: 0;
670
+ right: 0;
671
+ bottom: 0;
672
+ display: flex;
673
+ flex-direction: column;
674
+ align-items: center;
675
+ justify-content: center;
676
+ gap: 6px;
677
+ animation: fadeIn 0.25s ease-out;
678
+ z-index: 10;
679
+
680
+ // 简洁的单层细密网格
681
+ &::before {
682
+ content: '';
683
+ position: absolute;
684
+ top: 0;
685
+ left: 0;
686
+ right: 0;
687
+ bottom: 0;
688
+ background-image:
689
+ repeating-linear-gradient(
690
+ 0deg,
691
+ transparent,
692
+ transparent 1px,
693
+ currentColor 1px,
694
+ currentColor 2px
695
+ ),
696
+ repeating-linear-gradient(
697
+ 90deg,
698
+ transparent,
699
+ transparent 1px,
700
+ currentColor 1px,
701
+ currentColor 2px
702
+ );
703
+ opacity: 0.25;
704
+ z-index: 1;
705
+ }
706
+
707
+ .status-icon {
708
+ flex-shrink: 0;
709
+ position: relative;
710
+ z-index: 3;
711
+ }
712
+
713
+ .status-text {
714
+ font-size: 12px;
715
+ font-weight: 400;
716
+ letter-spacing: 0.5px;
717
+ line-height: 1;
718
+ position: relative;
719
+ z-index: 3;
720
+ }
721
+
722
+ // 已扫描状态 - 简洁白色
723
+ &.scanned {
724
+ background: rgba(255, 255, 255, 0.96);
725
+ color: rgba(100, 100, 100, 0.3);
726
+
727
+ .status-text {
728
+ color: #333333;
729
+ }
730
+ }
731
+
732
+ // 已过期状态 - 简洁深色
733
+ &.expired {
734
+ background: rgba(40, 40, 40, 0.92);
735
+ color: rgba(180, 180, 180, 0.3);
736
+ cursor: pointer;
737
+
738
+ .status-text {
739
+ color: #e0e0e0;
740
+ }
741
+
742
+ .status-icon path {
743
+ fill: #b0b0b0;
744
+ }
745
+ }
746
+ }
747
+
748
+ // 淡入动画
749
+ @keyframes fadeIn {
750
+ from {
751
+ opacity: 0;
752
+ }
753
+ to {
754
+ opacity: 1;
755
+ }
756
+ }
757
+ </style>
758
+ <style lang="scss">
759
+ .signComponentCommonBoxWeight {
760
+ .el-textarea__inner {
761
+ height: 37px;
762
+ min-height: 37px !important;
763
+ }
764
+
765
+ }
766
+ </style>