xt-element-ui 2.2.3 → 2.2.4
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/README.md +140 -192
- package/docs/README.md +162 -88
- package/docs/components/base/theme.md +248 -0
- package/docs/components/base/xt-list.md +1 -1
- package/docs/components/base/xt-scroll-arrow.md +87 -1
- package/docs/components/base/xt-scroll.md +237 -0
- package/docs/components/utils/config.md +18 -4
- package/docs/components/utils/theme.md +280 -183
- package/lib/index.common.1.js +4 -26
- package/lib/index.common.2.js +1 -1
- package/lib/index.common.js +1871 -671
- package/lib/index.css +1 -1
- package/lib/index.umd.1.js +4 -26
- package/lib/index.umd.2.js +1 -1
- package/lib/index.umd.js +1871 -671
- package/lib/index.umd.min.1.js +1 -1
- package/lib/index.umd.min.js +1 -1
- package/lib/theme/element-overrides.css +607 -0
- package/lib/theme/element-vars.css +280 -0
- package/lib/xt-badge/xt-badge.common.js +1 -1
- package/lib/xt-badge/xt-badge.umd.js +1 -1
- package/lib/xt-button/xt-button.common.js +1 -1
- package/lib/xt-button/xt-button.umd.js +1 -1
- package/lib/xt-card/xt-card.common.js +1 -1
- package/lib/xt-card/xt-card.umd.js +1 -1
- package/lib/xt-card-item/xt-card-item.common.js +1 -1
- package/lib/xt-card-item/xt-card-item.umd.js +1 -1
- package/lib/xt-chart/xt-chart.common.js +5 -5
- package/lib/xt-chart/xt-chart.umd.js +5 -5
- package/lib/xt-config-provider/xt-config-provider.css +1 -1
- package/lib/xt-date-picker/xt-date-picker.common.1.js +4 -26
- package/lib/xt-date-picker/xt-date-picker.common.2.js +1 -1
- package/lib/xt-date-picker/xt-date-picker.common.js +1 -47
- package/lib/xt-date-picker/xt-date-picker.css +1 -1
- package/lib/xt-date-picker/xt-date-picker.umd.1.js +4 -26
- package/lib/xt-date-picker/xt-date-picker.umd.2.js +1 -1
- package/lib/xt-date-picker/xt-date-picker.umd.js +1 -47
- package/lib/xt-date-picker/xt-date-picker.umd.min.1.js +1 -1
- package/lib/xt-date-picker/xt-date-picker.umd.min.js +1 -1
- package/lib/xt-flex-box/xt-flex-box.common.js +1 -1
- package/lib/xt-flex-box/xt-flex-box.umd.js +1 -1
- package/lib/xt-flow/xt-flow.common.js +1 -1
- package/lib/xt-flow/xt-flow.umd.js +1 -1
- package/lib/xt-form-schema/xt-form-schema.common.js +1 -1
- package/lib/xt-form-schema/xt-form-schema.umd.js +1 -1
- package/lib/xt-grid-box/xt-grid-box.common.js +1 -1
- package/lib/xt-grid-box/xt-grid-box.umd.js +1 -1
- package/lib/xt-grid-item/xt-grid-item.common.js +1 -1
- package/lib/xt-grid-item/xt-grid-item.umd.js +1 -1
- package/lib/xt-icon/xt-icon.common.js +1 -1
- package/lib/xt-icon/xt-icon.umd.js +1 -1
- package/lib/xt-input/xt-input.common.js +1 -1
- package/lib/xt-input/xt-input.umd.js +1 -1
- package/lib/xt-list/xt-list.common.js +457 -118
- package/lib/xt-list/xt-list.css +1 -1
- package/lib/xt-list/xt-list.umd.js +457 -118
- package/lib/xt-list/xt-list.umd.min.js +1 -1
- package/lib/xt-map/xt-map.common.js +1 -1
- package/lib/xt-map/xt-map.umd.js +1 -1
- package/lib/xt-map-provider/xt-map-provider.common.js +1 -1
- package/lib/xt-map-provider/xt-map-provider.umd.js +1 -1
- package/lib/xt-page/xt-page.common.js +1 -1
- package/lib/xt-page/xt-page.umd.js +1 -1
- package/lib/xt-progress/xt-progress.common.js +1 -1
- package/lib/xt-progress/xt-progress.umd.js +1 -1
- package/lib/xt-scroll/demo.html +10 -0
- package/lib/xt-scroll/index.js +1 -0
- package/lib/xt-scroll/xt-scroll.common.js +551 -0
- package/lib/xt-scroll/xt-scroll.css +1 -0
- package/lib/xt-scroll/xt-scroll.umd.js +561 -0
- package/lib/xt-scroll/xt-scroll.umd.min.js +1 -0
- package/lib/xt-scroll-arrow/xt-scroll-arrow.common.js +591 -170
- package/lib/xt-scroll-arrow/xt-scroll-arrow.css +1 -1
- package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.js +591 -170
- package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.min.js +1 -1
- package/lib/xt-select-tree/xt-select-tree.common.js +1 -1
- package/lib/xt-select-tree/xt-select-tree.umd.js +1 -1
- package/lib/xt-step-price/xt-step-price.common.js +2 -2
- package/lib/xt-step-price/xt-step-price.umd.js +2 -2
- package/lib/xt-step-price-item/xt-step-price-item.common.js +1 -1
- package/lib/xt-step-price-item/xt-step-price-item.umd.js +1 -1
- package/lib/xt-tab-pane/xt-tab-pane.common.js +1 -1
- package/lib/xt-tab-pane/xt-tab-pane.umd.js +1 -1
- package/lib/xt-table/xt-table.common.js +2 -2
- package/lib/xt-table/xt-table.umd.js +2 -2
- package/lib/xt-tabs/xt-tabs.common.js +1 -1
- package/lib/xt-tabs/xt-tabs.umd.js +1 -1
- package/lib/xt-text/xt-text.common.js +1 -1
- package/lib/xt-text/xt-text.umd.js +1 -1
- package/lib/xt-time/xt-time.common.js +1 -1
- package/lib/xt-time/xt-time.umd.js +1 -1
- package/lib/xt-transfer-tree/xt-transfer-tree.common.js +1 -1
- package/lib/xt-transfer-tree/xt-transfer-tree.umd.js +1 -1
- package/lib/xt-upload/xt-upload.common.js +2 -2
- package/lib/xt-upload/xt-upload.umd.js +2 -2
- package/package.json +5 -3
- package/src/components/index.scss +3 -0
- package/src/components/theme/element-overrides.css +607 -0
- package/src/components/theme/element-vars.css +280 -0
- package/src/components/theme/iframe-injector.js +502 -0
- package/src/components/theme/index.js +64 -0
- package/src/components/xt-config-provider/style/index.scss +0 -11
- package/src/components/xt-date-picker/component/RangeDate.vue +1 -29
- package/src/components/xt-date-picker/style/index.scss +80 -53
- package/src/components/xt-list/index.vue +23 -12
- package/src/components/xt-scroll/index.js +9 -0
- package/src/components/xt-scroll/index.vue +363 -0
- package/src/components/xt-scroll/style/index.scss +107 -0
- package/src/components/xt-scroll-arrow/index.vue +200 -83
- package/src/components/xt-scroll-arrow/style/index.scss +16 -5
- package/src/index.js +9 -2
- package/src/utils/color.js +105 -0
- package/src/utils/index.js +456 -468
- package/src/utils/theme-vars.js +134 -0
- package/lib/css/1.87a493be.css +0 -1
- package/lib/xt-date-picker/css/1.87a493be.css +0 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* XT-Element-UI iframe 主题注入工具
|
|
3
|
+
*
|
|
4
|
+
* 使用场景:微前端架构中,主系统统一管理主题,子系统通过 iframe 嵌入。
|
|
5
|
+
* 子系统为标准 Element UI 项目,无需任何改造。
|
|
6
|
+
*
|
|
7
|
+
* 设计:
|
|
8
|
+
* - 主题状态的唯一事实源是 utils/index.js(setTheme / setPrimaryColor)
|
|
9
|
+
* - 本模块仅负责 iframe DOM 操作(注入、MutationObserver、持久化、广播)
|
|
10
|
+
* - 通过订阅 utils.onConfigChange 实现主文档和 iframe 的同步
|
|
11
|
+
* - setTheme / setPrimaryColor 委托给 utils,保持外部 API 不变
|
|
12
|
+
*
|
|
13
|
+
* 工作流程:
|
|
14
|
+
* 1. 主系统调用 utils.setTheme('dark') → utils 同步更新 --xt-* + --el-* 变量
|
|
15
|
+
* 2. utils emitConfigChange → 所有 IframeThemeInjector 实例收到通知
|
|
16
|
+
* 3. 实例将新主题应用到其管理的所有 iframe
|
|
17
|
+
* 4. 持久化、广播事件、回调
|
|
18
|
+
* ============================================================ */
|
|
19
|
+
|
|
20
|
+
import { setTheme as _setTheme, setPrimaryColor as _setPrimaryColor, getConfig as _getConfig, onConfigChange as _onConfigChange } from '../../utils/index'
|
|
21
|
+
import { applyThemeVars, BRAND_COLORS, THEME_STORAGE_KEY, THEME_CHANGE_EVENT } from '../../utils/theme-vars'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* IframeThemeInjector 类
|
|
25
|
+
* 负责向 iframe 注入主题 CSS 并管理 iframe 侧主题状态
|
|
26
|
+
*
|
|
27
|
+
* 注意:主文档的 --el-* / --xt-* CSS 变量由 utils/index.js 管理,
|
|
28
|
+
* 本类仅负责 iframe 内的同步,避免重复设置主文档变量。
|
|
29
|
+
*/
|
|
30
|
+
class IframeThemeInjector {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Object} options
|
|
33
|
+
* @param {string} options.baseCSS - 基础 CSS 内容(注入到 iframe 的 <head>)
|
|
34
|
+
* @param {string} [options.theme='white'] - 初始主题 'white' | 'dark'
|
|
35
|
+
* @param {string} [options.primaryColor='#1890ff'] - 初始主色调
|
|
36
|
+
* @param {string} [options.brand=''] - 品牌标识 '' | 'water' | 'electricity' | 'gas'
|
|
37
|
+
* @param {boolean} [options.persist=false] - 是否持久化主题配置到 localStorage
|
|
38
|
+
* @param {Function} [options.onThemeChange] - 主题变化回调
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
if (!options.baseCSS) {
|
|
42
|
+
console.warn('[IframeThemeInjector] baseCSS 未提供,主题样式将无法注入到 iframe')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.baseCSS = options.baseCSS || ''
|
|
46
|
+
this.theme = options.theme || 'white'
|
|
47
|
+
this.brand = options.brand || ''
|
|
48
|
+
this.persist = options.persist || false
|
|
49
|
+
this.onThemeChange = options.onThemeChange || null
|
|
50
|
+
|
|
51
|
+
// 主色调:如果有 brand,优先使用 brand 对应的颜色
|
|
52
|
+
this.primaryColor = options.primaryColor || this._getBrandColor()
|
|
53
|
+
this._userPrimaryColor = options.primaryColor || '#1890ff'
|
|
54
|
+
|
|
55
|
+
// 管理的 iframe 列表
|
|
56
|
+
// Map<HTMLIFrameElement, { styleEl: HTMLStyleElement, injected: boolean }>
|
|
57
|
+
this.iframes = new Map()
|
|
58
|
+
|
|
59
|
+
// MutationObserver 用于自动检测新增 iframe
|
|
60
|
+
this._observer = null
|
|
61
|
+
this._observerTarget = null
|
|
62
|
+
|
|
63
|
+
// 注入计数器(用于去重定时器)
|
|
64
|
+
this._injectPending = new Set()
|
|
65
|
+
|
|
66
|
+
// 订阅 utils 配置变更,实现主文档和 iframe 的主题同步
|
|
67
|
+
this._unsubscribe = _onConfigChange((key, value) => {
|
|
68
|
+
this._onUtilsConfigChange(key, value)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ==================== 公共方法 ====================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 向指定 iframe 注入主题 CSS
|
|
76
|
+
* @param {HTMLIFrameElement} iframe - iframe 元素
|
|
77
|
+
* @returns {boolean} 是否注入成功
|
|
78
|
+
*/
|
|
79
|
+
inject(iframe) {
|
|
80
|
+
if (!iframe) {
|
|
81
|
+
console.warn('[IframeThemeInjector] inject: iframe 参数为空')
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 如果已注入,跳过
|
|
86
|
+
if (this.iframes.has(iframe)) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return this._doInject(iframe)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 批量注入:注入所有匹配选择器的 iframe
|
|
95
|
+
* @param {string} [selector='iframe'] - CSS 选择器
|
|
96
|
+
* @returns {number} 成功注入的数量
|
|
97
|
+
*/
|
|
98
|
+
injectAll(selector = 'iframe') {
|
|
99
|
+
const iframes = document.querySelectorAll(selector)
|
|
100
|
+
let count = 0
|
|
101
|
+
iframes.forEach(iframe => {
|
|
102
|
+
if (this.inject(iframe)) count++
|
|
103
|
+
})
|
|
104
|
+
return count
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 切换主题
|
|
109
|
+
* 委托给 utils 处理主文档 CSS 变量,本类负责 iframe 同步
|
|
110
|
+
* @param {string} theme - 'white' | 'dark'
|
|
111
|
+
* @param {Object} [options]
|
|
112
|
+
* @param {string} [options.primaryColor] - 主色调
|
|
113
|
+
* @param {string} [options.brand] - 品牌色
|
|
114
|
+
*/
|
|
115
|
+
setTheme(theme, options = {}) {
|
|
116
|
+
if (theme !== 'white' && theme !== 'dark') {
|
|
117
|
+
console.warn('[IframeThemeInjector] setTheme: 无效的主题值,使用 "white"', theme)
|
|
118
|
+
theme = 'white'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const prevTheme = this.theme
|
|
122
|
+
this.theme = theme
|
|
123
|
+
|
|
124
|
+
if (options.primaryColor) {
|
|
125
|
+
this.primaryColor = options.primaryColor
|
|
126
|
+
this._userPrimaryColor = options.primaryColor
|
|
127
|
+
} else if (options.brand || this.brand) {
|
|
128
|
+
this.brand = options.brand || this.brand
|
|
129
|
+
this.primaryColor = this._getBrandColor()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.brand !== undefined) {
|
|
133
|
+
this.brand = options.brand
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 委托给 utils 处理主文档的 CSS 变量设置
|
|
137
|
+
_setTheme(theme)
|
|
138
|
+
if (this.primaryColor) {
|
|
139
|
+
_setPrimaryColor(this.primaryColor)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 同步所有 iframe
|
|
143
|
+
this.iframes.forEach((_, iframeEl) => {
|
|
144
|
+
try {
|
|
145
|
+
if (iframeEl.contentDocument) {
|
|
146
|
+
this._applyThemeToDocument(iframeEl.contentDocument)
|
|
147
|
+
}
|
|
148
|
+
} catch (e) { /* 跨域 iframe 无法访问 */ }
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// 持久化
|
|
152
|
+
if (this.persist) {
|
|
153
|
+
this._saveToStorage()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 广播事件
|
|
157
|
+
this._broadcastChange()
|
|
158
|
+
|
|
159
|
+
// 回调
|
|
160
|
+
if (this.onThemeChange) {
|
|
161
|
+
this.onThemeChange({
|
|
162
|
+
theme: this.theme,
|
|
163
|
+
primaryColor: this.primaryColor,
|
|
164
|
+
brand: this.brand,
|
|
165
|
+
prevTheme
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 设置主色调
|
|
172
|
+
* @param {string} color - hex 颜色值,如 '#1890ff'
|
|
173
|
+
*/
|
|
174
|
+
setPrimaryColor(color) {
|
|
175
|
+
this.primaryColor = color
|
|
176
|
+
this._userPrimaryColor = color
|
|
177
|
+
this.brand = '' // 清除品牌色
|
|
178
|
+
|
|
179
|
+
// 委托给 utils 处理主文档
|
|
180
|
+
_setPrimaryColor(color)
|
|
181
|
+
|
|
182
|
+
// 同步 iframe
|
|
183
|
+
this._applyPrimaryColorToAll()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 设置品牌色
|
|
188
|
+
* @param {string} brand - '' | 'water' | 'electricity' | 'gas'
|
|
189
|
+
*/
|
|
190
|
+
setBrand(brand) {
|
|
191
|
+
this.brand = brand
|
|
192
|
+
this.primaryColor = this._getBrandColor()
|
|
193
|
+
|
|
194
|
+
// 委托给 utils 处理主文档
|
|
195
|
+
_setPrimaryColor(this.primaryColor)
|
|
196
|
+
|
|
197
|
+
// 同步 iframe
|
|
198
|
+
this._applyPrimaryColorToAll()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 移除指定 iframe 的注入
|
|
203
|
+
* @param {HTMLIFrameElement} iframe
|
|
204
|
+
*/
|
|
205
|
+
remove(iframe) {
|
|
206
|
+
const info = this.iframes.get(iframe)
|
|
207
|
+
if (info && info.styleEl && info.styleEl.parentNode) {
|
|
208
|
+
info.styleEl.parentNode.removeChild(info.styleEl)
|
|
209
|
+
}
|
|
210
|
+
this.iframes.delete(iframe)
|
|
211
|
+
this._injectPending.delete(iframe)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 移除所有注入
|
|
216
|
+
*/
|
|
217
|
+
removeAll() {
|
|
218
|
+
this.iframes.forEach((info, iframe) => {
|
|
219
|
+
if (info.styleEl && info.styleEl.parentNode) {
|
|
220
|
+
info.styleEl.parentNode.removeChild(info.styleEl)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
this.iframes.clear()
|
|
224
|
+
this._injectPending.clear()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 开始自动监听 DOM 变化,自动注入新添加的 iframe
|
|
229
|
+
* @param {HTMLElement} [target=document.body] - 监听的容器元素
|
|
230
|
+
*/
|
|
231
|
+
startObserving(target) {
|
|
232
|
+
this.stopObserving()
|
|
233
|
+
|
|
234
|
+
this._observerTarget = target || document.body
|
|
235
|
+
this._observer = new MutationObserver((mutations) => {
|
|
236
|
+
mutations.forEach(mutation => {
|
|
237
|
+
mutation.addedNodes.forEach(node => {
|
|
238
|
+
// 直接添加的 iframe
|
|
239
|
+
if (node.tagName === 'IFRAME') {
|
|
240
|
+
this._handleNewIframe(node)
|
|
241
|
+
}
|
|
242
|
+
// 包含 iframe 的子元素
|
|
243
|
+
if (node.querySelectorAll) {
|
|
244
|
+
node.querySelectorAll('iframe').forEach(iframe => {
|
|
245
|
+
this._handleNewIframe(iframe)
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
this._observer.observe(this._observerTarget, {
|
|
253
|
+
childList: true,
|
|
254
|
+
subtree: true
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 停止自动监听
|
|
260
|
+
*/
|
|
261
|
+
stopObserving() {
|
|
262
|
+
if (this._observer) {
|
|
263
|
+
this._observer.disconnect()
|
|
264
|
+
this._observer = null
|
|
265
|
+
}
|
|
266
|
+
this._observerTarget = null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 获取当前主题配置
|
|
271
|
+
* @returns {Object}
|
|
272
|
+
*/
|
|
273
|
+
getConfig() {
|
|
274
|
+
return {
|
|
275
|
+
theme: this.theme,
|
|
276
|
+
primaryColor: this.primaryColor,
|
|
277
|
+
brand: this.brand
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 获取已注入的 iframe 数量
|
|
283
|
+
* @returns {number}
|
|
284
|
+
*/
|
|
285
|
+
getIframeCount() {
|
|
286
|
+
return this.iframes.size
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 销毁实例,清理所有资源
|
|
291
|
+
*/
|
|
292
|
+
destroy() {
|
|
293
|
+
this.removeAll()
|
|
294
|
+
this.stopObserving()
|
|
295
|
+
if (this._unsubscribe) {
|
|
296
|
+
this._unsubscribe()
|
|
297
|
+
this._unsubscribe = null
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ==================== utils 变更响应 ====================
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 响应 utils 配置变更,同步到所有 iframe
|
|
305
|
+
* 统一使用 applyThemeVars 处理主题+主色变量
|
|
306
|
+
*/
|
|
307
|
+
_onUtilsConfigChange(key, value) {
|
|
308
|
+
if (key === 'theme') {
|
|
309
|
+
this.theme = value
|
|
310
|
+
} else if (key === 'primaryColor') {
|
|
311
|
+
this.primaryColor = value
|
|
312
|
+
} else {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
this._applyPrimaryColorToAll()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ==================== 私有方法 ====================
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 执行实际的注入操作
|
|
322
|
+
*/
|
|
323
|
+
_doInject(iframe) {
|
|
324
|
+
try {
|
|
325
|
+
const doc = iframe.contentDocument || iframe.contentWindow.document
|
|
326
|
+
if (!doc || !doc.head) {
|
|
327
|
+
// iframe 尚未加载完成,等待 load 事件
|
|
328
|
+
iframe.addEventListener('load', () => this._doInject(iframe), { once: true })
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 检查是否已注入过(防止重复)
|
|
333
|
+
const existing = doc.head.querySelector('style[data-xt-theme-injector]')
|
|
334
|
+
if (existing) {
|
|
335
|
+
this.iframes.set(iframe, { styleEl: existing, injected: true })
|
|
336
|
+
this._applyThemeToDocument(doc)
|
|
337
|
+
return true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 创建 style 元素并注入
|
|
341
|
+
const styleEl = doc.createElement('style')
|
|
342
|
+
styleEl.setAttribute('data-xt-theme-injector', '')
|
|
343
|
+
styleEl.setAttribute('type', 'text/css')
|
|
344
|
+
styleEl.textContent = this.baseCSS
|
|
345
|
+
doc.head.appendChild(styleEl)
|
|
346
|
+
|
|
347
|
+
// 应用主题变量
|
|
348
|
+
this._applyThemeToDocument(doc)
|
|
349
|
+
|
|
350
|
+
// 记录
|
|
351
|
+
this.iframes.set(iframe, { styleEl, injected: true })
|
|
352
|
+
|
|
353
|
+
return true
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.warn('[IframeThemeInjector] 无法注入主题到 iframe(可能是跨域限制):', e.message)
|
|
356
|
+
return false
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 处理新检测到的 iframe
|
|
362
|
+
*/
|
|
363
|
+
_handleNewIframe(iframe) {
|
|
364
|
+
if (this.iframes.has(iframe) || this._injectPending.has(iframe)) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this._injectPending.add(iframe)
|
|
369
|
+
|
|
370
|
+
const tryInject = () => {
|
|
371
|
+
this._injectPending.delete(iframe)
|
|
372
|
+
if (!this.iframes.has(iframe)) {
|
|
373
|
+
this._doInject(iframe)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
|
|
378
|
+
tryInject()
|
|
379
|
+
} else {
|
|
380
|
+
iframe.addEventListener('load', tryInject, { once: true })
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 将主题配置应用到指定文档(iframe 内)
|
|
386
|
+
* 统一使用 applyThemeVars 处理 CSS 变量
|
|
387
|
+
*/
|
|
388
|
+
_applyThemeToDocument(doc) {
|
|
389
|
+
if (!doc || !doc.documentElement) return
|
|
390
|
+
|
|
391
|
+
applyThemeVars(doc.documentElement, {
|
|
392
|
+
theme: this.theme,
|
|
393
|
+
primaryColor: this.primaryColor,
|
|
394
|
+
brand: this.brand
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* 更新所有 iframe 文档的主色调
|
|
400
|
+
* 统一使用 applyThemeVars 处理
|
|
401
|
+
*/
|
|
402
|
+
_applyPrimaryColorToAll() {
|
|
403
|
+
this.iframes.forEach((_, iframeEl) => {
|
|
404
|
+
try {
|
|
405
|
+
if (iframeEl.contentDocument) {
|
|
406
|
+
applyThemeVars(iframeEl.contentDocument.documentElement, {
|
|
407
|
+
theme: this.theme,
|
|
408
|
+
primaryColor: this.primaryColor,
|
|
409
|
+
brand: this.brand
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
} catch (e) { /* 跨域 */ }
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 获取品牌色对应的主色调
|
|
418
|
+
*/
|
|
419
|
+
_getBrandColor() {
|
|
420
|
+
if (!this.brand || !BRAND_COLORS[this.brand]) {
|
|
421
|
+
return this._userPrimaryColor || '#1890ff'
|
|
422
|
+
}
|
|
423
|
+
const colors = BRAND_COLORS[this.brand]
|
|
424
|
+
return this.theme === 'dark' ? colors.dark : colors.light
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 持久化到 localStorage
|
|
429
|
+
*/
|
|
430
|
+
_saveToStorage() {
|
|
431
|
+
if (typeof localStorage === 'undefined') return
|
|
432
|
+
try {
|
|
433
|
+
localStorage.setItem(THEME_STORAGE_KEY, JSON.stringify({
|
|
434
|
+
theme: this.theme,
|
|
435
|
+
primaryColor: this._userPrimaryColor,
|
|
436
|
+
brand: this.brand,
|
|
437
|
+
timestamp: Date.now()
|
|
438
|
+
}))
|
|
439
|
+
} catch (e) {
|
|
440
|
+
console.warn('[IframeThemeInjector] 保存主题配置失败:', e.message)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 从 localStorage 恢复配置
|
|
446
|
+
*/
|
|
447
|
+
_loadFromStorage() {
|
|
448
|
+
if (typeof localStorage === 'undefined') return null
|
|
449
|
+
try {
|
|
450
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY)
|
|
451
|
+
return stored ? JSON.parse(stored) : null
|
|
452
|
+
} catch (e) {
|
|
453
|
+
return null
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 广播主题变化事件
|
|
459
|
+
*/
|
|
460
|
+
_broadcastChange() {
|
|
461
|
+
if (typeof window === 'undefined') return
|
|
462
|
+
try {
|
|
463
|
+
window.dispatchEvent(new CustomEvent(THEME_CHANGE_EVENT, {
|
|
464
|
+
detail: {
|
|
465
|
+
theme: this.theme,
|
|
466
|
+
primaryColor: this.primaryColor,
|
|
467
|
+
brand: this.brand,
|
|
468
|
+
timestamp: Date.now()
|
|
469
|
+
},
|
|
470
|
+
bubbles: true,
|
|
471
|
+
composed: true
|
|
472
|
+
}))
|
|
473
|
+
} catch (e) {
|
|
474
|
+
// 忽略
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* 创建 IframeThemeInjector 实例并自动从 localStorage 恢复配置
|
|
481
|
+
* @param {Object} options
|
|
482
|
+
* @returns {IframeThemeInjector}
|
|
483
|
+
*/
|
|
484
|
+
function createInjector(options = {}) {
|
|
485
|
+
const injector = new IframeThemeInjector(options)
|
|
486
|
+
|
|
487
|
+
// 如果启用持久化,尝试从 localStorage 恢复
|
|
488
|
+
if (options.persist) {
|
|
489
|
+
const stored = injector._loadFromStorage()
|
|
490
|
+
if (stored) {
|
|
491
|
+
injector.theme = stored.theme || 'white'
|
|
492
|
+
injector.brand = stored.brand || ''
|
|
493
|
+
injector._userPrimaryColor = stored.primaryColor || '#1890ff'
|
|
494
|
+
injector.primaryColor = stored.primaryColor || injector._getBrandColor()
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return injector
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export default IframeThemeInjector
|
|
502
|
+
export { createInjector, IframeThemeInjector }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* XT-Element-UI Theme 模块入口
|
|
3
|
+
*
|
|
4
|
+
* 提供:
|
|
5
|
+
* 1. 静态 CSS 样式文件(用于注入到 iframe)
|
|
6
|
+
* 2. IframeThemeInjector - iframe 主题注入工具
|
|
7
|
+
* 3. 便捷的工厂函数
|
|
8
|
+
* ============================================================ */
|
|
9
|
+
|
|
10
|
+
import IframeThemeInjector, { createInjector } from './iframe-injector'
|
|
11
|
+
|
|
12
|
+
// 使用 raw-loader 以原始字符串形式导入 CSS 文件
|
|
13
|
+
// eslint-disable-next-line
|
|
14
|
+
const elementVarsCSS = require('!!raw-loader!./element-vars.css').default || require('!!raw-loader!./element-vars.css')
|
|
15
|
+
// eslint-disable-next-line
|
|
16
|
+
const elementOverridesCSS = require('!!raw-loader!./element-overrides.css').default || require('!!raw-loader!./element-overrides.css')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 合并后的完整 CSS 内容
|
|
20
|
+
* 可直接注入到 iframe 的 <head> 中
|
|
21
|
+
*/
|
|
22
|
+
const fullThemeCSS = elementVarsCSS + '\n' + elementOverridesCSS
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 创建一个预配置好 CSS 内容的 IframeThemeInjector 实例
|
|
26
|
+
* 这是推荐的使用方式,无需手动提供 CSS 内容
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} options
|
|
29
|
+
* @param {string} [options.theme='white'] - 初始主题 'white' | 'dark'
|
|
30
|
+
* @param {string} [options.primaryColor='#1890ff'] - 初始主色调
|
|
31
|
+
* @param {boolean} [options.persist=false] - 是否持久化主题配置
|
|
32
|
+
* @param {Function} [options.onThemeChange] - 主题变化回调
|
|
33
|
+
* @returns {IframeThemeInjector}
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // 基本使用
|
|
37
|
+
* const injector = createThemeInjector({ theme: 'white' })
|
|
38
|
+
* injector.injectAll('iframe')
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // 切换主题
|
|
42
|
+
* injector.setTheme('dark')
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // 自动监听新增 iframe
|
|
46
|
+
* injector.startObserving()
|
|
47
|
+
*/
|
|
48
|
+
function createThemeInjector(options = {}) {
|
|
49
|
+
return createInjector({
|
|
50
|
+
...options,
|
|
51
|
+
baseCSS: fullThemeCSS
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
IframeThemeInjector,
|
|
57
|
+
createInjector,
|
|
58
|
+
createThemeInjector,
|
|
59
|
+
elementVarsCSS,
|
|
60
|
+
elementOverridesCSS,
|
|
61
|
+
fullThemeCSS
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default createThemeInjector
|
|
@@ -1,12 +1 @@
|
|
|
1
1
|
@import '../../../styles/variables.scss';
|
|
2
|
-
|
|
3
|
-
.xt-config-provider {
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.xt-config-provider--dark {
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.xt-config-provider--dark {
|
|
10
|
-
background-color: var(--xt-color-bg-primary, #0b141d);
|
|
11
|
-
color: var(--xt-color-text-primary, rgba(255, 255, 255, 0.95));
|
|
12
|
-
}
|
|
@@ -105,32 +105,4 @@
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
</script>
|
|
109
|
-
<style scoped>
|
|
110
|
-
.range-picker {
|
|
111
|
-
display: flex;
|
|
112
|
-
align-items: center;
|
|
113
|
-
flex-wrap: wrap;
|
|
114
|
-
gap: 8px;
|
|
115
|
-
width: 100%;
|
|
116
|
-
}
|
|
117
|
-
.range-picker .el-date-editor {
|
|
118
|
-
flex: 1;
|
|
119
|
-
min-width: 130px;
|
|
120
|
-
}
|
|
121
|
-
.split {
|
|
122
|
-
flex-shrink: 0;
|
|
123
|
-
padding: 0 4px;
|
|
124
|
-
}
|
|
125
|
-
/* 手机/窄侧边栏自动竖向排列 */
|
|
126
|
-
@media (max-width: 360px) {
|
|
127
|
-
.range-picker {
|
|
128
|
-
flex-direction: column;
|
|
129
|
-
align-items: stretch;
|
|
130
|
-
}
|
|
131
|
-
.split {
|
|
132
|
-
text-align: center;
|
|
133
|
-
margin: 4px 0;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
</style>
|
|
108
|
+
</script>
|