hrp-ui-base 1.1.0 → 1.2.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hrp-ui-base",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "HRP 前端公共组件、工具方法和基础样式包",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -81,8 +81,10 @@
81
81
  "lodash": "^4.17.21",
82
82
  "mathjs": "^13.1.1",
83
83
  "moment": "^2.29.4",
84
+ "qrcode.vue": "^3.6.0",
84
85
  "qs": "^6.11.0",
85
86
  "sm-crypto": "^0.3.11",
87
+ "vue-cropper": "^1.1.4",
86
88
  "vuedraggable": "^4.1.0"
87
89
  },
88
90
  "devDependencies": {
@@ -77,6 +77,8 @@
77
77
  <ExportCenterDrawer v-model:visible="exportDrawerVisible" />
78
78
  <!-- 审批中心抽屉 -->
79
79
  <ApprovalCenterDrawer v-model:visible="approvalDrawerVisible" />
80
+ <!-- 个人签名弹窗 -->
81
+ <PersonalSignDialog ref="personalSignDialogRef" />
80
82
  </template>
81
83
 
82
84
  <script lang="ts" setup>
@@ -97,6 +99,7 @@ import SideMenu from "./sideMenu.vue";
97
99
  import PersonalizationGuideDialog from "./personalization-guide-dialog.vue";
98
100
  import ExportCenterDrawer from "./ExportCenterDrawer.vue";
99
101
  import ApprovalCenterDrawer from "./ApprovalCenterDrawer.vue";
102
+ import PersonalSignDialog from "./personal-sign/PersonalSignDialog.vue";
100
103
  import { useLayoutConfigStore } from "./stores/useLayoutConfigStore";
101
104
  import { useLayoutMenuStore } from "./stores/useLayoutMenuStore";
102
105
  import { useLayoutTabsStore } from "./stores/useLayoutTabsStore";
@@ -273,8 +276,9 @@ const handleMessageUpdateCount = async () => {
273
276
  updateNum.value += 1;
274
277
  };
275
278
 
279
+ const personalSignDialogRef = ref<InstanceType<typeof PersonalSignDialog>>();
276
280
  const handlePersonalSign = () => {
277
- // TODO: 打开签名弹窗
281
+ personalSignDialogRef.value?.open();
278
282
  };
279
283
 
280
284
  const handleCleanCache = () => {
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <el-dialog v-model="dialogVisible" title="裁剪签名图片" width="560px" @close="closeDialog">
3
+ <div class="cropper-content">
4
+ <div class="cropper-container">
5
+ <vue-cropper
6
+ v-if="showCropper"
7
+ class="crop"
8
+ ref="cropperRef"
9
+ :autoCrop="option.autoCrop"
10
+ :autoCropHeight="option.autoCropHeight"
11
+ :autoCropWidth="option.autoCropWidth"
12
+ :canMove="option.canMove"
13
+ :canScale="option.canScale"
14
+ :centerBox="option.centerBox"
15
+ :fixed="option.fixed"
16
+ :fixedBox="option.fixedBox"
17
+ :fixedNumber="option.fixedNumber"
18
+ :img="currentFile"
19
+ :info-true="option.infoTrue"
20
+ :mode="option.mode"
21
+ :origin="option.origin"
22
+ :outputSize="option.outputSize"
23
+ :outputType="option.outputType"
24
+ ></vue-cropper>
25
+ </div>
26
+
27
+ <!-- 旋转控制按钮 -->
28
+ <div class="rotate-controls">
29
+ <el-button-group>
30
+ <el-button size="small" @click="rotateLeft" :icon="RefreshLeft">
31
+ 左旋转90°
32
+ </el-button>
33
+ <el-button size="small" @click="rotateRight" :icon="RefreshRight">
34
+ 右旋转90°
35
+ </el-button>
36
+ </el-button-group>
37
+ </div>
38
+ </div>
39
+
40
+ <template #footer>
41
+ <el-button @click="dialogVisible = false">取消</el-button>
42
+ <el-button type="primary" @click="confirmCrop" :loading="submitLoading">确定</el-button>
43
+ </template>
44
+ </el-dialog>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { ref, nextTick } from "vue";
49
+ import { ElMessage } from "element-plus";
50
+ import { RefreshLeft, RefreshRight } from '@element-plus/icons-vue';
51
+
52
+ const emits = defineEmits(["success"]);
53
+
54
+ const submitLoading = ref<boolean>(false)
55
+
56
+ const option = ref({
57
+ autoCrop: true,
58
+ autoCropHeight: 100,
59
+ autoCropWidth: 133,
60
+ canMove: true,
61
+ canScale: true,
62
+ centerBox: true,
63
+ fixed: true,
64
+ fixedBox: false,
65
+ fixedNumber: [133, 100],
66
+ infoTrue: true,
67
+ mode: "contain",
68
+ origin: false,
69
+ outputSize: 1,
70
+ outputType: "png",
71
+ });
72
+
73
+ const showCropper = ref(false);
74
+ const dialogVisible = ref(false);
75
+ const cropperRef = ref<any>();
76
+ const currentFile = ref<any>();
77
+
78
+ // 打开裁剪弹框
79
+ const open = async (file: File | Blob) => {
80
+ try {
81
+ dialogVisible.value = true;
82
+ currentFile.value = await fileToBase64(file);
83
+ await nextTick();
84
+ setTimeout(() => {
85
+ showCropper.value = true;
86
+ }, 10);
87
+ } catch (error) {
88
+ console.error('打开裁剪器失败:', error);
89
+ ElMessage.error("打开裁剪器失败,请重试!");
90
+ }
91
+ };
92
+
93
+ // 文件转 Base64
94
+ const fileToBase64 = (file: File | Blob): Promise<string> => {
95
+ return new Promise((resolve, reject) => {
96
+ const reader = new FileReader();
97
+ reader.readAsDataURL(file);
98
+ reader.onload = () => resolve(reader.result as string);
99
+ reader.onerror = (error) => reject(error);
100
+ });
101
+ };
102
+
103
+ // Base64 转 Blob
104
+ function base64ToBlob(base64String: string, mimeType: string): Blob {
105
+ const byteString = atob(base64String.split(',')[1]);
106
+ const ab = new ArrayBuffer(byteString.length);
107
+ const ia = new Uint8Array(ab);
108
+
109
+ for (let i = 0; i < byteString.length; i++) {
110
+ ia[i] = byteString.charCodeAt(i);
111
+ }
112
+
113
+ return new Blob([ab], { type: mimeType });
114
+ }
115
+
116
+ // 确认裁剪
117
+ const confirmCrop = async () => {
118
+ submitLoading.value = true;
119
+ try {
120
+ cropperRef.value?.getCropData((data: string) => {
121
+ if (data) {
122
+ const croppedBlob = base64ToBlob(data, 'image/png');
123
+ emits("success", croppedBlob);
124
+ dialogVisible.value = false;
125
+ showCropper.value = false;
126
+ }
127
+ submitLoading.value = false;
128
+ });
129
+ } catch (error) {
130
+ ElMessage.error("裁剪图片失败!");
131
+ submitLoading.value = false;
132
+ }
133
+ };
134
+
135
+ // 左旋转90度
136
+ const rotateLeft = () => {
137
+ cropperRef.value?.rotateLeft();
138
+ };
139
+
140
+ // 右旋转90度
141
+ const rotateRight = () => {
142
+ cropperRef.value?.rotateRight();
143
+ };
144
+
145
+ // 关闭弹框
146
+ const closeDialog = () => {
147
+ showCropper.value = false;
148
+ currentFile.value = null;
149
+ };
150
+
151
+ defineExpose({
152
+ open,
153
+ });
154
+ </script>
155
+
156
+ <style scoped lang="scss">
157
+ .cropper-content {
158
+ display: flex;
159
+ flex-direction: column;
160
+ align-items: center;
161
+ gap: 16px;
162
+ width: 100%;
163
+ }
164
+
165
+ .cropper-container {
166
+ width: 500px;
167
+ height: 400px;
168
+ position: relative;
169
+ }
170
+
171
+ .crop {
172
+ width: 100%;
173
+ height: 100%;
174
+ }
175
+
176
+ .rotate-controls {
177
+ display: flex;
178
+ justify-content: center;
179
+ align-items: center;
180
+ padding: 8px 0;
181
+
182
+ .el-button-group {
183
+ display: flex;
184
+ gap: 0;
185
+ }
186
+ }
187
+ </style>