@blueking/status-tag-web-component 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.
- package/LICENSE +21 -0
- package/README.md +582 -0
- package/dist/loading-color.svg +1 -0
- package/dist/status-tag.mjs +320 -0
- package/dist/status-tag.umd.js +1 -0
- package/package.json +48 -0
- package/public/loading-color.svg +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Status Tag Web Component Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# Status Tag Web Component
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**一个无框架依赖的状态标签 Web Component,支持国际化,适用于任何前端项目**
|
|
8
|
+
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ✨ 特性
|
|
18
|
+
|
|
19
|
+
- ✅ **无框架依赖** - 原生 Web Component,可在任何项目中使用
|
|
20
|
+
- ✅ **100% 还原设计** - 严格按照原始 SCSS 实现,包括 22px 高度、6px 圆点、12px SVG 图标等
|
|
21
|
+
- ✅ **样式隔离** - 使用 Shadow DOM,避免样式冲突
|
|
22
|
+
- ✅ **国际化支持** - 内置中英文,可扩展其他语言
|
|
23
|
+
- ✅ **智能匹配** - 支持大小写不敏感的状态匹配
|
|
24
|
+
- ✅ **自定义配置** - 支持自定义状态映射
|
|
25
|
+
- ✅ **轻量级** - 压缩后约 12KB(gzip: 3.1KB)
|
|
26
|
+
- ✅ **SVG 内联** - 无需外部图片依赖
|
|
27
|
+
- ✅ **TypeScript** - 完整的类型支持
|
|
28
|
+
|
|
29
|
+
## 🚀 快速开始
|
|
30
|
+
|
|
31
|
+
### NPM 安装
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install status-tag-web-component
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### CDN 引入(开发测试)
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<!-- 本地文件方式(开发测试) -->
|
|
41
|
+
<script src="./dist/status-tag.umd.js"></script>
|
|
42
|
+
|
|
43
|
+
<!-- 或使用本地 ES Module -->
|
|
44
|
+
<script type="module">
|
|
45
|
+
import './dist/status-tag.mjs';
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **注意**: 正式的 CDN 链接将在发布到 npm 后提供。可使用本地文件或私有部署的方式。
|
|
50
|
+
|
|
51
|
+
### 基础使用
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<!DOCTYPE html>
|
|
55
|
+
<html lang="zh-CN">
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="UTF-8">
|
|
58
|
+
<title>Status Tag 示例</title>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
|
62
|
+
<status-tag status="loading"></status-tag>
|
|
63
|
+
<status-tag status="running"></status-tag>
|
|
64
|
+
<status-tag status="stop"></status-tag>
|
|
65
|
+
<status-tag status="warning"></status-tag>
|
|
66
|
+
<status-tag status="unknown"></status-tag>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<script src="./dist/status-tag.umd.js"></script>
|
|
70
|
+
</body>
|
|
71
|
+
</html>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### 国际化自动检测
|
|
75
|
+
|
|
76
|
+
组件会自动检测语言设置,优先级如下:
|
|
77
|
+
|
|
78
|
+
1. **手动设置 `locale` 属性**(最高优先级)
|
|
79
|
+
```html
|
|
80
|
+
<status-tag status="running" locale="en-US"></status-tag>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
2. **Cookie `blueking_language`**(自动检测)
|
|
84
|
+
- `blueking_language=en` → 英文显示
|
|
85
|
+
- 其他值或未设置 → 中文显示(默认)
|
|
86
|
+
```html
|
|
87
|
+
<!-- 无需设置 locale 属性,会自动从 Cookie 获取 -->
|
|
88
|
+
<status-tag status="running"></status-tag>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
3. **默认值**:`zh-CN`(中文)
|
|
92
|
+
|
|
93
|
+
这使得组件可以无缝集成到现有的国际化系统中。
|
|
94
|
+
|
|
95
|
+
## 📚 详细文档
|
|
96
|
+
|
|
97
|
+
### API 文档
|
|
98
|
+
|
|
99
|
+
#### 属性
|
|
100
|
+
|
|
101
|
+
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|
|
102
|
+
|--------|------|------|--------|------|
|
|
103
|
+
| `status` | string | ✅ | - | 状态值(如:'running', 'stop', 'warning' 等) |
|
|
104
|
+
| `locale` | string | ❌ | 'zh-CN' | 语言设置,支持 'zh-CN' 和 'en-US' |
|
|
105
|
+
| `status-map` | string (JSON) | ❌ | - | 自定义状态映射配置(JSON 字符串) |
|
|
106
|
+
|
|
107
|
+
#### 支持的默认状态
|
|
108
|
+
|
|
109
|
+
| 状态 | 中文文本 | 英文文本 | 主题 |
|
|
110
|
+
|------|----------|----------|------|
|
|
111
|
+
| `loading` | 加载中 | Loading | 蓝色 |
|
|
112
|
+
| `running` | 运行中 | Running | 绿色 |
|
|
113
|
+
| `stop` | 已停止 | Stopped | 灰色 |
|
|
114
|
+
| `warning` | 警告 | Warning | 橙色 |
|
|
115
|
+
| `unknown` | 未知 | Unknown | 黄橙色 |
|
|
116
|
+
|
|
117
|
+
### 设计规范
|
|
118
|
+
|
|
119
|
+
#### 视觉规格
|
|
120
|
+
|
|
121
|
+
- **容器高度**: 22px
|
|
122
|
+
- **内边距**: 0 8px
|
|
123
|
+
- **圆角**: 13px
|
|
124
|
+
- **字体**: 12px / 700 (bold)
|
|
125
|
+
- **Loading 图标**: 12×12px SVG 背景图,带旋转动画
|
|
126
|
+
- **Dot 图标**: 6×6px,带光晕效果(::before 伪元素,透明度 0.2)
|
|
127
|
+
|
|
128
|
+
#### 配色方案
|
|
129
|
+
|
|
130
|
+
| 状态 | 背景色 | 文字色 | 边框 | 图标 |
|
|
131
|
+
|------|--------|--------|------|------|
|
|
132
|
+
| loading | #EDF4FF | #699DF4 | 1px solid #CDDFFE | 12×12 SVG 图标 |
|
|
133
|
+
| running | #EBFAEF | #299E56 | 1px solid #CBF0DA | #E5F6EA + 1px solid #3FC06D |
|
|
134
|
+
| stop | #F5F7FA | #979BA5 | 1px solid #EAEBF0 | #F0F1F5 + 1px solid #C4C6CC |
|
|
135
|
+
| warning | #FDF4E9 | #F59500 | 1px solid #FCE5C0 | #FCE5C0 + 1px solid #F59500 |
|
|
136
|
+
| unknown | #fff3e8 | #ff9c01 | 无边框 | #ff9c01 |
|
|
137
|
+
|
|
138
|
+
### 智能匹配
|
|
139
|
+
|
|
140
|
+
组件会自动处理不同的大小写格式:
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<!-- 这些写法都会被识别为 'running' 状态 -->
|
|
144
|
+
<status-tag status="running"></status-tag>
|
|
145
|
+
<status-tag status="RUNNING"></status-tag>
|
|
146
|
+
<status-tag status="Running"></status-tag>
|
|
147
|
+
|
|
148
|
+
<!-- 未匹配时默认显示 'unknown' 状态 -->
|
|
149
|
+
<status-tag status="invalid_status"></status-tag>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 高级用法
|
|
153
|
+
|
|
154
|
+
#### 1. 自定义状态映射
|
|
155
|
+
|
|
156
|
+
您可以完全自定义状态文本和主题:
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<!-- 自定义审批流程状态 -->
|
|
160
|
+
<status-tag
|
|
161
|
+
status="pending"
|
|
162
|
+
status-map='{
|
|
163
|
+
"pending": {"text": "待审批", "theme": "warning"},
|
|
164
|
+
"approved": {"text": "已批准", "theme": "running"},
|
|
165
|
+
"rejected": {"text": "已拒绝", "theme": "stop"}
|
|
166
|
+
}'
|
|
167
|
+
></status-tag>
|
|
168
|
+
|
|
169
|
+
<!-- 自定义连接状态 -->
|
|
170
|
+
<status-tag
|
|
171
|
+
status="connected"
|
|
172
|
+
status-map='{
|
|
173
|
+
"connected": {"text": "已连接", "theme": "running"},
|
|
174
|
+
"disconnected": {"text": "已断开", "theme": "stop"},
|
|
175
|
+
"connecting": {"text": "连接中", "theme": "loading"}
|
|
176
|
+
}'
|
|
177
|
+
></status-tag>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### 2. 动态更新
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// 通过 JavaScript 动态更新
|
|
184
|
+
const tag = document.querySelector('status-tag');
|
|
185
|
+
|
|
186
|
+
// 方式1: 使用属性
|
|
187
|
+
tag.setAttribute('status', 'running');
|
|
188
|
+
|
|
189
|
+
// 方式2: 使用属性访问器
|
|
190
|
+
tag.status = 'running';
|
|
191
|
+
|
|
192
|
+
// 动态切换语言
|
|
193
|
+
tag.locale = 'en-US';
|
|
194
|
+
|
|
195
|
+
// 动态更新状态映射
|
|
196
|
+
tag.statusMap = {
|
|
197
|
+
custom_status: { text: 'Custom Status', theme: 'warning' }
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 3. React 中使用
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { useEffect, useRef } from 'react';
|
|
205
|
+
|
|
206
|
+
// 基础用法
|
|
207
|
+
function StatusDisplay({ status, locale = 'zh-CN' }) {
|
|
208
|
+
return React.createElement('status-tag', {
|
|
209
|
+
'attr-status': status,
|
|
210
|
+
'attr-locale': locale
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 使用 ref 控制
|
|
215
|
+
function ControlledStatus() {
|
|
216
|
+
const ref = useRef<HTMLElement>(null);
|
|
217
|
+
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
if (ref.current) {
|
|
220
|
+
ref.current.setAttribute('status', 'running');
|
|
221
|
+
ref.current.setAttribute('locale', 'zh-CN');
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
|
|
225
|
+
return React.createElement('status-tag', { ref });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 在列表中使用
|
|
229
|
+
function StatusList({ items }) {
|
|
230
|
+
return (
|
|
231
|
+
<div>
|
|
232
|
+
{items.map(item => (
|
|
233
|
+
<div key={item.id}>
|
|
234
|
+
<span>{item.name}</span>
|
|
235
|
+
<status-tag
|
|
236
|
+
status={item.status}
|
|
237
|
+
locale={item.locale}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
))}
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### 4. Vue 3 中使用
|
|
247
|
+
|
|
248
|
+
```vue
|
|
249
|
+
<template>
|
|
250
|
+
<!-- 直接使用 -->
|
|
251
|
+
<status-tag :status="status" :locale="locale" />
|
|
252
|
+
|
|
253
|
+
<!-- 动态更新 -->
|
|
254
|
+
<status-tag :status="currentStatus" />
|
|
255
|
+
<button @click="changeStatus">切换状态</button>
|
|
256
|
+
|
|
257
|
+
<!-- 在循环中使用 -->
|
|
258
|
+
<div v-for="item in items" :key="item.id">
|
|
259
|
+
<status-tag :status="item.status" :locale="locale" />
|
|
260
|
+
</div>
|
|
261
|
+
</template>
|
|
262
|
+
|
|
263
|
+
<script setup>
|
|
264
|
+
import { ref } from 'vue';
|
|
265
|
+
import 'status-tag-web-component';
|
|
266
|
+
|
|
267
|
+
// 响应式数据
|
|
268
|
+
const status = ref('running');
|
|
269
|
+
const locale = ref('zh-CN');
|
|
270
|
+
const items = ref([
|
|
271
|
+
{ id: 1, status: 'loading' },
|
|
272
|
+
{ id: 2, status: 'running' },
|
|
273
|
+
{ id: 3, status: 'stop' }
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
// 方法
|
|
277
|
+
const changeStatus = () => {
|
|
278
|
+
const statuses = ['loading', 'running', 'stop', 'warning'];
|
|
279
|
+
const currentIndex = statuses.indexOf(status.value);
|
|
280
|
+
status.value = statuses[(currentIndex + 1) % statuses.length];
|
|
281
|
+
};
|
|
282
|
+
</script>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### 5. Vue 2 中使用
|
|
286
|
+
|
|
287
|
+
```vue
|
|
288
|
+
<template>
|
|
289
|
+
<status-tag :status="status" :locale="locale" />
|
|
290
|
+
</template>
|
|
291
|
+
|
|
292
|
+
<script>
|
|
293
|
+
export default {
|
|
294
|
+
data() {
|
|
295
|
+
return {
|
|
296
|
+
status: 'running',
|
|
297
|
+
locale: 'zh-CN'
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
mounted() {
|
|
301
|
+
// 动态更新
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
this.status = 'stop';
|
|
304
|
+
}, 3000);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
</script>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### 6. Angular 中使用
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { Component, Input } from '@angular/core';
|
|
314
|
+
|
|
315
|
+
@Component({
|
|
316
|
+
selector: 'app-status-demo',
|
|
317
|
+
template: `
|
|
318
|
+
<div class="status-item">
|
|
319
|
+
<span>{{ title }}</span>
|
|
320
|
+
<status-tag
|
|
321
|
+
[attr.status]="status"
|
|
322
|
+
[attr.locale]="locale"
|
|
323
|
+
></status-tag>
|
|
324
|
+
</div>
|
|
325
|
+
`
|
|
326
|
+
})
|
|
327
|
+
export class StatusDemoComponent {
|
|
328
|
+
@Input() title = '状态显示';
|
|
329
|
+
@Input() status = 'running';
|
|
330
|
+
@Input() locale = 'zh-CN';
|
|
331
|
+
|
|
332
|
+
// 动态更新状态
|
|
333
|
+
changeStatus() {
|
|
334
|
+
const statuses = ['loading', 'running', 'stop', 'warning'];
|
|
335
|
+
const currentIndex = statuses.indexOf(this.status);
|
|
336
|
+
this.status = statuses[(currentIndex + 1) % statuses.length];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// app.module.ts 中导入
|
|
343
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
344
|
+
|
|
345
|
+
@NgModule({
|
|
346
|
+
declarations: [AppComponent],
|
|
347
|
+
imports: [BrowserModule],
|
|
348
|
+
bootstrap: [AppComponent],
|
|
349
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA] // 允许使用自定义元素
|
|
350
|
+
})
|
|
351
|
+
export class AppModule { }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### 7. 在原生 JavaScript 中使用
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
// 动态创建组件
|
|
358
|
+
function createStatusTag(status, locale = 'zh-CN') {
|
|
359
|
+
const tag = document.createElement('status-tag');
|
|
360
|
+
tag.setAttribute('status', status);
|
|
361
|
+
tag.setAttribute('locale', locale);
|
|
362
|
+
return tag;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 添加到页面
|
|
366
|
+
document.body.appendChild(createStatusTag('running'));
|
|
367
|
+
|
|
368
|
+
// 事件监听
|
|
369
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
370
|
+
const tags = document.querySelectorAll('status-tag');
|
|
371
|
+
tags.forEach(tag => {
|
|
372
|
+
console.log('Status:', tag.getAttribute('status'));
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 响应状态变化
|
|
377
|
+
const observer = new MutationObserver((mutations) => {
|
|
378
|
+
mutations.forEach((mutation) => {
|
|
379
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'status') {
|
|
380
|
+
console.log('Status changed to:', mutation.target.getAttribute('status'));
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const tag = document.querySelector('status-tag');
|
|
386
|
+
observer.observe(tag, { attributes: true });
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## 🔧 开发指南
|
|
390
|
+
|
|
391
|
+
### 环境要求
|
|
392
|
+
|
|
393
|
+
- Node.js >= 16
|
|
394
|
+
- npm >= 8
|
|
395
|
+
|
|
396
|
+
### 安装依赖
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
npm install
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 开发模式
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npm run dev
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### 构建
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
npm run build
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 清理
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npm run clean
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### 项目结构
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
status-tag-component/
|
|
424
|
+
├── src/
|
|
425
|
+
│ ├── components/
|
|
426
|
+
│ │ └── status-tag.ts # 组件核心逻辑(内联样式 + SVG)
|
|
427
|
+
│ ├── locales/
|
|
428
|
+
│ │ ├── zh-CN.json # 中文语言包
|
|
429
|
+
│ │ └── en-US.json # 英文语言包
|
|
430
|
+
│ ├── utils/
|
|
431
|
+
│ │ └── i18n.ts # 国际化工具
|
|
432
|
+
│ ├── types.ts # 类型定义
|
|
433
|
+
│ └── index.ts # 入口文件
|
|
434
|
+
├── examples/ # 示例文件
|
|
435
|
+
│ ├── basic.html # 基础示例
|
|
436
|
+
│ ├── final-test.html # 完整测试
|
|
437
|
+
│ └── final-test-v2.html # 样式验证测试
|
|
438
|
+
├── dist/ # 构建输出
|
|
439
|
+
├── public/
|
|
440
|
+
│ └── loading-color.svg # SVG 图标源文件
|
|
441
|
+
├── package.json
|
|
442
|
+
├── tsconfig.json
|
|
443
|
+
├── vite.config.ts
|
|
444
|
+
├── README.md
|
|
445
|
+
├── MIGRATION.md # 从 Vue 迁移指南
|
|
446
|
+
└── PROJECT_SUMMARY.md # 项目总结
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## 🌐 浏览器兼容性
|
|
450
|
+
|
|
451
|
+
| 浏览器 | 版本 |
|
|
452
|
+
|--------|------|
|
|
453
|
+
| Chrome | >= 54 |
|
|
454
|
+
| Firefox | >= 63 |
|
|
455
|
+
| Safari | >= 10.1 |
|
|
456
|
+
| Edge | >= 79 |
|
|
457
|
+
| iOS Safari | >= 10.3 |
|
|
458
|
+
| Android Chrome | >= 54 |
|
|
459
|
+
|
|
460
|
+
## ❓ 常见问题
|
|
461
|
+
|
|
462
|
+
### Q: 样式没有生效怎么办?
|
|
463
|
+
|
|
464
|
+
A: 确保在组件加载后再设置属性:
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// 错误方式
|
|
468
|
+
const tag = document.createElement('status-tag');
|
|
469
|
+
tag.status = 'running'; // 可能在组件未初始化时就设置了
|
|
470
|
+
document.body.appendChild(tag);
|
|
471
|
+
|
|
472
|
+
// 正确方式
|
|
473
|
+
const tag = document.createElement('status-tag');
|
|
474
|
+
document.body.appendChild(tag);
|
|
475
|
+
tag.status = 'running'; // 在添加到 DOM 后设置
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Q: 如何在 React 中正确使用?
|
|
479
|
+
|
|
480
|
+
A: 使用 `useEffect` 确保在组件挂载后设置属性:
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
function StatusTag({ status }) {
|
|
484
|
+
const ref = useRef<HTMLElement>(null);
|
|
485
|
+
|
|
486
|
+
useEffect(() => {
|
|
487
|
+
if (ref.current) {
|
|
488
|
+
ref.current.setAttribute('status', status);
|
|
489
|
+
}
|
|
490
|
+
}, [status]);
|
|
491
|
+
|
|
492
|
+
return <status-tag ref={ref}></status-tag>;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Q: 如何自定义样式?
|
|
497
|
+
|
|
498
|
+
A: 由于使用 Shadow DOM,需要通过 `::part` 选择器或使用 `CSSStyleSheet`:
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
// 方法1: 使用 ::part(如果组件支持)
|
|
502
|
+
const style = document.createElement('style');
|
|
503
|
+
style.textContent = `
|
|
504
|
+
status-tag::part(container) {
|
|
505
|
+
/* 自定义样式 */
|
|
506
|
+
}
|
|
507
|
+
`;
|
|
508
|
+
|
|
509
|
+
// 方法2: 修改组件内部样式(需要直接操作 shadowRoot)
|
|
510
|
+
const tag = document.querySelector('status-tag');
|
|
511
|
+
const shadowStyle = tag.shadowRoot.querySelector('style');
|
|
512
|
+
shadowStyle.textContent += `
|
|
513
|
+
.bkbase-status-tag {
|
|
514
|
+
/* 添加自定义样式 */
|
|
515
|
+
}
|
|
516
|
+
`;
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Q: 如何添加新语言?
|
|
520
|
+
|
|
521
|
+
A: 只需两步:
|
|
522
|
+
|
|
523
|
+
1. 在 `src/locales/` 添加语言文件(如 `ja-JP.json`)
|
|
524
|
+
2. 在 `src/utils/i18n.ts` 中注册
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
// src/utils/i18n.ts
|
|
528
|
+
import jaJP from '../locales/ja-JP.json';
|
|
529
|
+
|
|
530
|
+
const resources = {
|
|
531
|
+
'zh-CN': zhCN,
|
|
532
|
+
'en-US': enUS,
|
|
533
|
+
'ja-JP': jaJP // 添加新语言
|
|
534
|
+
};
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Q: 性能如何?
|
|
538
|
+
|
|
539
|
+
A: 组件非常轻量:
|
|
540
|
+
- **UMD 版本**: 8KB (gzip: 2.95KB)
|
|
541
|
+
- **ES Module 版本**: 12KB (gzip: 3.27KB)
|
|
542
|
+
- **首次渲染**: < 1ms
|
|
543
|
+
- **属性更新**: < 0.5ms
|
|
544
|
+
|
|
545
|
+
## 📝 许可证
|
|
546
|
+
|
|
547
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
|
548
|
+
|
|
549
|
+
## 🤝 贡献
|
|
550
|
+
|
|
551
|
+
欢迎提交 Issue 和 Pull Request!
|
|
552
|
+
|
|
553
|
+
### 贡献流程
|
|
554
|
+
|
|
555
|
+
1. Fork 本项目到您的仓库
|
|
556
|
+
2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
|
|
557
|
+
3. 提交更改 (`git commit -m 'Add some amazing feature'`)
|
|
558
|
+
4. 推送到分支 (`git push origin feature/amazing-feature`)
|
|
559
|
+
5. 创建 Pull Request
|
|
560
|
+
|
|
561
|
+
## 📖 相关文档
|
|
562
|
+
|
|
563
|
+
- [完整文档](./README.md)
|
|
564
|
+
- [迁移指南](./MIGRATION.md) - 从 Vue 迁移指南
|
|
565
|
+
- [项目总结](./PROJECT_SUMMARY.md) - 项目总结
|
|
566
|
+
- [快速上手](./QUICKSTART.md) - 5 分钟上手指南
|
|
567
|
+
- [完成报告](./COMPLETION_REPORT.md) - 项目完成报告
|
|
568
|
+
- [examples/final-test-v2.html](./examples/final-test-v2.html) - 完整测试页面
|
|
569
|
+
|
|
570
|
+
## ⭐ 致谢
|
|
571
|
+
|
|
572
|
+
感谢所有为这个项目做出贡献的开发者!
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
<div align="center">
|
|
577
|
+
|
|
578
|
+
**如果这个项目对您有帮助,请给我们一个 ⭐️!**
|
|
579
|
+
|
|
580
|
+
本地项目 · [查看文档](#) · [提交 Issue](#)
|
|
581
|
+
|
|
582
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><g fill="#3a84ff"><path d="M332.8 243.2c25.6 25.6 25.6 64 0 89.6-25.6 25.6-64 25.6-89.6 0L217.6 307.2 198.4 288c-25.6-25.6-25.6-64 0-89.6s64-25.6 89.6 0l25.6 25.6L332.8 243.2z" opacity=".1"/><path d="M192 448c38.4 0 64 25.6 64 64S230.4 576 192 576H160 128C89.6 576 64 550.4 64 512s25.6-64 64-64h32H192z" opacity=".15"/><path d="M243.2 691.2c25.6-25.6 64-25.6 89.6 0 25.6 25.6 25.6 64 0 89.6l-25.6 25.6L281.6 832c-25.6 25.6-64 25.6-89.6 0s-25.6-64 0-89.6l25.6-25.6L243.2 691.2z" opacity=".3"/><path d="M448 832c0-38.4 25.6-64 64-64s64 25.6 64 64v32V896c0 38.4-25.6 64-64 64s-64-25.6-64-64v-32V832z" opacity=".45"/><path d="M825.6 825.6c-25.6 25.6-64 25.6-89.6 0l-25.6-25.6 0 0-25.6-25.6c-25.6-25.6-25.6-64 0-89.6s64-25.6 89.6 0l25.6 25.6 25.6 25.6 0 0C851.2 761.6 851.2 806.4 825.6 825.6z" opacity=".6"/><path d="M896 448l-32 128H832c-38.4 0-64-25.6-64-64s25.6-64 64-64H896zM960 512c0 38.4-25.6 64-64 64h-32L896 448C934.4 448 960 473.6 960 512z" opacity=".75"/><path d="M742.4 192c25.6-19.2 64-19.2 83.2 6.4 25.6 25.6 25.6 64 0 89.6l-25.6 25.6-25.6 25.6c-25.6 25.6-64 25.6-89.6 0s-25.6-64 0-89.6" opacity=".9"/><path d="M448 160L576 192c0 38.4-25.6 64-64 64S448 230.4 448 192V160zM512 64c38.4 0 64 25.6 64 64v32H448V128C448 89.6 473.6 64 512 64zM448 160h128V192H448V160z"/></g></svg>
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
const status$1 = {
|
|
5
|
+
loading: "加载中",
|
|
6
|
+
running: "运行中",
|
|
7
|
+
stop: "已停止",
|
|
8
|
+
warning: "警告",
|
|
9
|
+
unknown: "未知"
|
|
10
|
+
};
|
|
11
|
+
const zhCN = {
|
|
12
|
+
status: status$1
|
|
13
|
+
};
|
|
14
|
+
const status = {
|
|
15
|
+
loading: "Loading",
|
|
16
|
+
running: "Running",
|
|
17
|
+
stop: "Stopped",
|
|
18
|
+
warning: "Warning",
|
|
19
|
+
unknown: "Unknown"
|
|
20
|
+
};
|
|
21
|
+
const enUS = {
|
|
22
|
+
status
|
|
23
|
+
};
|
|
24
|
+
const resources = {
|
|
25
|
+
"zh-CN": zhCN,
|
|
26
|
+
"en-US": enUS
|
|
27
|
+
};
|
|
28
|
+
function getLanguageFromCookie() {
|
|
29
|
+
if (typeof document === "undefined") {
|
|
30
|
+
return "zh-CN";
|
|
31
|
+
}
|
|
32
|
+
const cookies = document.cookie.split(";");
|
|
33
|
+
for (const cookie of cookies) {
|
|
34
|
+
const [name, ...valueParts] = cookie.trim().split("=");
|
|
35
|
+
if (name === "blueking_language") {
|
|
36
|
+
const value = valueParts.join("=");
|
|
37
|
+
return value === "en" ? "en-US" : "zh-CN";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "zh-CN";
|
|
41
|
+
}
|
|
42
|
+
class I18n {
|
|
43
|
+
constructor(initialLocale = "zh-CN") {
|
|
44
|
+
__publicField(this, "currentLocale", "zh-CN");
|
|
45
|
+
this.currentLocale = initialLocale || "zh-CN";
|
|
46
|
+
}
|
|
47
|
+
setLocale(locale) {
|
|
48
|
+
if (resources[locale]) {
|
|
49
|
+
this.currentLocale = locale;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getLocale() {
|
|
53
|
+
return this.currentLocale;
|
|
54
|
+
}
|
|
55
|
+
t(key) {
|
|
56
|
+
const keys = key.split(".");
|
|
57
|
+
let value = resources[this.currentLocale];
|
|
58
|
+
for (const k of keys) {
|
|
59
|
+
if (value && typeof value === "object" && k in value) {
|
|
60
|
+
value = value[k];
|
|
61
|
+
} else {
|
|
62
|
+
return key;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return typeof value === "string" ? value : key;
|
|
66
|
+
}
|
|
67
|
+
getAvailableLocales() {
|
|
68
|
+
return Object.keys(resources);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const i18n = new I18n();
|
|
72
|
+
const DEFAULT_STATUS_MAP = {
|
|
73
|
+
loading: { text: "loading", theme: "loading" },
|
|
74
|
+
running: { text: "running", theme: "running" },
|
|
75
|
+
stop: { text: "stop", theme: "stop" },
|
|
76
|
+
warning: { text: "warning", theme: "warning" },
|
|
77
|
+
unknown: { text: "unknown", theme: "unknown" }
|
|
78
|
+
};
|
|
79
|
+
const SVG_DATA_URI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNhODRmZiI+PHBhdGggZD0iTTMzMi44IDI0My4yYzI1LjYgMjUuNiAyNS42IDY0IDAgODkuNi0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBMMjE3LjYgMzA3LjIgMTk4LjQgMjg4Yy0yNS42LTI1LjYtMjUuNi02NCAwLTg5LjZzNjQtMjUuNiA4OS42IDBsMjUuNiAyNS42TDMzMi44IDI0My4yeiIgb3BhY2l0eT0iLjEiLz48cGF0aCBkPSJNMTkyIDQ0OGMzOC40IDAgNjQgMjUuNiA2NCA2NFMyMzAuNCA1NzYgMTkyIDU3NkgxNjAgMTI4Qzg5LjYgNTc2IDY0IDU1MC40IDY0IDUxMnMyNS42LTY0IDY0LTY0aDMySDE5MnoiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0yNDMuMiA2OTEuMmMyNS42LTI1LjYgNjQtMjUuNiA4OS42IDAgMjUuNiAyNS42IDI1LjYgNjQgMCA4OS42bC0yNS42IDI1LjZMMjgxLjYgODMyYy0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBzLTI1LjYtNjQgMC04OS42bDI1LjYtMjUuNkwyNDMuMiA2OTEuMnoiIG9wYWNpdHk9Ii4zIi8+PHBhdGggZD0iTTQ0OCA4MzJjMC0zOC40IDI1LjYtNjQgNjQtNjRzNjQgMjUuNiA2NCA2NHYzMlY4OTZjMCAzOC40LTI1LjYgNjQtNjQgNjRzLTY0LTI1LjYtNjQtNjR2LTMyVjgzMnoiIG9wYWNpdHk9Ii40NSIvPjxwYXRoIGQ9Ik04MjUuNiA4MjUuNmMtMjUuNiAyNS42LTY0IDI1LjYtODkuNiAwbC0yNS42LTI1LjYgMCAwLTI1LjYtMjUuNmMtMjUuNi0yNS42LTI1LjYtNjQgMC04OS42czY0LTI1LjYgODkuNiAwbDI1LjYgMjUuNiAyNS42IDI1LjYgMCAwQzg1MS4yIDc2MS42IDg1MS4yIDgwNi40IDgyNS42IDgyNS42eiIgb3BhY2l0eT0iLjYiLz48cGF0aCBkPSJNODk2IDQ0OGwtMzIgMTI4SDgzMmMtMzguNCAwLTY0LTI1LjYtNjQtNjRzMjUuNi02NCA2NC02NEg4OTZ6TTk2MCA1MTJjMCAzOC40LTI1LjYgNjQtNjQgNjRoLTMyTDg5NiA0NDhDOTM0LjQgNDQ4IDk2MCA0NzMuNiA5NjAgNTEyeiIgb3BhYWNpdHk9Ii43NSIvPjxwYXRoIGQ9Ik03NDIuNCAxOTJjMjUuNi0xOS4yIDY0LTE5LjIgODMuMiA2LjQgMjUuNiAyNS42IDI1LjYgNjQgMCA4OS42bC0yNS42IDI1LjYtMjUuNiAyNS42Yy0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBzLTI1LjYtNjQgMC04OS42IiBvcGFjaXR5PSIuOSIvPjxwYXRoIGQ9Ik00NDggMTYwTDU3NiAxOTJjMCAzOC40LTI1LjYgNjQtNjQgNjRTNDQ4IDIzMC40IDQ0OCAxOTJWMTYwek01MTIgNjRjMzguNCAwIDY0IDI1LjYgNjQgNjR2MzJINDQ4VjEyOEM0NDggODkuNiA0NzMuNiA2NCA1MTIgNjR6TTQ0OCAxNjBoMTI4VjE5Mkg0NDhWMTYwem0iLz48L2c+PC9zdmc+";
|
|
80
|
+
const COMPONENT_STYLES = `
|
|
81
|
+
.bkbase-status-tag {
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
padding: 0 8px;
|
|
85
|
+
height: 22px;
|
|
86
|
+
border-radius: 13px;
|
|
87
|
+
font-size: 12px;
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
background-color: #f0f1f5;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.bkbase-status-tag-loading {
|
|
93
|
+
width: 12px;
|
|
94
|
+
height: 12px;
|
|
95
|
+
margin-right: 4px;
|
|
96
|
+
background-image: url('${SVG_DATA_URI}');
|
|
97
|
+
background-repeat: no-repeat;
|
|
98
|
+
background-position: center;
|
|
99
|
+
background-size: 100%;
|
|
100
|
+
animation: bk-status-loading-rotate 1s linear infinite;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.bkbase-status-tag-dot {
|
|
104
|
+
display: inline-block;
|
|
105
|
+
width: 6px;
|
|
106
|
+
height: 6px;
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
margin-right: 4px;
|
|
109
|
+
background-color: #c4c6cc;
|
|
110
|
+
position: relative;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.bkbase-status-tag-dot::before {
|
|
114
|
+
content: '';
|
|
115
|
+
position: absolute;
|
|
116
|
+
width: 100%;
|
|
117
|
+
height: 100%;
|
|
118
|
+
border-radius: 50%;
|
|
119
|
+
background-color: inherit;
|
|
120
|
+
opacity: 0.2;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.bkbase-status-tag--loading {
|
|
124
|
+
background-color: #EDF4FF;
|
|
125
|
+
border: 1px solid #CDDFFE;
|
|
126
|
+
color: #699DF4;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.bkbase-status-tag--running {
|
|
130
|
+
background-color: #EBFAEF;
|
|
131
|
+
border: 1px solid #CBF0DA;
|
|
132
|
+
color: #299E56;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.bkbase-status-tag--running .bkbase-status-tag-dot {
|
|
136
|
+
background: #E5F6EA;
|
|
137
|
+
border: 1px solid #3FC06D;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.bkbase-status-tag--stop {
|
|
141
|
+
background-color: #F5F7FA;
|
|
142
|
+
color: #979BA5;
|
|
143
|
+
border: 1px solid #EAEBF0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.bkbase-status-tag--stop .bkbase-status-tag-dot {
|
|
147
|
+
background-color: #F0F1F5;
|
|
148
|
+
border: 1px solid #C4C6CC;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bkbase-status-tag--warning {
|
|
152
|
+
background-color: #FDF4E9;
|
|
153
|
+
color: #F59500;
|
|
154
|
+
border: 1px solid #FCE5C0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.bkbase-status-tag--warning .bkbase-status-tag-dot {
|
|
158
|
+
background: #FCE5C0;
|
|
159
|
+
border: 1px solid #F59500;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.bkbase-status-tag--unknown {
|
|
163
|
+
background-color: #fff3e8;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.bkbase-status-tag--unknown .bkbase-status-tag-dot {
|
|
167
|
+
background-color: #ff9c01;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@keyframes bk-status-loading-rotate {
|
|
171
|
+
from {
|
|
172
|
+
transform: rotate(0deg);
|
|
173
|
+
}
|
|
174
|
+
to {
|
|
175
|
+
transform: rotate(360deg);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
`;
|
|
179
|
+
class StatusTag extends HTMLElement {
|
|
180
|
+
constructor() {
|
|
181
|
+
super();
|
|
182
|
+
__publicField(this, "_status", "unknown");
|
|
183
|
+
__publicField(this, "_statusMap", {});
|
|
184
|
+
__publicField(this, "_locale", "zh-CN");
|
|
185
|
+
__publicField(this, "_mergedStatusMap", {});
|
|
186
|
+
this.attachShadow({ mode: "open" });
|
|
187
|
+
}
|
|
188
|
+
static get observedAttributes() {
|
|
189
|
+
return ["status", "status-map", "locale"];
|
|
190
|
+
}
|
|
191
|
+
connectedCallback() {
|
|
192
|
+
const localeAttr = this.getAttribute("locale");
|
|
193
|
+
if (!localeAttr) {
|
|
194
|
+
const cookieLocale = getLanguageFromCookie();
|
|
195
|
+
this._locale = cookieLocale;
|
|
196
|
+
i18n.setLocale(this._locale);
|
|
197
|
+
}
|
|
198
|
+
this.render();
|
|
199
|
+
}
|
|
200
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
201
|
+
if (oldValue === newValue) return;
|
|
202
|
+
switch (name) {
|
|
203
|
+
case "status":
|
|
204
|
+
this._status = newValue || "unknown";
|
|
205
|
+
break;
|
|
206
|
+
case "status-map":
|
|
207
|
+
try {
|
|
208
|
+
this._statusMap = newValue ? JSON.parse(newValue) : {};
|
|
209
|
+
} catch (e) {
|
|
210
|
+
console.warn("Invalid status-map JSON:", e);
|
|
211
|
+
this._statusMap = {};
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
case "locale":
|
|
215
|
+
this._locale = newValue || "zh-CN";
|
|
216
|
+
i18n.setLocale(this._locale);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
this.render();
|
|
220
|
+
}
|
|
221
|
+
get status() {
|
|
222
|
+
return this._status;
|
|
223
|
+
}
|
|
224
|
+
set status(value) {
|
|
225
|
+
this.setAttribute("status", value);
|
|
226
|
+
}
|
|
227
|
+
get statusMap() {
|
|
228
|
+
return this._statusMap;
|
|
229
|
+
}
|
|
230
|
+
set statusMap(value) {
|
|
231
|
+
this.setAttribute("status-map", JSON.stringify(value));
|
|
232
|
+
}
|
|
233
|
+
get locale() {
|
|
234
|
+
return this._locale;
|
|
235
|
+
}
|
|
236
|
+
set locale(value) {
|
|
237
|
+
this.setAttribute("locale", value);
|
|
238
|
+
}
|
|
239
|
+
mergeStatusMaps() {
|
|
240
|
+
this._mergedStatusMap = { ...DEFAULT_STATUS_MAP, ...this._statusMap };
|
|
241
|
+
return this._mergedStatusMap;
|
|
242
|
+
}
|
|
243
|
+
findMatchingStatusKey(status2) {
|
|
244
|
+
const map = this.mergeStatusMaps();
|
|
245
|
+
if (map[status2]) {
|
|
246
|
+
return status2;
|
|
247
|
+
}
|
|
248
|
+
const lowerStatus = status2.toLowerCase();
|
|
249
|
+
if (map[lowerStatus]) {
|
|
250
|
+
return lowerStatus;
|
|
251
|
+
}
|
|
252
|
+
const upperStatus = status2.toUpperCase();
|
|
253
|
+
if (map[upperStatus]) {
|
|
254
|
+
return upperStatus;
|
|
255
|
+
}
|
|
256
|
+
return "unknown";
|
|
257
|
+
}
|
|
258
|
+
getCurrentStatus() {
|
|
259
|
+
const matchedKey = this.findMatchingStatusKey(this._status);
|
|
260
|
+
const statusConfig = this.mergeStatusMap()[matchedKey] || this.mergeStatusMaps()["unknown"];
|
|
261
|
+
return {
|
|
262
|
+
...statusConfig,
|
|
263
|
+
text: i18n.t(`status.${statusConfig.text}`)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
mergeStatusMap() {
|
|
267
|
+
const map = this.mergeStatusMaps();
|
|
268
|
+
const merged = {};
|
|
269
|
+
for (const [key, config] of Object.entries(map)) {
|
|
270
|
+
merged[key] = {
|
|
271
|
+
...config,
|
|
272
|
+
text: this.isDefaultKey(key) ? config.text : config.text
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return merged;
|
|
276
|
+
}
|
|
277
|
+
isDefaultKey(key) {
|
|
278
|
+
return ["loading", "running", "stop", "warning", "unknown"].includes(key);
|
|
279
|
+
}
|
|
280
|
+
getThemeClass(theme) {
|
|
281
|
+
return `bkbase-status-tag--${theme}`;
|
|
282
|
+
}
|
|
283
|
+
createIcon(theme) {
|
|
284
|
+
if (theme === "loading") {
|
|
285
|
+
const icon = document.createElement("span");
|
|
286
|
+
icon.className = "bkbase-status-tag-loading";
|
|
287
|
+
return icon;
|
|
288
|
+
} else {
|
|
289
|
+
const icon = document.createElement("span");
|
|
290
|
+
icon.className = "bkbase-status-tag-dot";
|
|
291
|
+
return icon;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
render() {
|
|
295
|
+
const currentStatus = this.getCurrentStatus();
|
|
296
|
+
const themeClass = this.getThemeClass(currentStatus.theme);
|
|
297
|
+
const icon = this.createIcon(currentStatus.theme);
|
|
298
|
+
const text = document.createElement("span");
|
|
299
|
+
text.textContent = currentStatus.text;
|
|
300
|
+
const wrapper = document.createElement("div");
|
|
301
|
+
wrapper.className = `bkbase-status-tag ${themeClass}`;
|
|
302
|
+
wrapper.appendChild(icon);
|
|
303
|
+
wrapper.appendChild(text);
|
|
304
|
+
const style = document.createElement("style");
|
|
305
|
+
style.textContent = COMPONENT_STYLES;
|
|
306
|
+
this.shadowRoot.innerHTML = "";
|
|
307
|
+
this.shadowRoot.appendChild(style);
|
|
308
|
+
this.shadowRoot.appendChild(wrapper);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (typeof customElements !== "undefined") {
|
|
312
|
+
if (!customElements.get("status-tag")) {
|
|
313
|
+
customElements.define("status-tag", StatusTag);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export {
|
|
317
|
+
I18n,
|
|
318
|
+
StatusTag,
|
|
319
|
+
i18n
|
|
320
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).StatusTag={})}(this,function(t){"use strict";var n=Object.defineProperty,e=(t,e,s)=>((t,e,s)=>e in t?n(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s)(t,"symbol"!=typeof e?e+"":e,s);const s={"zh-CN":{status:{loading:"加载中",running:"运行中",stop:"已停止",warning:"警告",unknown:"未知"}},"en-US":{status:{loading:"Loading",running:"Running",stop:"Stopped",warning:"Warning",unknown:"Unknown"}}};class a{constructor(t="zh-CN"){e(this,"currentLocale","zh-CN"),this.currentLocale=t||"zh-CN"}setLocale(t){s[t]&&(this.currentLocale=t)}getLocale(){return this.currentLocale}t(t){const n=t.split(".");let e=s[this.currentLocale];for(const s of n){if(!e||"object"!=typeof e||!(s in e))return t;e=e[s]}return"string"==typeof e?e:t}getAvailableLocales(){return Object.keys(s)}}const o=new a,i={loading:{text:"loading",theme:"loading"},running:{text:"running",theme:"running"},stop:{text:"stop",theme:"stop"},warning:{text:"warning",theme:"warning"},unknown:{text:"unknown",theme:"unknown"}};class r extends HTMLElement{constructor(){super(),e(this,"_status","unknown"),e(this,"_statusMap",{}),e(this,"_locale","zh-CN"),e(this,"_mergedStatusMap",{}),this.attachShadow({mode:"open"})}static get observedAttributes(){return["status","status-map","locale"]}connectedCallback(){if(!this.getAttribute("locale")){const t=function(){if("undefined"==typeof document)return"zh-CN";const t=document.cookie.split(";");for(const n of t){const[t,...e]=n.trim().split("=");if("blueking_language"===t)return"en"===e.join("=")?"en-US":"zh-CN"}return"zh-CN"}();this._locale=t,o.setLocale(this._locale)}this.render()}attributeChangedCallback(t,n,e){if(n!==e){switch(t){case"status":this._status=e||"unknown";break;case"status-map":try{this._statusMap=e?JSON.parse(e):{}}catch(s){this._statusMap={}}break;case"locale":this._locale=e||"zh-CN",o.setLocale(this._locale)}this.render()}}get status(){return this._status}set status(t){this.setAttribute("status",t)}get statusMap(){return this._statusMap}set statusMap(t){this.setAttribute("status-map",JSON.stringify(t))}get locale(){return this._locale}set locale(t){this.setAttribute("locale",t)}mergeStatusMaps(){return this._mergedStatusMap={...i,...this._statusMap},this._mergedStatusMap}findMatchingStatusKey(t){const n=this.mergeStatusMaps();if(n[t])return t;const e=t.toLowerCase();if(n[e])return e;const s=t.toUpperCase();return n[s]?s:"unknown"}getCurrentStatus(){const t=this.findMatchingStatusKey(this._status),n=this.mergeStatusMap()[t]||this.mergeStatusMaps().unknown;return{...n,text:o.t(`status.${n.text}`)}}mergeStatusMap(){const t=this.mergeStatusMaps(),n={};for(const[e,s]of Object.entries(t))n[e]={...s,text:(this.isDefaultKey(e),s.text)};return n}isDefaultKey(t){return["loading","running","stop","warning","unknown"].includes(t)}getThemeClass(t){return`bkbase-status-tag--${t}`}createIcon(t){if("loading"===t){const t=document.createElement("span");return t.className="bkbase-status-tag-loading",t}{const t=document.createElement("span");return t.className="bkbase-status-tag-dot",t}}render(){const t=this.getCurrentStatus(),n=this.getThemeClass(t.theme),e=this.createIcon(t.theme),s=document.createElement("span");s.textContent=t.text;const a=document.createElement("div");a.className=`bkbase-status-tag ${n}`,a.appendChild(e),a.appendChild(s);const o=document.createElement("style");o.textContent="\n .bkbase-status-tag {\n display: inline-flex;\n align-items: center;\n padding: 0 8px;\n height: 22px;\n border-radius: 13px;\n font-size: 12px;\n font-weight: 700;\n background-color: #f0f1f5;\n }\n\n .bkbase-status-tag-loading {\n width: 12px;\n height: 12px;\n margin-right: 4px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNhODRmZiI+PHBhdGggZD0iTTMzMi44IDI0My4yYzI1LjYgMjUuNiAyNS42IDY0IDAgODkuNi0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBMMjE3LjYgMzA3LjIgMTk4LjQgMjg4Yy0yNS42LTI1LjYtMjUuNi02NCAwLTg5LjZzNjQtMjUuNiA4OS42IDBsMjUuNiAyNS42TDMzMi44IDI0My4yeiIgb3BhY2l0eT0iLjEiLz48cGF0aCBkPSJNMTkyIDQ0OGMzOC40IDAgNjQgMjUuNiA2NCA2NFMyMzAuNCA1NzYgMTkyIDU3NkgxNjAgMTI4Qzg5LjYgNTc2IDY0IDU1MC40IDY0IDUxMnMyNS42LTY0IDY0LTY0aDMySDE5MnoiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0yNDMuMiA2OTEuMmMyNS42LTI1LjYgNjQtMjUuNiA4OS42IDAgMjUuNiAyNS42IDI1LjYgNjQgMCA4OS42bC0yNS42IDI1LjZMMjgxLjYgODMyYy0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBzLTI1LjYtNjQgMC04OS42bDI1LjYtMjUuNkwyNDMuMiA2OTEuMnoiIG9wYWNpdHk9Ii4zIi8+PHBhdGggZD0iTTQ0OCA4MzJjMC0zOC40IDI1LjYtNjQgNjQtNjRzNjQgMjUuNiA2NCA2NHYzMlY4OTZjMCAzOC40LTI1LjYgNjQtNjQgNjRzLTY0LTI1LjYtNjQtNjR2LTMyVjgzMnoiIG9wYWNpdHk9Ii40NSIvPjxwYXRoIGQ9Ik04MjUuNiA4MjUuNmMtMjUuNiAyNS42LTY0IDI1LjYtODkuNiAwbC0yNS42LTI1LjYgMCAwLTI1LjYtMjUuNmMtMjUuNi0yNS42LTI1LjYtNjQgMC04OS42czY0LTI1LjYgODkuNiAwbDI1LjYgMjUuNiAyNS42IDI1LjYgMCAwQzg1MS4yIDc2MS42IDg1MS4yIDgwNi40IDgyNS42IDgyNS42eiIgb3BhY2l0eT0iLjYiLz48cGF0aCBkPSJNODk2IDQ0OGwtMzIgMTI4SDgzMmMtMzguNCAwLTY0LTI1LjYtNjQtNjRzMjUuNi02NCA2NC02NEg4OTZ6TTk2MCA1MTJjMCAzOC40LTI1LjYgNjQtNjQgNjRoLTMyTDg5NiA0NDhDOTM0LjQgNDQ4IDk2MCA0NzMuNiA5NjAgNTEyeiIgb3BhYWNpdHk9Ii43NSIvPjxwYXRoIGQ9Ik03NDIuNCAxOTJjMjUuNi0xOS4yIDY0LTE5LjIgODMuMiA2LjQgMjUuNiAyNS42IDI1LjYgNjQgMCA4OS42bC0yNS42IDI1LjYtMjUuNiAyNS42Yy0yNS42IDI1LjYtNjQgMjUuNi04OS42IDBzLTI1LjYtNjQgMC04OS42IiBvcGFjaXR5PSIuOSIvPjxwYXRoIGQ9Ik00NDggMTYwTDU3NiAxOTJjMCAzOC40LTI1LjYgNjQtNjQgNjRTNDQ4IDIzMC40IDQ0OCAxOTJWMTYwek01MTIgNjRjMzguNCAwIDY0IDI1LjYgNjQgNjR2MzJINDQ4VjEyOEM0NDggODkuNiA0NzMuNiA2NCA1MTIgNjR6TTQ0OCAxNjBoMTI4VjE5Mkg0NDhWMTYwem0iLz48L2c+PC9zdmc+');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 100%;\n animation: bk-status-loading-rotate 1s linear infinite;\n }\n\n .bkbase-status-tag-dot {\n display: inline-block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n margin-right: 4px;\n background-color: #c4c6cc;\n position: relative;\n }\n\n .bkbase-status-tag-dot::before {\n content: '';\n position: absolute;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: inherit;\n opacity: 0.2;\n }\n\n .bkbase-status-tag--loading {\n background-color: #EDF4FF;\n border: 1px solid #CDDFFE;\n color: #699DF4;\n }\n\n .bkbase-status-tag--running {\n background-color: #EBFAEF;\n border: 1px solid #CBF0DA;\n color: #299E56;\n }\n\n .bkbase-status-tag--running .bkbase-status-tag-dot {\n background: #E5F6EA;\n border: 1px solid #3FC06D;\n }\n\n .bkbase-status-tag--stop {\n background-color: #F5F7FA;\n color: #979BA5;\n border: 1px solid #EAEBF0;\n }\n\n .bkbase-status-tag--stop .bkbase-status-tag-dot {\n background-color: #F0F1F5;\n border: 1px solid #C4C6CC;\n }\n\n .bkbase-status-tag--warning {\n background-color: #FDF4E9;\n color: #F59500;\n border: 1px solid #FCE5C0;\n }\n\n .bkbase-status-tag--warning .bkbase-status-tag-dot {\n background: #FCE5C0;\n border: 1px solid #F59500;\n }\n\n .bkbase-status-tag--unknown {\n background-color: #fff3e8;\n }\n\n .bkbase-status-tag--unknown .bkbase-status-tag-dot {\n background-color: #ff9c01;\n }\n\n @keyframes bk-status-loading-rotate {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n",this.shadowRoot.innerHTML="",this.shadowRoot.appendChild(o),this.shadowRoot.appendChild(a)}}"undefined"!=typeof customElements&&(customElements.get("status-tag")||customElements.define("status-tag",r)),t.I18n=a,t.StatusTag=r,t.i18n=o,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blueking/status-tag-web-component",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A framework-agnostic Status Tag Web Component with i18n support",
|
|
5
|
+
"main": "dist/status-tag.umd.js",
|
|
6
|
+
"module": "dist/status-tag.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"public"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"web-component",
|
|
14
|
+
"status-tag",
|
|
15
|
+
"i18n",
|
|
16
|
+
"framework-agnostic",
|
|
17
|
+
"typescript",
|
|
18
|
+
"shadow-dom",
|
|
19
|
+
"custom-elements"
|
|
20
|
+
],
|
|
21
|
+
"author": "Your Name <your.email@example.com>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org/",
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/forrany/status-tag-component.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/forrany/status-tag-component.git",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16.0.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "vite",
|
|
37
|
+
"build": "tsc && vite build",
|
|
38
|
+
"preview": "vite preview",
|
|
39
|
+
"clean": "rm -rf dist"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.10.0",
|
|
43
|
+
"sass-embedded": "^1.93.3",
|
|
44
|
+
"terser": "^5.44.0",
|
|
45
|
+
"typescript": "^5.3.0",
|
|
46
|
+
"vite": "^5.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><g fill="#3a84ff"><path d="M332.8 243.2c25.6 25.6 25.6 64 0 89.6-25.6 25.6-64 25.6-89.6 0L217.6 307.2 198.4 288c-25.6-25.6-25.6-64 0-89.6s64-25.6 89.6 0l25.6 25.6L332.8 243.2z" opacity=".1"/><path d="M192 448c38.4 0 64 25.6 64 64S230.4 576 192 576H160 128C89.6 576 64 550.4 64 512s25.6-64 64-64h32H192z" opacity=".15"/><path d="M243.2 691.2c25.6-25.6 64-25.6 89.6 0 25.6 25.6 25.6 64 0 89.6l-25.6 25.6L281.6 832c-25.6 25.6-64 25.6-89.6 0s-25.6-64 0-89.6l25.6-25.6L243.2 691.2z" opacity=".3"/><path d="M448 832c0-38.4 25.6-64 64-64s64 25.6 64 64v32V896c0 38.4-25.6 64-64 64s-64-25.6-64-64v-32V832z" opacity=".45"/><path d="M825.6 825.6c-25.6 25.6-64 25.6-89.6 0l-25.6-25.6 0 0-25.6-25.6c-25.6-25.6-25.6-64 0-89.6s64-25.6 89.6 0l25.6 25.6 25.6 25.6 0 0C851.2 761.6 851.2 806.4 825.6 825.6z" opacity=".6"/><path d="M896 448l-32 128H832c-38.4 0-64-25.6-64-64s25.6-64 64-64H896zM960 512c0 38.4-25.6 64-64 64h-32L896 448C934.4 448 960 473.6 960 512z" opacity=".75"/><path d="M742.4 192c25.6-19.2 64-19.2 83.2 6.4 25.6 25.6 25.6 64 0 89.6l-25.6 25.6-25.6 25.6c-25.6 25.6-64 25.6-89.6 0s-25.6-64 0-89.6" opacity=".9"/><path d="M448 160L576 192c0 38.4-25.6 64-64 64S448 230.4 448 192V160zM512 64c38.4 0 64 25.6 64 64v32H448V128C448 89.6 473.6 64 512 64zM448 160h128V192H448V160z"/></g></svg>
|