@houaoran/designer 1.0.0

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/components.es.js +11424 -0
  4. package/dist/components.umd.js +878 -0
  5. package/dist/index.es.js +39113 -0
  6. package/dist/index.umd.js +1187 -0
  7. package/package.json +96 -0
  8. package/src/components/DragBox.vue +49 -0
  9. package/src/components/DragTool.vue +235 -0
  10. package/src/components/EventConfig.vue +557 -0
  11. package/src/components/FcDesigner.vue +2569 -0
  12. package/src/components/FcTitle.vue +69 -0
  13. package/src/components/FetchConfig.vue +415 -0
  14. package/src/components/FieldInput.vue +371 -0
  15. package/src/components/FnConfig.vue +315 -0
  16. package/src/components/FnEditor.vue +327 -0
  17. package/src/components/FnInput.vue +103 -0
  18. package/src/components/FormLabel.vue +47 -0
  19. package/src/components/HtmlEditor.vue +125 -0
  20. package/src/components/JsonPreview.vue +146 -0
  21. package/src/components/OptionsTextInput.vue +151 -0
  22. package/src/components/PropsInput.vue +72 -0
  23. package/src/components/Required.vue +75 -0
  24. package/src/components/Row.vue +26 -0
  25. package/src/components/SignaturePad.vue +176 -0
  26. package/src/components/Struct.vue +153 -0
  27. package/src/components/StructEditor.vue +121 -0
  28. package/src/components/StructTree.vue +209 -0
  29. package/src/components/TableOptions.vue +164 -0
  30. package/src/components/TreeOptions.vue +167 -0
  31. package/src/components/TypeSelect.vue +144 -0
  32. package/src/components/Validate.vue +302 -0
  33. package/src/components/ValueInput.vue +89 -0
  34. package/src/components/Warning.vue +46 -0
  35. package/src/components/ai/AiPanel.vue +1122 -0
  36. package/src/components/ai/MarkdownRenderer.vue +548 -0
  37. package/src/components/language/LanguageConfig.vue +174 -0
  38. package/src/components/language/LanguageInput.vue +191 -0
  39. package/src/components/style/BackgroundInput.vue +315 -0
  40. package/src/components/style/BorderInput.vue +242 -0
  41. package/src/components/style/BoxSizeInput.vue +166 -0
  42. package/src/components/style/BoxSpaceInput.vue +269 -0
  43. package/src/components/style/ColorInput.vue +90 -0
  44. package/src/components/style/ConfigItem.vue +118 -0
  45. package/src/components/style/FontInput.vue +197 -0
  46. package/src/components/style/PositionInput.vue +146 -0
  47. package/src/components/style/RadiusInput.vue +164 -0
  48. package/src/components/style/ShadowContent.vue +335 -0
  49. package/src/components/style/ShadowInput.vue +91 -0
  50. package/src/components/style/SizeInput.vue +118 -0
  51. package/src/components/style/StyleConfig.vue +307 -0
  52. package/src/components/table/Table.vue +252 -0
  53. package/src/components/table/TableView.vue +1058 -0
  54. package/src/components/tableForm/TableForm.vue +471 -0
  55. package/src/components/tableForm/TableFormColumnView.vue +103 -0
  56. package/src/components/tableForm/TableFormView.vue +46 -0
  57. package/src/components/tree/FcTree.vue +713 -0
  58. package/src/components/tree/FcTreeNode.vue +216 -0
  59. package/src/config/base/field.js +43 -0
  60. package/src/config/base/form.js +132 -0
  61. package/src/config/base/style.js +26 -0
  62. package/src/config/base/validate.js +15 -0
  63. package/src/config/index.js +70 -0
  64. package/src/config/menu.js +24 -0
  65. package/src/config/rule/alert.js +45 -0
  66. package/src/config/rule/button.js +49 -0
  67. package/src/config/rule/card.js +40 -0
  68. package/src/config/rule/cascader.js +121 -0
  69. package/src/config/rule/checkbox.js +68 -0
  70. package/src/config/rule/col.js +86 -0
  71. package/src/config/rule/collapse.js +30 -0
  72. package/src/config/rule/collapseItem.js +36 -0
  73. package/src/config/rule/color.js +53 -0
  74. package/src/config/rule/date.js +66 -0
  75. package/src/config/rule/dateRange.js +60 -0
  76. package/src/config/rule/divider.js +31 -0
  77. package/src/config/rule/editor.js +31 -0
  78. package/src/config/rule/group.js +86 -0
  79. package/src/config/rule/html.js +43 -0
  80. package/src/config/rule/image.js +32 -0
  81. package/src/config/rule/input.js +62 -0
  82. package/src/config/rule/number.js +49 -0
  83. package/src/config/rule/password.js +52 -0
  84. package/src/config/rule/radio.js +43 -0
  85. package/src/config/rule/rate.js +44 -0
  86. package/src/config/rule/row.js +46 -0
  87. package/src/config/rule/select.js +70 -0
  88. package/src/config/rule/signaturePad.js +59 -0
  89. package/src/config/rule/slider.js +53 -0
  90. package/src/config/rule/space.js +44 -0
  91. package/src/config/rule/subForm.js +47 -0
  92. package/src/config/rule/switch.js +46 -0
  93. package/src/config/rule/tabPane.js +29 -0
  94. package/src/config/rule/table.js +37 -0
  95. package/src/config/rule/tableForm.js +115 -0
  96. package/src/config/rule/tableFormColumn.js +55 -0
  97. package/src/config/rule/tabs.js +38 -0
  98. package/src/config/rule/tag.js +69 -0
  99. package/src/config/rule/text.js +41 -0
  100. package/src/config/rule/textarea.js +63 -0
  101. package/src/config/rule/time.js +58 -0
  102. package/src/config/rule/timeRange.js +49 -0
  103. package/src/config/rule/title.js +37 -0
  104. package/src/config/rule/transfer.js +59 -0
  105. package/src/config/rule/tree.js +70 -0
  106. package/src/config/rule/treeSelect.js +77 -0
  107. package/src/config/rule/upload.js +107 -0
  108. package/src/form/index.js +19 -0
  109. package/src/index.js +173 -0
  110. package/src/locale/en.js +981 -0
  111. package/src/locale/zh-cn.js +983 -0
  112. package/src/style/fonts/fc-icons.woff +0 -0
  113. package/src/style/icon.css +1052 -0
  114. package/src/style/index.css +836 -0
  115. package/src/utils/form.js +9 -0
  116. package/src/utils/highlight/highlight.min.js +307 -0
  117. package/src/utils/highlight/javascript.min.js +80 -0
  118. package/src/utils/highlight/style.css +1 -0
  119. package/src/utils/highlight/xml.min.js +29 -0
  120. package/src/utils/hintStubs.js +120 -0
  121. package/src/utils/index.js +544 -0
  122. package/src/utils/jsonDiff.js +173 -0
  123. package/src/utils/locale.js +23 -0
  124. package/src/utils/message.js +19 -0
  125. package/src/utils/template.js +105 -0
  126. package/types/index.d.ts +575 -0
@@ -0,0 +1,174 @@
1
+ <template>
2
+ <div class="_fd-language-config">
3
+ <div class="_fc-l-label">{{ t('language.name') }}</div>
4
+ <div class="_fc-l-info">
5
+ {{ t('warning.language') }}
6
+ </div>
7
+ <div class="_fd-lc-header">
8
+ <el-button size="small" @click="addColumn">{{ t('language.add') }}</el-button>
9
+ <el-button size="small" type="danger" plain :disabled="!selected.length" @click="batchRmColumn">
10
+ {{ t('language.batchRemove') }}
11
+ </el-button>
12
+ </div>
13
+ <div class="_fd-lc-body">
14
+ <el-table :data="column" size="small" ref="table"
15
+ @selection-change="selectionChange" row-key="key">
16
+ <el-table-column type="selection" width="30px"></el-table-column>
17
+ <el-table-column prop="key" label="Key" width="90px"></el-table-column>
18
+ <template v-for="item in localeOptions" :key="item.value">
19
+ <el-table-column :prop="item.value" :label="item.label" min-width="100px">
20
+ <template #default="scope">
21
+ <template v-if="scope.row.input">
22
+ <el-input size="small" v-model="scope.row[item.value]" @blur="saveColumn(scope.row, true)"></el-input>
23
+ </template>
24
+ <template v-else>
25
+ {{ scope.row[item.value] || '-' }}
26
+ </template>
27
+ </template>
28
+ </el-table-column>
29
+ </template>
30
+ <el-table-column width="75px" :label="t('tableOptions.handle')" fixed="right">
31
+ <template #default="scope">
32
+ <div class="_fd-lc-handle">
33
+ <i class="fc-icon icon-edit" v-if="!scope.row.input" @click="scope.row.input = true"></i>
34
+ <i class="fc-icon icon-check" v-else @click="saveColumn(scope.row)"></i>
35
+ <i class="fc-icon icon-group" @click="copy(scope.row.key)"></i>
36
+ <i class="fc-icon icon-delete-circle" @click="rmColumn(scope.$index)"></i>
37
+ </div>
38
+ </template>
39
+ </el-table-column>
40
+ </el-table>
41
+ </div>
42
+ </div>
43
+
44
+ </template>
45
+
46
+ <script>
47
+ import {defineComponent} from 'vue';
48
+ import {copyTextToClipboard} from '../../utils';
49
+
50
+ export default defineComponent({
51
+ name: 'LanguageConfig',
52
+ inject: ['designer'],
53
+ computed: {
54
+ localeOptions() {
55
+ return this.designer.setupState.getConfig('localeOptions', [
56
+ {value: 'zh-cn', label: '简体中文'},
57
+ {value: 'en', label: 'English'},
58
+ ]);
59
+ },
60
+ t() {
61
+ return this.designer.setupState.t;
62
+ },
63
+ },
64
+ data() {
65
+ return {
66
+ column: [],
67
+ uni: 0,
68
+ selected: [],
69
+ }
70
+ },
71
+ methods: {
72
+ copy(key) {
73
+ copyTextToClipboard(key);
74
+ },
75
+ addColumn() {
76
+ this.column.unshift({
77
+ key: this.randomString(),
78
+ input: true,
79
+ })
80
+ },
81
+ saveColumn(row, input) {
82
+ row.input = input || false;
83
+ const language = this.designer.setupState.formOptions.language;
84
+ this.localeOptions.forEach(item => {
85
+ if (!language[item.value]) {
86
+ language[item.value] = {};
87
+ }
88
+ language[item.value][row.key] = row[item.value];
89
+ })
90
+ },
91
+ rmColumn(idx) {
92
+ const row = this.column[idx];
93
+ this.column.splice(idx, 1);
94
+ const language = this.designer.setupState.formOptions.language;
95
+ this.localeOptions.forEach(item => {
96
+ if (language[item.value]) {
97
+ delete language[item.value][row.key]
98
+ }
99
+ })
100
+ },
101
+ batchRmColumn() {
102
+ this.selected.forEach(item => {
103
+ this.rmColumn(this.column.indexOf(item));
104
+ });
105
+ this.selected = [];
106
+ },
107
+ selectionChange(list) {
108
+ this.selected = list;
109
+ },
110
+ randomString() {
111
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
112
+ let result = '';
113
+ const charactersLength = characters.length;
114
+
115
+ for (let i = 0; i < 7; i++) {
116
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
117
+ }
118
+ return characters.charAt((this.uni++) % 26) + result;
119
+ }
120
+ },
121
+ mounted() {
122
+ const language = this.designer.setupState.formOptions.language || {};
123
+ const column = {};
124
+ Object.keys(language).forEach(lang => {
125
+ Object.keys(language[lang]).forEach(key => {
126
+ if (!column[key]) {
127
+ column[key] = {
128
+ key: key,
129
+ }
130
+ }
131
+ column[key][lang] = language[lang][key];
132
+ })
133
+ });
134
+ this.column = Object.values(column);
135
+ }
136
+
137
+ });
138
+ </script>
139
+
140
+ <style>
141
+ ._fd-language-config {
142
+ height: 100%;
143
+ overflow: auto;
144
+ }
145
+
146
+ ._fd-lc-body, ._fd-lc-header {
147
+ padding: 0 12px;
148
+ }
149
+
150
+ ._fd-lc-body {
151
+ overflow: auto;
152
+ }
153
+
154
+ ._fd-lc-header {
155
+ display: flex;
156
+ justify-content: flex-end;
157
+ margin-bottom: 12px;
158
+ }
159
+
160
+ ._fd-language-config .el-table__cell {
161
+ height: 34px;
162
+ }
163
+
164
+ ._fd-language-config ._fc-l-info {
165
+ margin-bottom: 12px;
166
+ }
167
+
168
+ ._fd-lc-handle {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ cursor: pointer;
173
+ }
174
+ </style>
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <el-input class="_fd-language-input" :class="{'is-variable': isVar}" :placeholder="placeholder" :disabled="disabled"
3
+ :modelValue="modelValue"
4
+ @update:modelValue="onInput"
5
+ @blur="$emit('blur')"
6
+ :size="size || 'small'">
7
+ <template #append v-if="showLanguage !== false">
8
+ <el-popover placement="bottom-end" :width="300" :hide-after="0" trigger="click" ref="pop"
9
+ popper-class="_fd-language-popover">
10
+ <template #reference>
11
+ <i class="fc-icon icon-language"></i>
12
+ </template>
13
+ <div class="_fd-language-list">
14
+ <div class="_fd-language-header">
15
+ <div class="_fd-language-title">
16
+ {{ t('language.select') }}<i class="fc-icon icon-setting" @click="openConfig"></i>
17
+ </div>
18
+ <div class="_fd-language-name">
19
+ <template v-for="item in localeList" :key="item.value">
20
+ <div>{{ item.label }}</div>
21
+ </template>
22
+ </div>
23
+ </div>
24
+ <template v-for="lang in language" :key="lang.key">
25
+ <div class="_fd-language-item" @click="clickLang(lang.key)">
26
+ <template v-for="item in localeList" :key="item.value">
27
+ <div>{{ lang[item.value] || '-' }}</div>
28
+ </template>
29
+ </div>
30
+ </template>
31
+ </div>
32
+ </el-popover>
33
+ </template>
34
+ </el-input>
35
+ </template>
36
+
37
+ <script>
38
+ import {defineComponent} from 'vue';
39
+
40
+ export default defineComponent({
41
+ name: 'LanguageInput',
42
+ inject: ['designer'],
43
+ emits: ['update:modelValue', 'blur', 'change'],
44
+ props: {
45
+ size: String,
46
+ placeholder: String,
47
+ modelValue: String,
48
+ disabled: Boolean,
49
+ },
50
+ computed: {
51
+ isVar() {
52
+ return !!(this.modelValue || '').match(/^\{\{\s*\$t\.(.+)\s*\}\}$/);
53
+ },
54
+ t() {
55
+ return this.designer.setupState.t;
56
+ },
57
+ localeList() {
58
+ const localeOptions = this.designer.setupState.getConfig('localeOptions', [
59
+ {value: 'zh-cn', label: '简体中文'},
60
+ {value: 'en', label: 'English'},
61
+ ]);
62
+ const localeList = [];
63
+ const locale = this.designer.props?.locale?.name || 'zh-cn';
64
+ localeOptions.forEach((item) => {
65
+ if (item.value === locale) {
66
+ localeList.unshift(item);
67
+ } else if (localeList.length < 2) {
68
+ localeList.push(item);
69
+ }
70
+ });
71
+ if (localeList.length > 2) {
72
+ localeList.pop();
73
+ }
74
+ return localeList;
75
+ },
76
+ showLanguage() {
77
+ return this.designer.setupState.getConfig('showLanguage');
78
+ },
79
+ language() {
80
+ const language = this.designer.setupState.formOptions.language || {};
81
+ const column = {};
82
+ Object.keys(language).forEach(lang => {
83
+ Object.keys(language[lang]).forEach(key => {
84
+ if (!column[key]) {
85
+ column[key] = {
86
+ key: key,
87
+ }
88
+ }
89
+ column[key][lang] = language[lang][key];
90
+ })
91
+ });
92
+ return Object.values(column);
93
+ }
94
+ },
95
+ methods: {
96
+ openConfig() {
97
+ this.designer.setupState.activeModule = 'language';
98
+ },
99
+ clickLang(key) {
100
+ this.onInput(`{{$t.${key}}}`);
101
+ this.$refs.pop.hide();
102
+ },
103
+ onInput(val) {
104
+ this.$emit('update:modelValue', val);
105
+ this.$emit('change', val);
106
+ }
107
+ },
108
+ mounted() {
109
+ }
110
+
111
+ });
112
+ </script>
113
+
114
+ <style>
115
+ ._fd-language-list {
116
+ max-height: 320px;
117
+ padding-top: 70px;
118
+ overflow: auto;
119
+ }
120
+
121
+ ._fd-language-input .el-input-group__append {
122
+ width: 25px;
123
+ padding: 0;
124
+ margin: 0;
125
+ color: #AAAAAA;
126
+ cursor: pointer;
127
+ }
128
+
129
+ ._fd-language-input.is-variable input {
130
+ color: #2E73FF;
131
+ }
132
+
133
+ ._fd-language-header, ._fd-language-item {
134
+ display: flex;
135
+ border-bottom: 1px solid #ECECEC;
136
+ padding: 0 12px;
137
+ }
138
+
139
+ ._fd-language-header {
140
+ font-weight: 500;
141
+ padding-top: 10px;
142
+ overflow: auto;
143
+ color: #262626;
144
+ position: absolute;
145
+ top: 0;
146
+ left: 0;
147
+ right: 0;
148
+ background-color: #FFFFFF;
149
+ flex-direction: column;
150
+ }
151
+
152
+ ._fd-language-name > div, ._fd-language-item > div {
153
+ flex: 1;
154
+ font-size: 12px;
155
+ padding: 5px;
156
+ min-width: 70px;
157
+ }
158
+
159
+ ._fd-language-title {
160
+ margin: 6px 0;
161
+ }
162
+
163
+ ._fd-language-title .fc-icon {
164
+ color: #2E73FF;
165
+ cursor: pointer;
166
+ font-size: 14px;
167
+ }
168
+
169
+ ._fd-language-name {
170
+ display: flex;
171
+ }
172
+
173
+ ._fd-language-name > div {
174
+ white-space: nowrap;
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ }
178
+
179
+ ._fd-language-item {
180
+ cursor: pointer;
181
+ }
182
+
183
+ ._fd-language-item:hover {
184
+ color: #2E73FF;
185
+ background-color: #CCDFFF;
186
+ }
187
+
188
+ ._fd-language-popover {
189
+ padding: 0 !important;
190
+ }
191
+ </style>
@@ -0,0 +1,315 @@
1
+ <template>
2
+ <ConfigItem :label="t('style.background.name')">
3
+ <ColorInput v-model="backgroundColor" @change="onInput"></ColorInput>
4
+ <template #append>
5
+ <div class="_fd-background-input">
6
+ <el-form label-width="50px" label-position="top" inline size="small">
7
+ <el-form-item :label="t('style.background.image')" class="_fd-bg-image-item">
8
+ <el-input
9
+ v-model="backgroundImageUrl"
10
+ clearable
11
+ @change="onImageUrlChange"
12
+ @keydown.enter="onImageUrlChange"
13
+ :placeholder="t('style.background.placeholder')"
14
+ >
15
+ <template #append>
16
+ <div style="cursor: pointer" @click="handleImageInput"><i class="fc-icon icon-image"></i></div>
17
+ </template>
18
+ </el-input>
19
+ </el-form-item>
20
+ <div class="_fd-bg-size-repeat-row">
21
+ <el-form-item :label="t('style.background.size.name')" class="_fd-bg-size-item">
22
+ <el-select v-model="sizeSelectValue" clearable @change="onSizeSelectChange" style="width: 100%">
23
+ <el-option v-for="item in sizeType" :key="item.value" :label="item.label" :value="item.value" />
24
+ </el-select>
25
+ <div class="_fd-bg-size-custom" v-if="sizeSelectValue === 'custom'">
26
+ <SizeInput v-model="backgroundSizeX" @change="onSizeChange" />
27
+ <span style="margin: 0 5px">×</span>
28
+ <SizeInput v-model="backgroundSizeY" @change="onSizeChange" />
29
+ </div>
30
+ </el-form-item>
31
+ <el-form-item :label="t('style.background.repeat.name')" class="_fd-bg-repeat-item">
32
+ <el-select v-model="backgroundStyle.backgroundRepeat" clearable @change="onInput" style="width: 100%">
33
+ <el-option v-for="item in repeatType" :key="item.value" :label="item.label" :value="item.value" />
34
+ </el-select>
35
+ </el-form-item>
36
+ </div>
37
+ <el-form-item :label="t('style.background.position')" class="_fd-bg-position-item">
38
+ <div class="_fd-bg-position">
39
+ <SizeInput v-model="backgroundPositionX" @change="onPositionChange" />
40
+ <SizeInput v-model="backgroundPositionY" @change="onPositionChange" />
41
+ </div>
42
+ </el-form-item>
43
+ </el-form>
44
+ </div>
45
+ </template>
46
+ </ConfigItem>
47
+ </template>
48
+
49
+ <script>
50
+ import { defineComponent } from 'vue';
51
+ import SizeInput from './SizeInput.vue';
52
+ import ConfigItem from './ConfigItem.vue';
53
+ import ColorInput from './ColorInput.vue';
54
+
55
+ export default defineComponent({
56
+ name: 'BackgroundInput',
57
+ components: { SizeInput, ConfigItem, ColorInput },
58
+ inject: ['designer'],
59
+ emits: ['update:modelValue', 'change'],
60
+ props: {
61
+ modelValue: {
62
+ type: Object,
63
+ default: () => ({}),
64
+ },
65
+ },
66
+ watch: {
67
+ modelValue() {
68
+ this.tidyValue();
69
+ },
70
+ },
71
+ computed: {
72
+ t() {
73
+ return this.designer.setupState.t;
74
+ },
75
+ sizeType() {
76
+ const size = this.t('style.background.size') || {};
77
+ return [
78
+ { label: size.cover || 'cover', value: 'cover' },
79
+ { label: size.contain || 'contain', value: 'contain' },
80
+ { label: size.auto || 'auto', value: 'auto' },
81
+ ];
82
+ },
83
+ repeatType() {
84
+ const repeat = this.t('style.background.repeat') || {};
85
+ return ['no-repeat', 'repeat', 'repeat-x', 'repeat-y', 'round', 'space'].map(v => {
86
+ return { label: repeat[v] || v, value: v };
87
+ });
88
+ },
89
+ },
90
+ data() {
91
+ return {
92
+ backgroundColor: '',
93
+ backgroundImageUrl: '',
94
+ backgroundStyle: {
95
+ backgroundImage: '',
96
+ backgroundSize: '',
97
+ backgroundPosition: '',
98
+ backgroundRepeat: '',
99
+ },
100
+ backgroundSizeX: '',
101
+ backgroundSizeY: '',
102
+ backgroundPositionX: '',
103
+ backgroundPositionY: '',
104
+ sizeSelectValue: '',
105
+ };
106
+ },
107
+ methods: {
108
+ tidyValue() {
109
+ const value = this.modelValue || {};
110
+ this.backgroundColor = value.backgroundColor || '';
111
+
112
+ // 解析 backgroundImage,提取url()中的内容
113
+ const bgImage = value.backgroundImage || '';
114
+ if (bgImage) {
115
+ const match = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
116
+ this.backgroundImageUrl = match ? match[1] : bgImage;
117
+ } else {
118
+ this.backgroundImageUrl = '';
119
+ }
120
+
121
+ this.backgroundStyle = {
122
+ backgroundImage: bgImage,
123
+ backgroundSize: value.backgroundSize || '',
124
+ backgroundPosition: value.backgroundPosition || '',
125
+ backgroundRepeat: value.backgroundRepeat || '',
126
+ };
127
+
128
+ // 解析 backgroundSize
129
+ this.parseBackgroundSize(value.backgroundSize);
130
+
131
+ // 解析 backgroundPosition
132
+ this.parseBackgroundPosition(value.backgroundPosition);
133
+ },
134
+ parseBackgroundSize(size) {
135
+ if (!size) {
136
+ this.sizeSelectValue = '';
137
+ this.backgroundSizeX = '';
138
+ this.backgroundSizeY = '';
139
+ return;
140
+ }
141
+ if (size === 'cover' || size === 'contain' || size === 'auto') {
142
+ this.sizeSelectValue = size;
143
+ this.backgroundSizeX = '';
144
+ this.backgroundSizeY = '';
145
+ return;
146
+ }
147
+ // 自定义尺寸
148
+ const parts = size.split(/\s+/);
149
+ this.backgroundSizeX = parts[0] || '';
150
+ this.backgroundSizeY = parts[1] || '';
151
+ this.sizeSelectValue = 'custom';
152
+ },
153
+ parseBackgroundPosition(position) {
154
+ if (!position) {
155
+ this.backgroundPositionX = '';
156
+ this.backgroundPositionY = '';
157
+ return;
158
+ }
159
+ const parts = position.split(/\s+/);
160
+ this.backgroundPositionX = parts[0] || '';
161
+ this.backgroundPositionY = parts[1] || '';
162
+ },
163
+ onSizeSelectChange(value) {
164
+ if (!value) {
165
+ this.backgroundStyle.backgroundSize = '';
166
+ this.backgroundSizeX = '';
167
+ this.backgroundSizeY = '';
168
+ } else if (value === 'custom') {
169
+ // 切换到自定义模式,保持当前值或使用默认值
170
+ if (this.backgroundSizeX || this.backgroundSizeY) {
171
+ const size = [this.backgroundSizeX || 'auto', this.backgroundSizeY || 'auto'].join(' ');
172
+ this.backgroundStyle.backgroundSize = size;
173
+ } else {
174
+ this.backgroundStyle.backgroundSize = '';
175
+ }
176
+ } else {
177
+ // 预设值
178
+ this.backgroundStyle.backgroundSize = value;
179
+ this.backgroundSizeX = '';
180
+ this.backgroundSizeY = '';
181
+ }
182
+ this.onInput();
183
+ },
184
+ onSizeChange() {
185
+ if (this.backgroundSizeX || this.backgroundSizeY) {
186
+ const size = [this.backgroundSizeX || 'auto', this.backgroundSizeY || 'auto'].join(' ');
187
+ this.backgroundStyle.backgroundSize = size;
188
+ } else {
189
+ this.backgroundStyle.backgroundSize = '';
190
+ }
191
+ this.onInput();
192
+ },
193
+ onPositionChange() {
194
+ if (this.backgroundPositionX || this.backgroundPositionY) {
195
+ const position = [this.backgroundPositionX || '0', this.backgroundPositionY || '0'].join(' ');
196
+ this.backgroundStyle.backgroundPosition = position;
197
+ } else {
198
+ this.backgroundStyle.backgroundPosition = '';
199
+ }
200
+ this.onInput();
201
+ },
202
+ onImageUrlChange() {
203
+ if (this.backgroundImageUrl) {
204
+ this.backgroundStyle.backgroundImage = `url(${this.backgroundImageUrl})`;
205
+ } else {
206
+ this.backgroundStyle.backgroundImage = '';
207
+ }
208
+ this.onInput();
209
+ },
210
+ handleImageInput() {
211
+ const input = document.createElement('input');
212
+ input.type = 'file';
213
+ input.accept = 'image/*';
214
+ input.onchange = e => {
215
+ const file = e.target.files[0];
216
+ if (file) {
217
+ const reader = new FileReader();
218
+ reader.onload = event => {
219
+ this.backgroundImageUrl = event.target.result;
220
+ this.backgroundStyle.backgroundImage = `url(${event.target.result})`;
221
+ this.onInput();
222
+ };
223
+ reader.readAsDataURL(file);
224
+ }
225
+ };
226
+ input.click();
227
+ },
228
+ onInput() {
229
+ const style = {
230
+ backgroundColor: this.backgroundColor || '',
231
+ ...Object.keys(this.backgroundStyle).reduce((acc, key) => {
232
+ if (this.backgroundStyle[key] !== '') {
233
+ acc[key] = this.backgroundStyle[key];
234
+ }
235
+ return acc;
236
+ }, {}),
237
+ };
238
+ // 移除空值
239
+ Object.keys(style).forEach(k => {
240
+ if (style[k] === '') {
241
+ delete style[k];
242
+ }
243
+ });
244
+ this.$emit('update:modelValue', style);
245
+ this.$emit('change', style);
246
+ },
247
+ },
248
+ created() {
249
+ this.tidyValue();
250
+ },
251
+ });
252
+ </script>
253
+
254
+ <style>
255
+ ._fd-background-input {
256
+ display: flex;
257
+ justify-content: center;
258
+ padding: 0 5px;
259
+ }
260
+
261
+ ._fd-background-input .el-form {
262
+ display: flex;
263
+ flex-direction: column;
264
+ width: 100%;
265
+ }
266
+
267
+ ._fd-background-input .el-input-group__append {
268
+ padding: 0;
269
+ width: 24px;
270
+ }
271
+
272
+ ._fd-bg-image-item {
273
+ width: 100%;
274
+ }
275
+
276
+ ._fd-bg-size-repeat-row {
277
+ display: flex;
278
+ width: 100%;
279
+ gap: 10px;
280
+ }
281
+
282
+ ._fd-bg-size-item,
283
+ ._fd-bg-repeat-item {
284
+ flex: 1;
285
+ }
286
+
287
+ ._fd-bg-position-item {
288
+ width: 100%;
289
+ }
290
+
291
+ ._fd-background-input .el-form--inline .el-form-item {
292
+ margin: 0;
293
+ padding: 0;
294
+ }
295
+
296
+ ._fd-background-input ._fd-size-input .el-input-number--small {
297
+ width: 100%;
298
+ }
299
+
300
+ ._fd-bg-size-custom {
301
+ display: flex;
302
+ align-items: center;
303
+ margin-top: 5px;
304
+ }
305
+
306
+ ._fd-bg-position {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 5px;
310
+ }
311
+
312
+ ._fd-bg-position ._fd-size-input {
313
+ flex: 1;
314
+ }
315
+ </style>