@ebiz/designer-components 0.1.54 → 0.1.56

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": "@ebiz/designer-components",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,7 +25,9 @@
25
25
  "unplugin-auto-import": "^19.0.0",
26
26
  "unplugin-vue-components": "^28.0.0",
27
27
  "vue": "^3.5.13",
28
- "vue-router": "^4.2.5"
28
+ "vue-router": "^4.2.5",
29
+ "@wangeditor/editor": "^5.1.23",
30
+ "@wangeditor/editor-for-vue": "^5.1.12"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@opentiny/tiny-engine-mock": "^2.1.0",
@@ -0,0 +1,276 @@
1
+ <template>
2
+ <div class="ebiz-rich-text-editor" style="border: 1px solid #ccc">
3
+ <Toolbar
4
+ style="border-bottom: 1px solid #ccc"
5
+ :editor="editorRef"
6
+ :defaultConfig="mergedToolbarConfig"
7
+ :mode="mode"
8
+ />
9
+ <Editor
10
+ :style="{height: `${height}px`, overflowY: 'hidden'}"
11
+ v-model="computedModelValue"
12
+ :defaultConfig="mergedEditorConfig"
13
+ :mode="mode"
14
+ @onCreated="handleCreated"
15
+ @onChange="handleChange"
16
+ @onDestroyed="handleDestroyed"
17
+ @onFocus="handleFocus"
18
+ @onBlur="handleBlur"
19
+ @customAlert="handleCustomAlert"
20
+ @customPaste="handleCustomPaste"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script>
26
+ /**
27
+ * @displayName 富文本编辑器
28
+ * @description 基于WangEditor的富文本编辑器组件,支持图片和视频上传
29
+ * @category 表单组件
30
+ * @name EbizRichTextEditor
31
+ */
32
+ export default {
33
+ name: "EbizRichTextEditor"
34
+ }
35
+ </script>
36
+
37
+ <script setup>
38
+ import '@wangeditor/editor/dist/css/style.css'
39
+ import { defineProps, defineEmits, computed, ref, shallowRef, onBeforeUnmount, onMounted } from 'vue'
40
+ import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
41
+ // import { upload } from '../utils/upload'
42
+
43
+ // 编辑器实例,必须用 shallowRef
44
+ const editorRef = shallowRef()
45
+
46
+ // 编辑器内容
47
+ const valueHtml = ref('')
48
+
49
+ // 编辑器模式
50
+ const mode = ref('default') // 或 'simple'
51
+
52
+ const props = defineProps({
53
+ /**
54
+ * 绑定的值
55
+ */
56
+ modelValue: {
57
+ type: String,
58
+ default: ''
59
+ },
60
+ /**
61
+ * 是否禁用
62
+ */
63
+ disabled: {
64
+ type: Boolean,
65
+ default: false
66
+ },
67
+ /**
68
+ * 是否只读
69
+ */
70
+ readonly: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+ /**
75
+ * 占位文本
76
+ */
77
+ placeholder: {
78
+ type: String,
79
+ default: '请输入内容...'
80
+ },
81
+ /**
82
+ * 编辑器高度
83
+ */
84
+ height: {
85
+ type: [String, Number],
86
+ default: 300
87
+ },
88
+ /**
89
+ * 工具栏配置
90
+ */
91
+ toolbarConfig: {
92
+ type: Object,
93
+ default: () => ({})
94
+ },
95
+ /**
96
+ * 编辑器配置
97
+ */
98
+ editorConfig: {
99
+ type: Object,
100
+ default: () => ({})
101
+ },
102
+ /**
103
+ * 图片上传路径
104
+ */
105
+ imagePath: {
106
+ type: String,
107
+ default: 'editor'
108
+ },
109
+ /**
110
+ * 视频上传路径
111
+ */
112
+ videoPath: {
113
+ type: String,
114
+ default: 'video'
115
+ },
116
+ /**
117
+ * 最大长度
118
+ */
119
+ maxLength: {
120
+ type: Number,
121
+ default: undefined
122
+ }
123
+ });
124
+
125
+ const emit = defineEmits([
126
+ 'update:modelValue',
127
+ 'change',
128
+ 'focus',
129
+ 'blur',
130
+ 'created',
131
+ 'destroyed'
132
+ ]);
133
+
134
+ // 计算属性处理v-model双向绑定
135
+ const computedModelValue = computed({
136
+ get: () => props.modelValue,
137
+ set: (val) => {
138
+ emit('update:modelValue', val);
139
+ }
140
+ });
141
+
142
+ // 合并工具栏配置
143
+ const mergedToolbarConfig = computed(() => {
144
+ return {
145
+ ...props.toolbarConfig
146
+ };
147
+ });
148
+
149
+ // 合并编辑器配置
150
+ const mergedEditorConfig = computed(() => {
151
+ return {
152
+ placeholder: props.placeholder,
153
+ readOnly: props.disabled || props.readonly,
154
+ maxLength: props.maxLength,
155
+ MENU_CONF: {
156
+ uploadImage: {
157
+ customUpload(file, insertFn) {
158
+
159
+ upload({
160
+ path: props.imagePath,
161
+ file: {
162
+ name: file.name,
163
+ raw: file
164
+ },
165
+ onSuccess: (res) => {
166
+ insertFn(res.url, '', '');
167
+ }
168
+ });
169
+ },
170
+ },
171
+ uploadVideo: {
172
+ customUpload(file, insertFn) {
173
+ upload({
174
+ path: props.videoPath,
175
+ file: {
176
+ name: file.name,
177
+ raw: file
178
+ },
179
+ onSuccess: (res) => {
180
+ insertFn(res.url, '', '');
181
+ }
182
+ });
183
+ },
184
+ },
185
+ ...props.editorConfig?.MENU_CONF
186
+ },
187
+ ...props.editorConfig
188
+ };
189
+ });
190
+
191
+ // 编辑器创建完成回调
192
+ const handleCreated = (editor) => {
193
+ editorRef.value = editor; // 记录编辑器实例
194
+ emit('created', editor);
195
+ };
196
+
197
+ // 内容变化事件
198
+ const handleChange = (editor) => {
199
+ emit('change', editor.getHtml());
200
+ };
201
+
202
+ // 销毁事件
203
+ const handleDestroyed = (editor) => {
204
+ emit('destroyed', editor);
205
+ };
206
+
207
+ // 获得焦点事件
208
+ const handleFocus = (editor) => {
209
+ emit('focus', editor);
210
+ };
211
+
212
+ // 失去焦点事件
213
+ const handleBlur = (editor) => {
214
+ emit('blur', editor);
215
+ };
216
+
217
+ // 自定义提示事件
218
+ const handleCustomAlert = (info, type) => {
219
+ console.log(`【自定义提示】${type} - ${info}`);
220
+ };
221
+
222
+ // 自定义粘贴事件
223
+ const handleCustomPaste = (editor, event, callback) => {
224
+ console.log('ClipboardEvent 粘贴事件对象', event);
225
+ // 返回值(注意,vue 事件的返回值,不能用 return)
226
+ callback(true); // true 继续默认的粘贴行为
227
+ };
228
+
229
+ // 组件销毁时,销毁编辑器实例
230
+ onBeforeUnmount(() => {
231
+ const editor = editorRef.value;
232
+ if (editor === null) return;
233
+ editor.destroy();
234
+ });
235
+
236
+ // 暴露编辑器实例和方法
237
+ defineExpose({
238
+ editor: editorRef,
239
+ insertText: (text) => {
240
+ if (editorRef.value) {
241
+ editorRef.value.insertText(text);
242
+ }
243
+ },
244
+ getHtml: () => {
245
+ if (editorRef.value) {
246
+ return editorRef.value.getHtml();
247
+ }
248
+ return '';
249
+ },
250
+ getText: () => {
251
+ if (editorRef.value) {
252
+ return editorRef.value.getText();
253
+ }
254
+ return '';
255
+ }
256
+ });
257
+ </script>
258
+
259
+ <style scoped>
260
+ .ebiz-rich-text-editor {
261
+ border-radius: var(--td-radius-default);
262
+ overflow: hidden;
263
+ }
264
+
265
+ .ebiz-rich-text-editor :deep(.w-e-text-container) {
266
+ z-index: 1; /* 避免编辑区域遮挡其他元素 */
267
+ }
268
+
269
+ .ebiz-rich-text-editor :deep(.w-e-toolbar) {
270
+ z-index: 2; /* 确保工具栏在编辑区域之上 */
271
+ }
272
+
273
+ .ebiz-rich-text-editor :deep(.w-e-text-placeholder) {
274
+ color: var(--td-text-color-placeholder);
275
+ }
276
+ </style>
@@ -436,7 +436,12 @@ const getOperationTypeText = (approver) => {
436
436
  case 'TRANSFER':
437
437
  return '转审'
438
438
  case 'ADD_SIGN':
439
- return '加签'
439
+ const operationData = JSON.parse(approver.operation.operationData || '{}')
440
+ if (operationData.position == 'before') {
441
+ return "向前加签";
442
+ }
443
+ return '已同意并加签'
444
+ // return '加签'
440
445
  case 'REJECT':
441
446
  return '驳回'
442
447
  case 'RETURN':
@@ -121,6 +121,23 @@
121
121
  @change="handleChange" />
122
122
  </template>
123
123
 
124
+ <!-- 富文本编辑器 -->
125
+ <template v-else-if="type === 'rich-text'">
126
+ <ebiz-rich-text-editor
127
+ v-model="computedModelValue"
128
+ :placeholder="placeholder || '请输入内容'"
129
+ :disabled="disabled"
130
+ :readonly="readonly"
131
+ :height="editorHeight"
132
+ :toolbar-config="editorToolbarConfig"
133
+ :editor-config="editorConfig"
134
+ :image-path="editorImagePath"
135
+ :video-path="editorVideoPath"
136
+ :max-length="maxLength"
137
+ @change="handleChange"
138
+ />
139
+ </template>
140
+
124
141
  <!-- 子表格 -->
125
142
  <template v-else-if="type === 'table'">
126
143
  <div class="ebiz-form-table">
@@ -239,7 +256,7 @@ export default {
239
256
  </script>
240
257
 
241
258
  <script setup>
242
- import { defineProps, defineEmits, computed, watch } from 'vue';
259
+ import { defineProps, defineEmits, computed, watch, onMounted } from 'vue';
243
260
  import {
244
261
  FormItem as TFormItem,
245
262
  Input as TInput,
@@ -263,6 +280,7 @@ import EbizRemoteSelect from '../../EbizRemoteSelect.vue';
263
280
  import EbizEmployeeSelector from '../../EbizEmployeeSelector.vue';
264
281
  import EbizDepartmentSelector from '../../EbizDepartmentSelector.vue';
265
282
  import { EbizUpload } from '../../../index.js'
283
+ import EbizRichTextEditor from '../../EbizRichTextEditor.vue';
266
284
 
267
285
  const props = defineProps({
268
286
  /**
@@ -275,7 +293,7 @@ const props = defineProps({
275
293
  validator: (val) => [
276
294
  'input', 'number', 'textarea', 'select', 'date', 'datetime', 'time', 'radio',
277
295
  'checkbox', 'switch', 'slider', 'upload', 'cascader', 'tree-select',
278
- 'color-picker', 'employee', 'dept', 'table'
296
+ 'color-picker', 'employee', 'dept', 'table', 'rich-text'
279
297
  ].includes(val)
280
298
  },
281
299
  /**
@@ -649,6 +667,41 @@ const props = defineProps({
649
667
  addButtonText: {
650
668
  type: String,
651
669
  default: '添加数据'
670
+ },
671
+ /**
672
+ * 富文本编辑器高度
673
+ */
674
+ editorHeight: {
675
+ type: [String, Number],
676
+ default: 300
677
+ },
678
+ /**
679
+ * 富文本编辑器工具栏配置
680
+ */
681
+ editorToolbarConfig: {
682
+ type: Object,
683
+ default: () => ({})
684
+ },
685
+ /**
686
+ * 富文本编辑器配置
687
+ */
688
+ editorConfig: {
689
+ type: Object,
690
+ default: () => ({})
691
+ },
692
+ /**
693
+ * 富文本图片上传路径
694
+ */
695
+ editorImagePath: {
696
+ type: String,
697
+ default: 'editor'
698
+ },
699
+ /**
700
+ * 富文本视频上传路径
701
+ */
702
+ editorVideoPath: {
703
+ type: String,
704
+ default: 'video'
652
705
  }
653
706
  });
654
707
 
@@ -804,6 +857,10 @@ watch(
804
857
  },
805
858
  { deep: true }
806
859
  );
860
+
861
+ // 组件挂载时初始化文件列表
862
+ onMounted(() => {
863
+ });
807
864
  </script>
808
865
 
809
866
  <style scoped>
package/src/index.js CHANGED
@@ -100,6 +100,7 @@ import EbizFileList from './components/EbizFileList.vue'
100
100
  import EbizDetailView from './components/EbizDetailView.vue'
101
101
  import EbizDetailItem from './components/EbizDetailItem.vue'
102
102
  import EbizPdfViewer from './components/EbizPdfViewer.vue'
103
+ import EbizRichTextEditor from './components/EbizRichTextEditor.vue'
103
104
 
104
105
  // 导出组件
105
106
  export {
@@ -242,5 +243,7 @@ export {
242
243
  EbizDetailView,
243
244
  EbizDetailItem,
244
245
  // PDF预览组件
245
- EbizPdfViewer
246
+ EbizPdfViewer,
247
+ // 富文本编辑器组件
248
+ EbizRichTextEditor
246
249
  }
@@ -4,12 +4,7 @@
4
4
  <div class="demo-section">
5
5
  <h3>基础表单示例</h3>
6
6
  <div class="demo-block">
7
- <ebiz-s-form
8
- ref="basicFormRef"
9
- :data="formData"
10
- :rules="formRules"
11
- show-error-message
12
- @success="handleSuccess"
7
+ <ebiz-s-form ref="basicFormRef" :data="formData" :rules="formRules" show-error-message @success="handleSuccess"
13
8
  @error="handleError">
14
9
  <ebiz-s-form-item label="姓名" name="name" type="input" placeholder="请输入姓名"></ebiz-s-form-item>
15
10
  <ebiz-s-form-item label="年龄" name="age" type="number" placeholder="请输入年龄"></ebiz-s-form-item>
@@ -17,6 +12,10 @@
17
12
  <ebiz-s-form-item label="爱好" name="hobbies" type="checkbox" :options="hobbyOptions"></ebiz-s-form-item>
18
13
  <ebiz-s-form-item label="职业" name="occupation" type="select" :options="occupationOptions"></ebiz-s-form-item>
19
14
  <ebiz-s-form-item label="出生日期" name="birthday" type="date"></ebiz-s-form-item>
15
+
16
+ <ebiz-s-form-item v-model="formData.richText" label="富文本" name="richText" type="rich-text"></ebiz-s-form-item>
17
+
18
+
20
19
  {{ formData.bio }}
21
20
  <ebiz-s-form-item v-model="formData.bio" label="简介" name="bio" type="table" :tableColumns="[
22
21
  { name: '名称', key: 'name' },
@@ -33,16 +32,9 @@
33
32
  <t-button @click="openEditFormWithData">打开编辑表单(预设数据)</t-button>
34
33
  <t-button @click="openEditFormWithId">打开编辑表单(加载详情)</t-button>
35
34
  </t-space>
36
- <ebiz-s-form
37
- ref="editFormRef"
38
- :api-config="submitApiConfig"
39
- :detail-api-config="detailApiConfig"
40
- :rules="editFormRules"
41
- show-error-message
42
- reset-on-success
43
- @success="handleEditSuccess"
44
- @error="handleEditError"
45
- @detail-loaded="handleDetailLoaded">
35
+ <ebiz-s-form ref="editFormRef" :api-config="submitApiConfig" :detail-api-config="detailApiConfig"
36
+ :rules="editFormRules" show-error-message reset-on-success @success="handleEditSuccess"
37
+ @error="handleEditError" @detail-loaded="handleDetailLoaded">
46
38
  <ebiz-s-form-item label="ID" name="id" type="input" disabled></ebiz-s-form-item>
47
39
  <ebiz-s-form-item label="标题" name="title" type="input" placeholder="请输入标题"></ebiz-s-form-item>
48
40
  <ebiz-s-form-item label="内容" name="body" type="textarea" placeholder="请输入内容"></ebiz-s-form-item>
@@ -54,10 +46,7 @@
54
46
  <div class="demo-section">
55
47
  <h3>行内表单示例</h3>
56
48
  <div class="demo-block">
57
- <ebiz-s-form
58
- :data="inlineFormData"
59
- layout="inline"
60
- label-width="60px">
49
+ <ebiz-s-form :data="inlineFormData" layout="inline" label-width="60px">
61
50
  <ebiz-s-form-item label="用户名" name="username" type="input" placeholder="请输入用户名"></ebiz-s-form-item>
62
51
  <ebiz-s-form-item label="密码" name="password" type="input" placeholder="请输入密码"></ebiz-s-form-item>
63
52
  </ebiz-s-form>
@@ -84,9 +73,7 @@
84
73
  <div class="demo-section">
85
74
  <h3>顶部标签表单示例</h3>
86
75
  <div class="demo-block">
87
- <ebiz-s-form
88
- :data="topLabelFormData"
89
- label-align="top">
76
+ <ebiz-s-form :data="topLabelFormData" label-align="top">
90
77
  <ebiz-s-form-item label="邮箱" name="email" type="input" placeholder="请输入邮箱"></ebiz-s-form-item>
91
78
  <ebiz-s-form-item label="手机号" name="phone" type="input" placeholder="请输入手机号"></ebiz-s-form-item>
92
79
  <ebiz-s-form-item label="是否启用" name="enabled" type="switch"></ebiz-s-form-item>
@@ -1,126 +0,0 @@
1
- import {request} from "./request";
2
- import COS from "cos-js-sdk-v5";
3
- import OSS from "ali-oss";
4
- import moment from "moment/moment";
5
-
6
- const defaultOnPercent = () => {
7
- }
8
- const defaultOnSuccess = () => {
9
- }
10
- const defaultOnFail = () => {
11
- }
12
-
13
- export const upload = (options: any) => {
14
- const path: string = options.path;
15
- const file: any = options.file;
16
- const onPercent: Function = options.onProgress || defaultOnPercent;
17
- const onSuccess: Function = options.onSuccess || defaultOnSuccess;
18
- const onFail: Function = options.onFail || defaultOnFail;
19
-
20
- let credentials: any, url;
21
- request.get({
22
- url: "/file/getSecretKey"
23
- }).then((res) => {
24
- if (res.oss_type === 'ALIYUN') {
25
- const client = new OSS({
26
- // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
27
- region: res.region,
28
- // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
29
- accessKeyId: res.accessKeyId,
30
- accessKeySecret: res.accessKeySecret,
31
- // 从STS服务获取的安全令牌(SecurityToken)。
32
- stsToken: res.securityToken,
33
- // 刷新临时访问凭证的时间间隔,单位为毫秒。
34
- refreshSTSTokenInterval: 300000,
35
- // 填写Bucket名称。
36
- bucket: res.bucket
37
- });
38
- const options = {
39
- mime: "json",
40
- headers: {"Content-Type": "text/plain"},
41
- };
42
- client.put(handleUploadName("img", path, file.name), file.raw, options).then((uploadRes: any) => {
43
- console.log(uploadRes)
44
- // 创建一个 URL 对象
45
- const urlObj = new URL(uploadRes.url);
46
- // 从 URL 对象中获取路径部分
47
- const url = res.host + urlObj.pathname.substring(1);
48
-
49
- request.get({
50
- url: "/file/getAccessUrl",
51
- params: {
52
- url,
53
- }
54
- }).then(res => {
55
- console.log(res)
56
- onSuccess({url: res})
57
- })
58
- }).catch((e: any) => {
59
- onFail(e)
60
- });
61
- } else {
62
- credentials = res.credentials;
63
- let cos = new COS({
64
- getAuthorization: function (options, callback) {
65
- callback({
66
- TmpSecretId: credentials.tmpSecretId,
67
- TmpSecretKey: credentials.tmpSecretKey,
68
- SecurityToken: credentials.sessionToken,
69
- StartTime: credentials.startTime,
70
- ExpiredTime: credentials.expiredTime
71
- });
72
- }
73
- });
74
- cos.uploadFile(
75
- {
76
- Bucket: credentials.bucket,
77
- Region: credentials.region,
78
- Key: handleUploadName("img", path, file.name),
79
- Body: file.raw,
80
- SliceSize: 1024 * 1024 * 5 /* 触发分块上传的阈值,超过5MB使用分块上传,小于5MB使用简单上传。可自行设置,非必须 */,
81
- onProgress: (progressData) => {
82
- if (!!onPercent) {
83
- onPercent({file, percent: progressData.percent});
84
- }
85
- },
86
- onFileFinish: (err, data, options) => {
87
- url = credentials.path + "/" + options.Key;
88
- request.get({
89
- url: "/file/getAccessUrl",
90
- params: {
91
- url,
92
- }
93
- }).then(res => {
94
- console.log(res)
95
- onPercent({file, percent: 100});
96
- onSuccess({url: res})
97
- })
98
- }
99
- },
100
- function (err, data) {
101
- onFail(err)
102
- }
103
- );
104
- }
105
-
106
- });
107
- }
108
-
109
-
110
- // 规范上传图片格式
111
- const handleUploadName = function (type: string, path: string, file_name: string) {
112
- // /img/类型(avatar/category/article)/年月日/md5(文件名+随机字符串+毫秒数)+时间.文件后缀
113
- let newFileName =
114
- type +
115
- '/' +
116
- path +
117
- '/' +
118
- moment(new Date()).format('YYMMDD') +
119
- '/' +
120
- file_name.split(file_name.substring(file_name.lastIndexOf('.')))[0] +
121
- Math.random().toString(36).substring(2) +
122
- Date.now() +
123
- moment(new Date()).format('HHmm') +
124
- file_name.substring(file_name.lastIndexOf('.'));
125
- return newFileName;
126
- };