xt-element-ui 1.2.5 → 1.2.7

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 (42) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +240 -104
  4. package/docs/README.md +67 -168
  5. package/docs/components/base/xt-card-item.md +1 -1
  6. package/docs/components/base/xt-step-price.md +280 -0
  7. package/docs/components/base/xt-time.md +165 -0
  8. package/docs/components/extend/ex-chart.md +252 -144
  9. package/docs/components/extend/ex-date-picker.md +0 -0
  10. package/docs/components/extend/ex-icon.md +1 -2
  11. package/docs/components/extend/ex-page.md +0 -75
  12. package/lib/index.common.js +1172 -586
  13. package/lib/index.css +1 -1
  14. package/lib/index.umd.js +1172 -586
  15. package/lib/index.umd.min.js +5 -5
  16. package/package.json +80 -41
  17. package/src/components/ex-chart/ExBar.vue +35 -29
  18. package/src/components/ex-chart/ExLine.vue +23 -14
  19. package/src/components/ex-chart/ExMulti.vue +30 -52
  20. package/src/components/ex-chart/ExPie.vue +23 -16
  21. package/src/components/ex-chart/theme/dark.js +5 -4
  22. package/src/components/ex-chart/theme/white.js +1 -0
  23. package/src/components/ex-chart/utils.js +148 -7
  24. package/src/components/ex-date-picker/index.js +2 -0
  25. package/src/components/{xt-date-picker → ex-date-picker}/index.vue +1 -1
  26. package/src/components/index.scss +6 -0
  27. package/src/components/xt-card-item/index.vue +3 -1
  28. package/src/components/xt-step-price/index.js +2 -0
  29. package/src/components/xt-step-price/index.vue +270 -0
  30. package/src/components/xt-step-price/style/index.scss +115 -0
  31. package/src/components/xt-step-price-item/index.js +2 -0
  32. package/src/components/xt-step-price-item/index.vue +174 -0
  33. package/src/components/xt-time/index.js +2 -0
  34. package/src/components/xt-time/index.vue +313 -0
  35. package/src/components/xt-time/style/index.scss +23 -0
  36. package/src/index.js +12 -3
  37. package/src/components/ex-chart/theme/blue.js +0 -91
  38. package/src/components/ex-chart/theme/orange.js +0 -92
  39. package/src/components/ex-chart/theme/starry.js +0 -106
  40. package/src/components/xt-date-picker/index.js +0 -2
  41. /package/src/components/{xt-date-picker → ex-date-picker}/SearchDate.vue +0 -0
  42. /package/src/components/{xt-date-picker → ex-date-picker}/quarter.vue +0 -0
@@ -1,16 +1,10 @@
1
1
  import themeWhite from "./theme/white";
2
2
  import themeDark from "./theme/dark";
3
- import themeBlue from "./theme/blue";
4
- import themeStarry from "./theme/starry";
5
- import themeOrange from "./theme/orange";
6
3
 
7
4
  const themeKeys = {
8
5
  "default": themeWhite,
9
6
  "white": themeWhite,
10
- "dark": themeDark,
11
- "blue": themeBlue,
12
- "starry": themeStarry,
13
- "orange": themeOrange
7
+ "dark": themeDark
14
8
  };
15
9
 
16
10
  // echarts 仅在客户端加载,避免 SSR 编译/渲染报错
@@ -121,6 +115,153 @@ EchartsUtil.EchartsUtil = {
121
115
 
122
116
  EchartsUtil.chartInstanceList = [];
123
117
 
118
+ // === 极简模式(simpleMode)预设配置 ===
119
+ // 极简模式:隐藏图例、隐藏坐标轴/网格线、缩小内边距、简化 tooltip、去除动画装饰
120
+
121
+ EchartsUtil.simpleModeOption = {
122
+ legend: { show: false },
123
+ tooltip: {
124
+ borderWidth: 0,
125
+ extraCssText: "box-shadow: 0 2px 8px rgba(0,0,0,0.12);"
126
+ },
127
+ grid: {
128
+ top: "5%",
129
+ left: "5%",
130
+ right: "5%",
131
+ bottom: "5%",
132
+ containLabel: true
133
+ },
134
+ xAxis: {
135
+ axisLine: { show: false },
136
+ axisTick: { show: false },
137
+ splitLine: { show: false }
138
+ },
139
+ yAxis: {
140
+ axisLine: { show: false },
141
+ axisTick: { show: false },
142
+ splitLine: { show: false },
143
+ axisLabel: { show: false }
144
+ },
145
+ animationDuration: 300,
146
+ animationEasing: "linear"
147
+ };
148
+
149
+ // 各类图表的极简模式特殊配置
150
+ EchartsUtil.simpleModeByType = {
151
+ bar: {
152
+ series: {
153
+ barWidth: "60%",
154
+ label: { show: false },
155
+ markPoint: null,
156
+ emphasis: {
157
+ itemStyle: {
158
+ shadowBlur: 6,
159
+ shadowOffsetX: 0,
160
+ shadowColor: "rgba(0,0,0,0.15)"
161
+ }
162
+ }
163
+ }
164
+ },
165
+ line: {
166
+ series: {
167
+ symbol: "none",
168
+ smooth: true,
169
+ symbolSize: 0,
170
+ label: { show: false },
171
+ areaStyle: null
172
+ }
173
+ },
174
+ pie: {
175
+ legend: { show: false },
176
+ title: { show: false },
177
+ series: {
178
+ radius: ["40%", "72%"],
179
+ label: { show: false },
180
+ labelLine: { show: false }
181
+ }
182
+ },
183
+ multi: {
184
+ legend: { show: false },
185
+ grid: {
186
+ top: "8%",
187
+ left: "5%",
188
+ right: "5%",
189
+ bottom: "5%",
190
+ containLabel: true
191
+ }
192
+ }
193
+ };
194
+
195
+ // 深拷贝 + 深合并工具
196
+ function deepMerge(target, source) {
197
+ if (source == null) return target;
198
+ if (Array.isArray(source)) return source.slice();
199
+ if (typeof source !== "object") return source;
200
+ if (target == null || typeof target !== "object") {
201
+ return JSON.parse(JSON.stringify(source));
202
+ }
203
+ const result = Array.isArray(target) ? target.slice() : Object.assign({}, target);
204
+ Object.keys(source).forEach(key => {
205
+ const src = source[key];
206
+ if (src === null || src === undefined) {
207
+ if (source.hasOwnProperty(key)) result[key] = src;
208
+ return;
209
+ }
210
+ if (typeof src === "object") {
211
+ result[key] = deepMerge(result[key], src);
212
+ } else {
213
+ result[key] = src;
214
+ }
215
+ });
216
+ return result;
217
+ }
218
+
219
+ // 对 option 应用极简模式(会保留调用方显式设置的值)
220
+ EchartsUtil.applySimpleMode = function(option, type) {
221
+ if (!option || typeof option !== "object") return option;
222
+
223
+ const base = JSON.parse(JSON.stringify(EchartsUtil.simpleModeOption));
224
+ const typeConfig = EchartsUtil.simpleModeByType[type] || {};
225
+ const merged = deepMerge(base, typeConfig);
226
+
227
+ // 合并 base 与 typeConfig 到当前 option
228
+ Object.keys(merged).forEach(key => {
229
+ if (key === "series") return; // series 单独处理
230
+ if (option[key] == null) {
231
+ option[key] = merged[key];
232
+ } else if (typeof option[key] === "object" && typeof merged[key] === "object" && !Array.isArray(option[key])) {
233
+ option[key] = deepMerge(merged[key], option[key]);
234
+ }
235
+ // 若调用方已有原始值则保留(不覆盖)
236
+ });
237
+
238
+ // series 处理:若存在则对每个 series 应用简化配置
239
+ if (option.series && Array.isArray(option.series)) {
240
+ const simpleSeries = (merged.series || {});
241
+ option.series = option.series.map(s => {
242
+ if (s == null || typeof s !== "object") return s;
243
+ return deepMerge(simpleSeries, s); // 调用方的值优先级更高
244
+ });
245
+ }
246
+
247
+ // 特殊:xAxis / yAxis 可能是数组
248
+ ["xAxis", "yAxis"].forEach(axisKey => {
249
+ if (option[axisKey] != null && typeof option[axisKey] === "object") {
250
+ const simpleAxis = merged[axisKey] || {};
251
+ if (Array.isArray(option[axisKey])) {
252
+ option[axisKey] = option[axisKey].map(axis => {
253
+ if (axis == null || typeof axis !== "object") return axis;
254
+ return deepMerge(simpleAxis, axis);
255
+ });
256
+ } else {
257
+ option[axisKey] = deepMerge(simpleAxis, option[axisKey]);
258
+ }
259
+ }
260
+ });
261
+
262
+ return option;
263
+ };
264
+
124
265
  EchartsUtil.mergeOptions = function(themeOption, customOption) {
125
266
  return Object.assign({}, themeOption, customOption);
126
267
  };
@@ -0,0 +1,2 @@
1
+ import ExDatePicker from './index.vue'
2
+ export default ExDatePicker
@@ -20,7 +20,7 @@ const typeFormatEnum = {
20
20
  import FlexBox from '../xt-flex-box/index.vue'
21
21
  import Quarter from "./quarter.vue";
22
22
  export default {
23
- name: "XtDatePicker",
23
+ name: "ExDatePicker",
24
24
  components: {
25
25
  FlexBox,
26
26
  Quarter
@@ -21,6 +21,12 @@
21
21
  // Text 组件样式
22
22
  @import './xt-text/style/index.scss';
23
23
 
24
+ // StepPrice 组件样式
25
+ @import './xt-step-price/style/index.scss';
26
+
27
+ // Time 组件样式
28
+ @import './xt-time/style/index.scss';
29
+
24
30
  // ExButton 组件样式
25
31
  @import './ex-button/style/index.scss';
26
32
 
@@ -10,7 +10,9 @@
10
10
  </div>
11
11
  </div>
12
12
  <div style="flex: 1; height: 100%;">
13
- <xt-text size="extra-large" :type="type"><i class="el-icon-user"></i></xt-text>
13
+ <slot name="icon">
14
+ <xt-text size="extra-large" :type="type"><i class="el-icon-user"></i></xt-text>
15
+ </slot>
14
16
  </div>
15
17
  </xt-flex-box>
16
18
  </ex-card>
@@ -0,0 +1,2 @@
1
+ import XtStepPrice from './index.vue'
2
+ export default XtStepPrice
@@ -0,0 +1,270 @@
1
+ <template>
2
+ <div class="xt-step-price" :class="{ 'is-disabled': disabled }">
3
+ <div v-if="title || $slots.header" class="xt-step-price__header">
4
+ <xt-text v-if="title" bold size="medium">{{ title }}</xt-text>
5
+ <slot name="header" />
6
+ <el-button
7
+ v-if="!disabled && !isLimitReached"
8
+ type="primary"
9
+ size="small"
10
+ icon="el-icon-plus"
11
+ plain
12
+ @click="onAdd"
13
+ >新增阶梯</el-button>
14
+ <xt-text v-if="isLimitReached" size="small" type-color="info">已达上限({{ localItems.length }}/{{ limit }})</xt-text>
15
+ </div>
16
+
17
+ <div class="xt-step-price__list">
18
+ <XtStepPriceItem
19
+ v-for="(item, idx) in localItems"
20
+ :key="idx"
21
+ :value="item"
22
+ :index="idx"
23
+ :is-first="idx === 0"
24
+ :is-last="idx === localItems.length - 1"
25
+ :items-length="localItems.length"
26
+ :removable="idx !== 0"
27
+ :min-locked="idx !== 0 ? true : false"
28
+ :unit="unit"
29
+ :precision="precision"
30
+ :left-bracket="leftBracket"
31
+ :right-bracket="rightBracket"
32
+ :field-keys="fieldKeys"
33
+ :disabled="disabled"
34
+ @input="(val) => onItemInput(val, idx)"
35
+ @max-change="onMaxChange"
36
+ @min-change="onMinChange"
37
+ @delete="onDelete"
38
+ />
39
+ </div>
40
+
41
+ <div v-if="localItems.length === 0" class="xt-step-price__empty">
42
+ <span>暂无数据,点击右上角「新增阶梯」开始配置</span>
43
+ </div>
44
+
45
+ <div v-if="tip || $slots.tip" class="xt-step-price__tip">
46
+ <slot name="tip">
47
+ <xt-text size="small" type-color="warning">{{ tip }}</xt-text>
48
+ </slot>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script>
54
+ import XtStepPriceItem from '../xt-step-price-item/index.vue'
55
+
56
+ export default {
57
+ name: 'XtStepPrice',
58
+
59
+ components: { XtStepPriceItem },
60
+
61
+ computed: {
62
+ keyMin() { return (this.fieldKeys && this.fieldKeys.min) || 'min' },
63
+ keyMax() { return (this.fieldKeys && this.fieldKeys.max) || 'max' },
64
+ keyPrice() { return (this.fieldKeys && this.fieldKeys.price) || 'price' },
65
+ isLimitReached() {
66
+ const lim = Number(this.limit)
67
+ return lim > 0 && this.localItems.length >= lim
68
+ }
69
+ },
70
+
71
+ props: {
72
+ value: {
73
+ type: Array,
74
+ default: () => []
75
+ },
76
+ title: { type: String, default: '' },
77
+ unit: { type: String, default: '元' },
78
+ precision: { type: Number, default: 2 },
79
+ // 左括号:默认 '[',传空字符串则不显示
80
+ leftBracket: { type: String, default: '[' },
81
+ // 右括号:默认 null,走内置逻辑(只有1条为 ']',多条为 ')');传具体值则强制使用
82
+ rightBracket: { type: String, default: null },
83
+ // 字段名映射:{ min, max, price },允许传入的 value 使用自定义字段名
84
+ fieldKeys: {
85
+ type: Object,
86
+ default: () => ({ min: 'min', max: 'max', price: 'price' })
87
+ },
88
+ // 阶梯数量上限;<= 0 表示不限制
89
+ limit: { type: Number, default: 0 },
90
+ disabled: { type: Boolean, default: false },
91
+ tip: {
92
+ type: String,
93
+ default: '区间左闭右闭 [min, max],最后一级为 [min, +∞),保证连续且不重叠。'
94
+ },
95
+ // 默认初始值:当传入空数组时,是否自动生成首条默认阶梯 [0, +∞)
96
+ defaultFirst: { type: Boolean, default: true }
97
+ },
98
+
99
+ data() {
100
+ return {
101
+ localItems: this.normalize(this.value)
102
+ }
103
+ },
104
+
105
+ watch: {
106
+ value: {
107
+ deep: true,
108
+ handler(val) {
109
+ this.localItems = this.normalize(val)
110
+ }
111
+ }
112
+ },
113
+
114
+ methods: {
115
+ // 统一数字转换:空值/非法值一律转为 fallback(默认 0)
116
+ safeNumber(v, fallback = 0) {
117
+ if (v === null || v === undefined || v === '' || v === Infinity || v === -Infinity) return fallback
118
+ const n = Number(v)
119
+ return isNaN(n) ? fallback : n
120
+ },
121
+
122
+ cloneItems(items) {
123
+ if (!Array.isArray(items)) return []
124
+ return items.map((it) => ({
125
+ [this.keyMin]: this.safeNumber(it && it[this.keyMin], 0),
126
+ [this.keyMax]: (it && it[this.keyMax] == null) || (it && it[this.keyMax] === '') ? null : this.safeNumber(it[this.keyMax], null),
127
+ [this.keyPrice]: this.safeNumber(it && it[this.keyPrice], 0)
128
+ }))
129
+ },
130
+
131
+ normalize(items) {
132
+ const list = this.cloneItems(items)
133
+ if (list.length === 0 && this.defaultFirst) {
134
+ list.push({ [this.keyMin]: 0, [this.keyMax]: null, [this.keyPrice]: 0 })
135
+ }
136
+ this.ensureContinuity(list)
137
+ return list
138
+ },
139
+
140
+ // 保证 items 连续且不重叠:items[i].max === items[i+1].min,首条 min === 0,末条 max === null
141
+ ensureContinuity(list) {
142
+ if (!Array.isArray(list) || list.length === 0) return
143
+ list[0][this.keyMin] = 0
144
+
145
+ for (let i = 0; i < list.length; i++) {
146
+ const cur = list[i]
147
+ const next = list[i + 1]
148
+ const curMin = this.safeNumber(cur[this.keyMin], 0)
149
+ if (next) {
150
+ let curMax = this.safeNumber(cur[this.keyMax], curMin + 1)
151
+ if (curMax <= curMin) curMax = curMin + 1
152
+ cur[this.keyMax] = curMax
153
+ next[this.keyMin] = curMax
154
+ } else {
155
+ cur[this.keyMax] = null
156
+ }
157
+ cur[this.keyPrice] = this.safeNumber(cur[this.keyPrice], 0)
158
+ }
159
+ },
160
+
161
+ emit() {
162
+ this.ensureContinuity(this.localItems)
163
+ const cloned = this.cloneItems(this.localItems)
164
+ this.$emit('input', cloned)
165
+ this.$emit('change', cloned)
166
+ },
167
+
168
+ onItemInput(val, idx) {
169
+ const cur = this.localItems[idx]
170
+ if (!cur) return
171
+ if (val && val[this.keyPrice] !== undefined) cur[this.keyPrice] = this.safeNumber(val[this.keyPrice], 0)
172
+ },
173
+
174
+ onMaxChange(val, idx) {
175
+ const cur = this.localItems[idx]
176
+ if (!cur) return
177
+ const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0) + 1)
178
+ cur[this.keyMax] = n
179
+ const next = this.localItems[idx + 1]
180
+ if (next) next[this.keyMin] = n
181
+ this.emit()
182
+ },
183
+
184
+ onMinChange(val, idx) {
185
+ const cur = this.localItems[idx]
186
+ if (!cur) return
187
+ if (idx === 0) {
188
+ cur[this.keyMin] = 0
189
+ } else {
190
+ const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0))
191
+ cur[this.keyMin] = n
192
+ const prev = this.localItems[idx - 1]
193
+ if (prev) prev[this.keyMax] = n
194
+ }
195
+ this.emit()
196
+ },
197
+
198
+ onAdd() {
199
+ debugger
200
+ const list = this.localItems
201
+ const lim = Number(this.limit)
202
+ if (lim > 0 && list.length >= lim) return
203
+
204
+ // 场景 1:空数组 —— 直接 push 一条 [0, +∞)
205
+ if (list.length === 0) {
206
+ list.push({
207
+ [this.keyMin]: 0,
208
+ [this.keyMax]: null,
209
+ [this.keyPrice]: 10
210
+ })
211
+ this.emit()
212
+ return
213
+ }
214
+
215
+ // 场景 2:已有数据 —— 在末条前插入新条
216
+ const last = list[list.length - 1]
217
+ const newMin = this.safeNumber(last[this.keyMin], 0)
218
+ const newMax = newMin + 1
219
+
220
+ // 新条 price:优先继承「倒数第二条」的 price,其次用末条 price(>0 时),否则默认 10
221
+ const prev = list[list.length - 2]
222
+ let newPrice = 10
223
+ if (prev) {
224
+ newPrice = this.safeNumber(prev[this.keyPrice], 10)
225
+ } else {
226
+ const lastPrice = this.safeNumber(last[this.keyPrice], 0)
227
+ newPrice = lastPrice > 0 ? lastPrice : 10
228
+ }
229
+
230
+ // 关键修复:构造全新数组,避免 splice 导致 Vue 组件复用 & 响应式不同步
231
+ // 新条 = { newMin, newMax, newPrice }
232
+ // 新末条 = { min: newMax, max: null, price: newPrice }
233
+ const newArr = [
234
+ ...list.slice(0, -1),
235
+ {
236
+ [this.keyMin]: newMin,
237
+ [this.keyMax]: newMax,
238
+ [this.keyPrice]: newPrice
239
+ },
240
+ {
241
+ [this.keyMin]: newMax,
242
+ [this.keyMax]: null,
243
+ [this.keyPrice]: newPrice
244
+ }
245
+ ]
246
+
247
+ this.localItems = newArr
248
+ this.emit()
249
+ },
250
+
251
+ onDelete(idx) {
252
+ const list = this.localItems
253
+ if (list.length <= 1) return // 至少保留一条
254
+ if (idx === 0) return // 首条不允许删除,保证区间起点始终为 0
255
+ const cur = list[idx]
256
+ const prev = list[idx - 1]
257
+ const next = list[idx + 1]
258
+ if (prev && next) {
259
+ prev[this.keyMax] = cur[this.keyMax] == null ? null : Number(cur[this.keyMax])
260
+ } else if (prev && !next) {
261
+ prev[this.keyMax] = null
262
+ } else if (!prev && next) {
263
+ next[this.keyMin] = 0
264
+ }
265
+ list.splice(idx, 1)
266
+ this.emit()
267
+ }
268
+ }
269
+ }
270
+ </script>
@@ -0,0 +1,115 @@
1
+ .xt-step-price {
2
+ padding: 12px 16px;
3
+ background-color: #ffffff;
4
+ border: 1px solid #ebeef5;
5
+ border-radius: 4px;
6
+
7
+ &__header {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: space-between;
11
+ margin-bottom: 12px;
12
+ }
13
+
14
+ &__list {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 8px;
18
+ }
19
+
20
+ &__empty {
21
+ padding: 24px;
22
+ text-align: center;
23
+ color: #909399;
24
+ font-size: 13px;
25
+ }
26
+
27
+ &__tip {
28
+ margin-top: 12px;
29
+ color: #909399;
30
+ font-size: 12px;
31
+ line-height: 1.6;
32
+ }
33
+
34
+ &.is-disabled {
35
+ background-color: #fafafa;
36
+ opacity: 0.85;
37
+ pointer-events: none;
38
+ }
39
+ }
40
+
41
+ .xt-step-price-item {
42
+ display: flex;
43
+ align-items: center;
44
+ padding: 8px 12px;
45
+ background-color: #f5f7fa;
46
+ border-radius: 4px;
47
+ transition: background-color 0.2s ease;
48
+
49
+ &:hover {
50
+ background-color: #ecf0f4;
51
+ }
52
+
53
+ &__range {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ flex: 0 0 auto;
57
+ color: #606266;
58
+ font-size: 13px;
59
+ }
60
+
61
+ &__bracket {
62
+ margin: 0 4px;
63
+ color: #909399;
64
+ font-weight: 600;
65
+ }
66
+
67
+ &__comma {
68
+ margin: 0 6px;
69
+ color: #909399;
70
+ }
71
+
72
+ &__infinity {
73
+ display: inline-block;
74
+ min-width: 60px;
75
+ text-align: center;
76
+ color: #c0c4cc;
77
+ font-size: 13px;
78
+ }
79
+
80
+ &__arrow {
81
+ margin: 0 12px;
82
+ color: #409eff;
83
+ font-weight: 600;
84
+ }
85
+
86
+ &__price {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ flex: 1;
90
+ }
91
+
92
+ &__unit {
93
+ margin-left: 8px;
94
+ color: #606266;
95
+ font-size: 13px;
96
+ }
97
+
98
+ &__input {
99
+ width: 80px;
100
+ text-align: center;
101
+
102
+ &--price {
103
+ width: 140px;
104
+ }
105
+ }
106
+
107
+ &__delete {
108
+ margin-left: 12px;
109
+ color: #f56c6c !important;
110
+ }
111
+
112
+ &__delete:hover {
113
+ color: #f78989 !important;
114
+ }
115
+ }
@@ -0,0 +1,2 @@
1
+ import XtStepPriceItem from './index.vue'
2
+ export default XtStepPriceItem