@firerian/fireui 1.0.1 → 1.0.3
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/dist/fireui.cjs +1 -1
- package/dist/fireui.cjs.map +1 -1
- package/dist/fireui.css +7 -1
- package/dist/fireui.es.mjs +14155 -74
- package/dist/fireui.es.mjs.map +1 -1
- package/dist/fireui.umd.js +1 -1
- package/dist/fireui.umd.js.map +1 -1
- package/dist/types/index.d.ts +57 -4
- package/package.json +4 -2
- package/src/components/index.ts +5 -1
- package/src/components/input/index.ts +3 -3
- package/src/components/input/input.test.ts +12 -12
- package/src/components/input/input.vue +34 -31
- package/src/components/rich-text/index.ts +8 -0
- package/src/components/rich-text/rich-text.vue +210 -0
- package/src/components/rich-text/types.ts +52 -0
- package/src/index.ts +21 -3
- package/src/styles/components.scss +230 -0
- package/src/styles/variables.scss +5 -1
- package/src/utils/install.ts +5 -2
package/dist/types/index.d.ts
CHANGED
|
@@ -340,8 +340,30 @@ declare const _default_2: __VLS_WithTemplateSlots_2<typeof __VLS_component_2, __
|
|
|
340
340
|
|
|
341
341
|
declare const _default_3: __VLS_WithTemplateSlots_3<typeof __VLS_component_3, __VLS_TemplateResult_3["slots"]>;
|
|
342
342
|
|
|
343
|
+
declare const _default_4: DefineComponent<RichTextProps, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
344
|
+
change: (value: string) => any;
|
|
345
|
+
"update:modelValue": (value: string) => any;
|
|
346
|
+
ready: (quill: any) => any;
|
|
347
|
+
"image-upload": (file: File, callback: (url: string) => void) => any;
|
|
348
|
+
}, string, PublicProps, Readonly<RichTextProps> & Readonly<{
|
|
349
|
+
onChange?: ((value: string) => any) | undefined;
|
|
350
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
351
|
+
onReady?: ((quill: any) => any) | undefined;
|
|
352
|
+
"onImage-upload"?: ((file: File, callback: (url: string) => void) => any) | undefined;
|
|
353
|
+
}>, {
|
|
354
|
+
disabled: boolean;
|
|
355
|
+
height: string | number;
|
|
356
|
+
placeholder: string;
|
|
357
|
+
modelValue: string;
|
|
358
|
+
toolbar: any[];
|
|
359
|
+
}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
360
|
+
|
|
343
361
|
export declare const FButton: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
344
362
|
|
|
363
|
+
export declare const FInput: typeof _default_3 & {
|
|
364
|
+
install: (app: App) => void;
|
|
365
|
+
};
|
|
366
|
+
|
|
345
367
|
export declare const FireAside: {
|
|
346
368
|
new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<{
|
|
347
369
|
width?: string | number;
|
|
@@ -643,10 +665,6 @@ height: string | number;
|
|
|
643
665
|
install: (app: App) => void;
|
|
644
666
|
};
|
|
645
667
|
|
|
646
|
-
export declare const FireInput: typeof _default_3 & {
|
|
647
|
-
install: (app: App) => void;
|
|
648
|
-
};
|
|
649
|
-
|
|
650
668
|
export declare const FireMain: {
|
|
651
669
|
new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<{
|
|
652
670
|
class?: string;
|
|
@@ -676,6 +694,10 @@ style?: string | object;
|
|
|
676
694
|
install: (app: App) => void;
|
|
677
695
|
};
|
|
678
696
|
|
|
697
|
+
export declare const FireRichText: typeof _default_4 & {
|
|
698
|
+
install: (app: App) => void;
|
|
699
|
+
};
|
|
700
|
+
|
|
679
701
|
export declare const FireRow: {
|
|
680
702
|
new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<RowProps_2> & Readonly<{}>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, PublicProps, {
|
|
681
703
|
gutter: number | [number, number];
|
|
@@ -1240,6 +1262,37 @@ declare interface ResponsiveProps_2 {
|
|
|
1240
1262
|
offset?: number;
|
|
1241
1263
|
}
|
|
1242
1264
|
|
|
1265
|
+
declare interface RichTextProps {
|
|
1266
|
+
/**
|
|
1267
|
+
* 绑定的值
|
|
1268
|
+
*/
|
|
1269
|
+
modelValue?: string;
|
|
1270
|
+
/**
|
|
1271
|
+
* 编辑器高度
|
|
1272
|
+
*/
|
|
1273
|
+
height?: string | number;
|
|
1274
|
+
/**
|
|
1275
|
+
* 是否禁用
|
|
1276
|
+
*/
|
|
1277
|
+
disabled?: boolean;
|
|
1278
|
+
/**
|
|
1279
|
+
* 占位文本
|
|
1280
|
+
*/
|
|
1281
|
+
placeholder?: string;
|
|
1282
|
+
/**
|
|
1283
|
+
* 自定义工具栏配置
|
|
1284
|
+
*/
|
|
1285
|
+
toolbar?: any[];
|
|
1286
|
+
/**
|
|
1287
|
+
* 自定义类名
|
|
1288
|
+
*/
|
|
1289
|
+
class?: string;
|
|
1290
|
+
/**
|
|
1291
|
+
* 自定义样式
|
|
1292
|
+
*/
|
|
1293
|
+
style?: string | object;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1243
1296
|
export declare interface RowProps {
|
|
1244
1297
|
/**
|
|
1245
1298
|
* 栅格间距
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firerian/fireui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A modern Vue 3 UI component library",
|
|
6
6
|
"main": "./dist/fireui.cjs",
|
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@floating-ui/vue": "^1.1.10",
|
|
77
|
+
"@vueup/vue-quill": "^1.2.0",
|
|
77
78
|
"async-validator": "^4.2.5",
|
|
78
|
-
"lucide-vue-next": "^0.454.0"
|
|
79
|
+
"lucide-vue-next": "^0.454.0",
|
|
80
|
+
"quill": "^2.0.3"
|
|
79
81
|
},
|
|
80
82
|
"devDependencies": {
|
|
81
83
|
"@eslint/js": "^9.13.0",
|
package/src/components/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export { FireTips } from './tips'
|
|
|
24
24
|
export type { TipsProps, TipsEmits, TipsSlots } from './tips/types'
|
|
25
25
|
|
|
26
26
|
// Input 组件
|
|
27
|
-
export {
|
|
27
|
+
export { FInput } from './input'
|
|
28
28
|
export type { InputProps, InputEmits, InputSlots } from './input/types'
|
|
29
29
|
|
|
30
30
|
// Form 组件
|
|
@@ -34,3 +34,7 @@ export type { FormProps, FormEmits, FormInstance, FormItemProps, FormItemEmits,
|
|
|
34
34
|
// Table 组件
|
|
35
35
|
export { FireTable, FireTableColumn } from './table'
|
|
36
36
|
export type { TableProps, TableEmits, TableInstance, TableColumnProps, TableColumnSlots } from '../types/table'
|
|
37
|
+
|
|
38
|
+
// RichText 组件
|
|
39
|
+
export { FireRichText } from './rich-text'
|
|
40
|
+
export type { RichTextProps, RichTextEmits } from './rich-text/types'
|
|
@@ -2,7 +2,7 @@ import Input from './input.vue'
|
|
|
2
2
|
import { withInstall } from '../../utils'
|
|
3
3
|
import type { App } from 'vue'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const FInput = withInstall(Input as any) as typeof Input & { install: (app: App) => void }
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
export default
|
|
7
|
+
export { FInput }
|
|
8
|
+
export default FInput
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import { mount } from '@vue/test-utils'
|
|
3
|
-
import {
|
|
3
|
+
import { FInput } from './index'
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('FInput', () => {
|
|
6
6
|
it('should render correctly', () => {
|
|
7
|
-
const wrapper = mount(
|
|
7
|
+
const wrapper = mount(FInput, {
|
|
8
8
|
props: {
|
|
9
9
|
modelValue: 'test'
|
|
10
10
|
}
|
|
@@ -15,7 +15,7 @@ describe('FireInput', () => {
|
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
it('should update modelValue when input', async () => {
|
|
18
|
-
const wrapper = mount(
|
|
18
|
+
const wrapper = mount(FInput, {
|
|
19
19
|
props: {
|
|
20
20
|
modelValue: ''
|
|
21
21
|
}
|
|
@@ -30,7 +30,7 @@ describe('FireInput', () => {
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
it('should emit focus and blur events', async () => {
|
|
33
|
-
const wrapper = mount(
|
|
33
|
+
const wrapper = mount(FInput)
|
|
34
34
|
|
|
35
35
|
const input = wrapper.find('input')
|
|
36
36
|
await input.trigger('focus')
|
|
@@ -41,7 +41,7 @@ describe('FireInput', () => {
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
it('should emit enter event', async () => {
|
|
44
|
-
const wrapper = mount(
|
|
44
|
+
const wrapper = mount(FInput)
|
|
45
45
|
|
|
46
46
|
const input = wrapper.find('input')
|
|
47
47
|
await input.trigger('keydown.enter')
|
|
@@ -49,7 +49,7 @@ describe('FireInput', () => {
|
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
it('should be disabled', () => {
|
|
52
|
-
const wrapper = mount(
|
|
52
|
+
const wrapper = mount(FInput, {
|
|
53
53
|
props: {
|
|
54
54
|
disabled: true
|
|
55
55
|
}
|
|
@@ -61,7 +61,7 @@ describe('FireInput', () => {
|
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
it('should be readonly', () => {
|
|
64
|
-
const wrapper = mount(
|
|
64
|
+
const wrapper = mount(FInput, {
|
|
65
65
|
props: {
|
|
66
66
|
readonly: true
|
|
67
67
|
}
|
|
@@ -72,7 +72,7 @@ describe('FireInput', () => {
|
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
it('should show word limit', () => {
|
|
75
|
-
const wrapper = mount(
|
|
75
|
+
const wrapper = mount(FInput, {
|
|
76
76
|
props: {
|
|
77
77
|
modelValue: 'test',
|
|
78
78
|
maxlength: 10,
|
|
@@ -84,7 +84,7 @@ describe('FireInput', () => {
|
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
it('should toggle password visibility', async () => {
|
|
87
|
-
const wrapper = mount(
|
|
87
|
+
const wrapper = mount(FInput, {
|
|
88
88
|
props: {
|
|
89
89
|
password: true
|
|
90
90
|
}
|
|
@@ -105,7 +105,7 @@ describe('FireInput', () => {
|
|
|
105
105
|
const sizes = ['large', 'medium', 'small']
|
|
106
106
|
|
|
107
107
|
sizes.forEach((size) => {
|
|
108
|
-
const wrapper = mount(
|
|
108
|
+
const wrapper = mount(FInput, {
|
|
109
109
|
props: {
|
|
110
110
|
size: size as 'large' | 'medium' | 'small'
|
|
111
111
|
}
|
|
@@ -116,7 +116,7 @@ describe('FireInput', () => {
|
|
|
116
116
|
})
|
|
117
117
|
|
|
118
118
|
it('should render prefix and suffix slots', () => {
|
|
119
|
-
const wrapper = mount(
|
|
119
|
+
const wrapper = mount(FInput, {
|
|
120
120
|
slots: {
|
|
121
121
|
prefix: '<span class="prefix">P</span>',
|
|
122
122
|
suffix: '<span class="suffix">S</span>'
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
}
|
|
11
11
|
]"
|
|
12
12
|
>
|
|
13
|
-
<div v-if="$slots.prefix || prefixIcon" class="
|
|
13
|
+
<div v-if="$slots.prefix || prefixIcon" :class="ns.e('prefix')">
|
|
14
14
|
<slot name="prefix">
|
|
15
15
|
<component :is="prefixIcon" v-if="prefixIcon" />
|
|
16
16
|
</slot>
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
@focus="handleFocus"
|
|
29
29
|
@blur="handleBlur"
|
|
30
30
|
@keydown.enter="handleEnter"
|
|
31
|
-
class="
|
|
31
|
+
:class="ns.e('inner')"
|
|
32
32
|
/>
|
|
33
|
-
<div v-if="$slots.suffix || suffixIcon || showWordLimit || (password && !disabled)" class="
|
|
34
|
-
<span v-if="showWordLimit" class="
|
|
35
|
-
<div v-if="password && !disabled" class="
|
|
33
|
+
<div v-if="$slots.suffix || suffixIcon || showWordLimit || (password && !disabled)" :class="ns.e('suffix')">
|
|
34
|
+
<span v-if="showWordLimit" :class="ns.e('word-limit')">{{ (modelValue || '').toString().length }}/{{ maxlength }}</span>
|
|
35
|
+
<div v-if="password && !disabled" :class="ns.e('password-toggle')">
|
|
36
36
|
<button
|
|
37
37
|
type="button"
|
|
38
38
|
@click="togglePassword"
|
|
39
|
-
class="
|
|
39
|
+
:class="ns.e('password-icon')"
|
|
40
40
|
aria-label="Toggle password visibility"
|
|
41
41
|
>
|
|
42
42
|
<Eye v-if="!showPassword" :size="16" />
|
|
@@ -58,6 +58,11 @@ import { useNamespace } from '../../utils'
|
|
|
58
58
|
// 初始化命名空间
|
|
59
59
|
const ns = useNamespace('input')
|
|
60
60
|
|
|
61
|
+
// 组件名称
|
|
62
|
+
defineOptions({
|
|
63
|
+
name: 'FInput'
|
|
64
|
+
})
|
|
65
|
+
|
|
61
66
|
// Props 定义 - 使用 export 导出
|
|
62
67
|
export interface InputProps {
|
|
63
68
|
type?: string
|
|
@@ -127,47 +132,45 @@ const togglePassword = () => {
|
|
|
127
132
|
}
|
|
128
133
|
</script>
|
|
129
134
|
|
|
130
|
-
<style
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.#{$namespace}-input {
|
|
135
|
+
<style lang="scss">
|
|
136
|
+
.fire-input {
|
|
134
137
|
position: relative;
|
|
135
138
|
display: inline-flex;
|
|
136
139
|
align-items: center;
|
|
137
140
|
width: 100%;
|
|
138
|
-
border: 1px solid
|
|
139
|
-
border-radius:
|
|
141
|
+
border: 1px solid #d9d9d9;
|
|
142
|
+
border-radius: 4px;
|
|
140
143
|
transition: all 0.2s ease;
|
|
141
144
|
background-color: #fff;
|
|
142
145
|
|
|
143
146
|
&:hover {
|
|
144
|
-
border-color:
|
|
147
|
+
border-color: #3b82f6;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
&:focus-within {
|
|
148
|
-
border-color:
|
|
149
|
-
box-shadow: 0 0 0 2px rgba(
|
|
151
|
+
border-color: #3b82f6;
|
|
152
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
&--disabled {
|
|
153
|
-
background-color:
|
|
154
|
-
border-color:
|
|
156
|
+
background-color: #f5f5f5;
|
|
157
|
+
border-color: #d9d9d9;
|
|
155
158
|
cursor: not-allowed;
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
background-color:
|
|
160
|
+
.fire-input__inner {
|
|
161
|
+
background-color: #f5f5f5;
|
|
159
162
|
cursor: not-allowed;
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
&--prefix {
|
|
164
|
-
|
|
167
|
+
.fire-input__inner {
|
|
165
168
|
padding-left: 8px;
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
&--suffix {
|
|
170
|
-
|
|
173
|
+
.fire-input__inner {
|
|
171
174
|
padding-right: 8px;
|
|
172
175
|
}
|
|
173
176
|
}
|
|
@@ -175,7 +178,7 @@ const togglePassword = () => {
|
|
|
175
178
|
&--large {
|
|
176
179
|
height: 40px;
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
.fire-input__inner {
|
|
179
182
|
height: 40px;
|
|
180
183
|
font-size: 14px;
|
|
181
184
|
padding: 0 12px;
|
|
@@ -185,7 +188,7 @@ const togglePassword = () => {
|
|
|
185
188
|
&--medium {
|
|
186
189
|
height: 32px;
|
|
187
190
|
|
|
188
|
-
|
|
191
|
+
.fire-input__inner {
|
|
189
192
|
height: 32px;
|
|
190
193
|
font-size: 14px;
|
|
191
194
|
padding: 0 12px;
|
|
@@ -195,7 +198,7 @@ const togglePassword = () => {
|
|
|
195
198
|
&--small {
|
|
196
199
|
height: 24px;
|
|
197
200
|
|
|
198
|
-
|
|
201
|
+
.fire-input__inner {
|
|
199
202
|
height: 24px;
|
|
200
203
|
font-size: 12px;
|
|
201
204
|
padding: 0 8px;
|
|
@@ -208,17 +211,17 @@ const togglePassword = () => {
|
|
|
208
211
|
outline: none;
|
|
209
212
|
background: transparent;
|
|
210
213
|
font-size: 14px;
|
|
211
|
-
color:
|
|
214
|
+
color: #333333;
|
|
212
215
|
transition: all 0.2s ease;
|
|
213
216
|
|
|
214
217
|
&::placeholder {
|
|
215
|
-
color:
|
|
218
|
+
color: #666666;
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
&__prefix {
|
|
220
223
|
padding: 0 8px;
|
|
221
|
-
color:
|
|
224
|
+
color: #666666;
|
|
222
225
|
font-size: 14px;
|
|
223
226
|
}
|
|
224
227
|
|
|
@@ -226,13 +229,13 @@ const togglePassword = () => {
|
|
|
226
229
|
display: flex;
|
|
227
230
|
align-items: center;
|
|
228
231
|
padding: 0 8px;
|
|
229
|
-
color:
|
|
232
|
+
color: #666666;
|
|
230
233
|
font-size: 14px;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
&__word-limit {
|
|
234
237
|
font-size: 12px;
|
|
235
|
-
color:
|
|
238
|
+
color: #666666;
|
|
236
239
|
margin-right: 8px;
|
|
237
240
|
}
|
|
238
241
|
|
|
@@ -245,11 +248,11 @@ const togglePassword = () => {
|
|
|
245
248
|
border: none;
|
|
246
249
|
cursor: pointer;
|
|
247
250
|
padding: 0;
|
|
248
|
-
color:
|
|
251
|
+
color: #666666;
|
|
249
252
|
transition: color 0.2s ease;
|
|
250
253
|
|
|
251
254
|
&:hover {
|
|
252
|
-
color:
|
|
255
|
+
color: #3b82f6;
|
|
253
256
|
}
|
|
254
257
|
}
|
|
255
258
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import RichText from './rich-text.vue'
|
|
2
|
+
import { withInstall } from '../../utils'
|
|
3
|
+
import type { App } from 'vue'
|
|
4
|
+
|
|
5
|
+
const FireRichText = withInstall(RichText as any) as typeof RichText & { install: (app: App) => void }
|
|
6
|
+
|
|
7
|
+
export { FireRichText }
|
|
8
|
+
export default FireRichText
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="[
|
|
4
|
+
ns.b.value,
|
|
5
|
+
{
|
|
6
|
+
[ns.m('disabled')]: disabled
|
|
7
|
+
}
|
|
8
|
+
]"
|
|
9
|
+
:style="{
|
|
10
|
+
...(typeof props.style === 'object' ? props.style : {}),
|
|
11
|
+
height
|
|
12
|
+
}"
|
|
13
|
+
>
|
|
14
|
+
<QuillEditor
|
|
15
|
+
v-model:content="localValue"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
:placeholder="placeholder"
|
|
18
|
+
:toolbar="toolbar"
|
|
19
|
+
@ready="handleReady"
|
|
20
|
+
@update:content="handleUpdate"
|
|
21
|
+
@text-change="handleTextChange"
|
|
22
|
+
@image-upload="handleImageUpload"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { ref, computed, watch } from 'vue'
|
|
29
|
+
import { QuillEditor } from '@vueup/vue-quill'
|
|
30
|
+
import 'quill/dist/quill.snow.css'
|
|
31
|
+
import { useNamespace } from '../../utils'
|
|
32
|
+
import type { RichTextProps, RichTextEmits } from './types'
|
|
33
|
+
|
|
34
|
+
const ns = useNamespace('rich-text')
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(defineProps<RichTextProps>(), {
|
|
37
|
+
modelValue: '',
|
|
38
|
+
height: '300px',
|
|
39
|
+
disabled: false,
|
|
40
|
+
placeholder: '请输入内容...',
|
|
41
|
+
toolbar: () => [
|
|
42
|
+
['bold', 'italic', 'underline', 'strike'],
|
|
43
|
+
['blockquote', 'code-block'],
|
|
44
|
+
[{ 'header': 1 }, { 'header': 2 }],
|
|
45
|
+
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
|
46
|
+
[{ 'script': 'sub' }, { 'script': 'super' }],
|
|
47
|
+
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
|
48
|
+
[{ 'direction': 'rtl' }],
|
|
49
|
+
[{ 'size': ['small', false, 'large', 'huge'] }],
|
|
50
|
+
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
|
51
|
+
[{ 'color': [] }, { 'background': [] }],
|
|
52
|
+
[{ 'font': [] }],
|
|
53
|
+
[{ 'align': [] }],
|
|
54
|
+
['clean'],
|
|
55
|
+
['link', 'image', 'video']
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const emit = defineEmits<RichTextEmits>()
|
|
60
|
+
|
|
61
|
+
const localValue = ref(props.modelValue)
|
|
62
|
+
|
|
63
|
+
const height = computed(() => {
|
|
64
|
+
return typeof props.height === 'number' ? `${props.height}px` : props.height
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
watch(
|
|
70
|
+
() => props.modelValue,
|
|
71
|
+
(newValue) => {
|
|
72
|
+
if (newValue !== localValue.value) {
|
|
73
|
+
localValue.value = newValue
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const handleReady = (quill: any) => {
|
|
79
|
+
emit('ready', quill)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleUpdate = (value: string) => {
|
|
83
|
+
emit('update:modelValue', value)
|
|
84
|
+
emit('change', value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handleTextChange = (_delta: any, _oldDelta: any, source: any) => {
|
|
88
|
+
if (source === 'user') {
|
|
89
|
+
emit('change', localValue.value)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleImageUpload = (file: File, callback: (url: string) => void) => {
|
|
94
|
+
emit('image-upload', file, callback)
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<style scoped lang="scss">
|
|
99
|
+
@use '../../styles/variables' as *;
|
|
100
|
+
|
|
101
|
+
.#{$namespace}-rich-text {
|
|
102
|
+
border: 1px solid $border-color;
|
|
103
|
+
border-radius: $border-radius-base;
|
|
104
|
+
transition: all 0.2s ease;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
|
|
107
|
+
&:hover {
|
|
108
|
+
border-color: $primary-color;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&--disabled {
|
|
112
|
+
background-color: $disabled-bg;
|
|
113
|
+
border-color: $border-color;
|
|
114
|
+
cursor: not-allowed;
|
|
115
|
+
|
|
116
|
+
.ql-container {
|
|
117
|
+
background-color: $disabled-bg;
|
|
118
|
+
cursor: not-allowed;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ql-container {
|
|
123
|
+
font-size: 14px;
|
|
124
|
+
height: 100%;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.ql-editor {
|
|
128
|
+
min-height: calc(100% - 42px);
|
|
129
|
+
line-height: 1.5;
|
|
130
|
+
max-height: calc(100% - 42px);
|
|
131
|
+
overflow-y: auto;
|
|
132
|
+
padding-bottom: 40px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ql-toolbar.ql-snow {
|
|
136
|
+
border-bottom: 1px solid $border-color;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ql-snow .ql-picker-label {
|
|
140
|
+
color: $text-color;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.ql-snow .ql-picker-options {
|
|
144
|
+
color: $text-color;
|
|
145
|
+
background-color: #fff;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.ql-snow .ql-picker-item:hover {
|
|
149
|
+
background-color: $primary-color-light;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ql-snow .ql-picker-item.ql-selected {
|
|
153
|
+
color: $primary-color;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ql-snow .ql-button:hover {
|
|
157
|
+
color: $primary-color;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.ql-snow .ql-active {
|
|
161
|
+
color: $primary-color;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ql-snow .ql-tooltip {
|
|
165
|
+
background-color: #fff;
|
|
166
|
+
border: 1px solid $border-color;
|
|
167
|
+
border-radius: $border-radius-base;
|
|
168
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ql-snow .ql-tooltip input[type=text] {
|
|
172
|
+
border: 1px solid $border-color;
|
|
173
|
+
border-radius: $border-radius-base;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ql-snow .ql-tooltip input[type=text]:focus {
|
|
177
|
+
border-color: $primary-color;
|
|
178
|
+
box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ql-snow .ql-tooltip button {
|
|
182
|
+
background-color: $primary-color;
|
|
183
|
+
color: #fff;
|
|
184
|
+
border: none;
|
|
185
|
+
border-radius: $border-radius-base;
|
|
186
|
+
padding: 4px 8px;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
|
|
189
|
+
&:hover {
|
|
190
|
+
background-color: $primary-color-dark;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.ql-snow .ql-tooltip button.ql-action {
|
|
195
|
+
background-color: $success-color;
|
|
196
|
+
|
|
197
|
+
&:hover {
|
|
198
|
+
background-color: $success-color-dark;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ql-snow .ql-tooltip button.ql-remove {
|
|
203
|
+
background-color: $danger-color;
|
|
204
|
+
|
|
205
|
+
&:hover {
|
|
206
|
+
background-color: $danger-color-dark;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// 简化 Quill 类型定义
|
|
2
|
+
type QuillType = any
|
|
3
|
+
|
|
4
|
+
export interface RichTextProps {
|
|
5
|
+
/**
|
|
6
|
+
* 绑定的值
|
|
7
|
+
*/
|
|
8
|
+
modelValue?: string
|
|
9
|
+
/**
|
|
10
|
+
* 编辑器高度
|
|
11
|
+
*/
|
|
12
|
+
height?: string | number
|
|
13
|
+
/**
|
|
14
|
+
* 是否禁用
|
|
15
|
+
*/
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* 占位文本
|
|
19
|
+
*/
|
|
20
|
+
placeholder?: string
|
|
21
|
+
/**
|
|
22
|
+
* 自定义工具栏配置
|
|
23
|
+
*/
|
|
24
|
+
toolbar?: any[]
|
|
25
|
+
/**
|
|
26
|
+
* 自定义类名
|
|
27
|
+
*/
|
|
28
|
+
class?: string
|
|
29
|
+
/**
|
|
30
|
+
* 自定义样式
|
|
31
|
+
*/
|
|
32
|
+
style?: string | object
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RichTextEmits {
|
|
36
|
+
/**
|
|
37
|
+
* 值变化事件
|
|
38
|
+
*/
|
|
39
|
+
(e: 'update:modelValue', value: string): void
|
|
40
|
+
/**
|
|
41
|
+
* 编辑器初始化完成事件
|
|
42
|
+
*/
|
|
43
|
+
(e: 'ready', quill: QuillType): void
|
|
44
|
+
/**
|
|
45
|
+
* 内容变化事件
|
|
46
|
+
*/
|
|
47
|
+
(e: 'change', value: string): void
|
|
48
|
+
/**
|
|
49
|
+
* 图片上传事件
|
|
50
|
+
*/
|
|
51
|
+
(e: 'image-upload', file: File, callback: (url: string) => void): void
|
|
52
|
+
}
|