├── img
├── icon128.png
├── icon16.png
├── icon48.png
└── github-logo.svg
├── manifest.json
├── css
└── styles.css
├── js
├── popup.js
└── cron-parser.js
└── popup.html
/img/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yml2213/cron/HEAD/img/icon128.png
--------------------------------------------------------------------------------
/img/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yml2213/cron/HEAD/img/icon16.png
--------------------------------------------------------------------------------
/img/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yml2213/cron/HEAD/img/icon48.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Cron定时解析",
3 | "description": "一个简单的Cron表达式解析工具",
4 | "version": "2.0.0",
5 | "manifest_version": 3,
6 | "action": {
7 | "default_popup": "popup.html",
8 | "default_icon": {
9 | "16": "img/icon16.png",
10 | "48": "img/icon48.png",
11 | "128": "img/icon128.png"
12 | },
13 | "default_title": "Cron定时解析"
14 | },
15 | "icons": {
16 | "16": "img/icon16.png",
17 | "48": "img/icon48.png",
18 | "128": "img/icon128.png"
19 | },
20 | "permissions": [],
21 | "host_permissions": []
22 | }
--------------------------------------------------------------------------------
/img/github-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/styles.css:
--------------------------------------------------------------------------------
1 | /* 基础样式 */
2 | html, body {
3 | height: 100%;
4 | }
5 |
6 | body {
7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
8 | width: 800px; /* 增加宽度以适应双栏 */
9 | height: 500px;
10 | margin: 0;
11 | color: #333;
12 | background-color: #f7f7f7;
13 | display: flex;
14 | flex-direction: column;
15 | }
16 |
17 | .container {
18 | display: flex;
19 | flex-direction: column;
20 | height: 100%;
21 | padding: 16px;
22 | box-sizing: border-box;
23 | }
24 |
25 | h1 {
26 | font-size: 18px;
27 | margin: 0;
28 | color: #333;
29 | }
30 |
31 | h4 {
32 | margin-top: 16px;
33 | margin-bottom: 8px;
34 | color: #2c8898;
35 | }
36 |
37 | /* 头部样式 */
38 | .header {
39 | display: flex;
40 | justify-content: space-between;
41 | align-items: center;
42 | margin-bottom: 16px;
43 | flex-shrink: 0;
44 | }
45 |
46 | .header-left {
47 | display: flex;
48 | align-items: center;
49 | gap: 8px;
50 | }
51 |
52 | #github-link {
53 | display: inline-flex;
54 | align-items: center;
55 | justify-content: center;
56 | }
57 |
58 | #github-link img {
59 | width: 20px;
60 | height: 20px;
61 | filter: grayscale(1);
62 | transition: filter 0.3s;
63 | }
64 |
65 | #github-link:hover img {
66 | filter: grayscale(0);
67 | }
68 |
69 | /* 主内容区 */
70 | .main-content {
71 | display: flex;
72 | flex: 1;
73 | gap: 16px;
74 | overflow: hidden; /* 防止子元素溢出 */
75 | }
76 |
77 | /* 左右面板 */
78 | .left-panel, .right-panel {
79 | background-color: white;
80 | border-radius: 8px;
81 | border: 1px solid #e0e0e0;
82 | padding: 16px;
83 | box-sizing: border-box;
84 | display: flex;
85 | flex-direction: column;
86 | }
87 |
88 | .left-panel {
89 | flex: 6; /* 左侧更宽 */
90 | }
91 |
92 | .right-panel {
93 | flex: 4; /* 右侧稍窄 */
94 | }
95 |
96 | /* 格式切换开关 */
97 | .format-switch-container {
98 | display: flex;
99 | align-items: center;
100 | }
101 |
102 | .switch {
103 | position: relative;
104 | display: inline-block;
105 | width: 46px;
106 | height: 24px;
107 | margin-right: 10px;
108 | }
109 |
110 | .switch input {
111 | opacity: 0;
112 | width: 0;
113 | height: 0;
114 | }
115 |
116 | .slider {
117 | position: absolute;
118 | cursor: pointer;
119 | top: 0;
120 | left: 0;
121 | right: 0;
122 | bottom: 0;
123 | background-color: #ccc;
124 | transition: .4s;
125 | }
126 |
127 | .slider:before {
128 | position: absolute;
129 | content: "";
130 | height: 18px;
131 | width: 18px;
132 | left: 3px;
133 | bottom: 3px;
134 | background-color: white;
135 | transition: .4s;
136 | }
137 |
138 | input:checked + .slider {
139 | background-color: #2c8898;
140 | }
141 |
142 | input:focus + .slider {
143 | box-shadow: 0 0 1px #2c8898;
144 | }
145 |
146 | input:checked + .slider:before {
147 | transform: translateX(22px);
148 | }
149 |
150 | .slider.round {
151 | border-radius: 24px;
152 | }
153 |
154 | .slider.round:before {
155 | border-radius: 50%;
156 | }
157 |
158 | /* 表单样式 */
159 | .input-group {
160 | margin-bottom: 16px;
161 | display: flex;
162 | align-items: center;
163 | gap: 8px;
164 | flex-shrink: 0;
165 | }
166 |
167 | label {
168 | font-weight: 500;
169 | }
170 |
171 | input[type="text"] {
172 | flex: 1;
173 | padding: 8px;
174 | border: 1px solid #ddd;
175 | border-radius: 4px;
176 | box-sizing: border-box;
177 | font-size: 14px;
178 | }
179 |
180 | /* 选项卡样式 */
181 | .tabs {
182 | display: flex;
183 | border-bottom: 1px solid #ddd;
184 | flex-shrink: 0;
185 | }
186 |
187 | .tab {
188 | padding: 8px 16px;
189 | cursor: pointer;
190 | border-bottom: 2px solid transparent;
191 | transition: all 0.3s;
192 | color: #666;
193 | }
194 |
195 | .tab:hover {
196 | color: #333;
197 | }
198 |
199 | .tab.active {
200 | border-bottom-color: #2c8898;
201 | color: #2c8898;
202 | font-weight: 500;
203 | }
204 |
205 | .tab-content-wrapper {
206 | flex: 1;
207 | overflow-y: auto;
208 | padding-top: 16px;
209 | }
210 |
211 | /* 结果区域样式 */
212 | .result-wrapper {
213 | flex: 1;
214 | display: flex;
215 | flex-direction: column;
216 | overflow: hidden;
217 | }
218 |
219 | #result-area {
220 | border: 1px solid #ddd;
221 | border-radius: 4px;
222 | padding: 12px;
223 | background-color: #f9f9f9;
224 | flex: 1;
225 | overflow-y: auto;
226 | white-space: pre-wrap;
227 | font-family: monospace;
228 | font-size: 14px;
229 | line-height: 1.6;
230 | }
231 |
232 | /* 表格样式 */
233 | table {
234 | width: 100%;
235 | border-collapse: collapse;
236 | margin-bottom: 16px;
237 | font-size: 14px;
238 | }
239 |
240 | table, th, td {
241 | border: 1px solid #ddd;
242 | }
243 |
244 | th, td {
245 | padding: 8px;
246 | text-align: left;
247 | }
248 |
249 | th {
250 | background-color: #f2f2f2;
251 | font-weight: 500;
252 | }
253 |
254 | /* 列表样式 */
255 | ul {
256 | padding-left: 20px;
257 | }
258 |
259 | li {
260 | margin-bottom: 8px;
261 | line-height: 1.5;
262 | }
263 |
264 | code {
265 | background-color: #f0f0f0;
266 | padding: 2px 4px;
267 | border-radius: 3px;
268 | font-family: monospace;
269 | cursor: pointer;
270 | }
271 |
272 | /* 工具提示样式 */
273 | .tooltip {
274 | position: relative;
275 | display: inline-block;
276 | cursor: help;
277 | }
278 |
279 | .tooltip .tooltip-text {
280 | visibility: hidden;
281 | width: 200px;
282 | background-color: #555;
283 | color: #fff;
284 | text-align: center;
285 | border-radius: 6px;
286 | padding: 5px;
287 | position: absolute;
288 | z-index: 1;
289 | bottom: 125%;
290 | left: 50%;
291 | margin-left: -100px;
292 | opacity: 0;
293 | transition: opacity 0.3s;
294 | }
295 |
296 | .tooltip:hover .tooltip-text {
297 | visibility: visible;
298 | opacity: 1;
299 | }
--------------------------------------------------------------------------------
/js/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Cron 解析器扩展的主要 JS 文件
3 | */
4 | document.addEventListener('DOMContentLoaded', function () {
5 | // 初始化 Cron 解析器
6 | const parser = new CronParser()
7 |
8 | // 获取 DOM 元素
9 | const cronInput = document.getElementById('cron-input')
10 | const parseButton = document.getElementById('parse-button')
11 | const resultArea = document.getElementById('result-area')
12 | const formatSwitch = document.getElementById('format-switch')
13 | const formatLabel = document.getElementById('format-label')
14 | const tabs = document.querySelectorAll('.tab')
15 | const tabContents = document.querySelectorAll('.tab-content')
16 | const examplesList = document.getElementById('examples-list')
17 | const examplesListSix = document.getElementById('examples-list-six')
18 | const specialCharsList = document.getElementById('special-chars-list')
19 |
20 | // 示例数据 - 5 位格式
21 | const examples = [
22 | { expression: '*/5 * * * *', description: '每5分钟执行一次' },
23 | { expression: '0 * * * *', description: '每小时的第0分钟执行' },
24 | { expression: '0 0 * * *', description: '每天午夜执行' },
25 | { expression: '0 12 * * *', description: '每天中午12点执行' },
26 | { expression: '0 0 * * 0', description: '每周日午夜执行' },
27 | { expression: '0 0 1 * *', description: '每月1号午夜执行' },
28 | { expression: '0 0 1 1 *', description: '每年1月1日午夜执行' },
29 | { expression: '0 8-17 * * 1-5', description: '工作日的8点到17点整点执行' }
30 | ]
31 |
32 | // 示例数据 - 6 位格式
33 | const examplesSix = [
34 | { expression: '0 */5 * * * *', description: '每5分钟执行一次' },
35 | { expression: '0 0 * * * *', description: '每小时整点执行' },
36 | { expression: '0 0 0 * * *', description: '每天午夜执行' },
37 | { expression: '0 0 12 * * *', description: '每天中午12点执行' },
38 | { expression: '*/10 * * * * *', description: '每10秒执行一次' },
39 | { expression: '0 0 0 * * 0', description: '每周日午夜执行' },
40 | { expression: '0 0 0 1 * *', description: '每月1号午夜执行' },
41 | { expression: '0 0 0 1 1 *', description: '每年1月1日午夜执行' }
42 | ]
43 |
44 | // 特殊字符说明
45 | const specialChars = [
46 | { char: '*', description: '表示所有可能的值' },
47 | { char: ',', description: '用于列举值,如 "1,3,5"' },
48 | { char: '-', description: '表示范围,如 "1-5"' },
49 | { char: '/', description: '表示步长,如 "*/5" 表示每5个单位' },
50 | { char: '?', description: '用于日期和星期字段,表示不指定值' }
51 | ]
52 |
53 | // 格式切换
54 | formatSwitch.addEventListener('change', function () {
55 | const is6Field = formatSwitch.checked
56 | formatLabel.textContent = is6Field ? '6位格式(秒 分 时 日 月 周)' : '5位格式(分 时 日 月 周)'
57 |
58 | // 更新输入框占位符
59 | cronInput.placeholder = is6Field ? '例如: 0 */5 * * * *' : '例如: */5 * * * *'
60 |
61 | // 如果输入框不为空,尝试自动转换格式
62 | const expression = cronInput.value.trim()
63 | if (expression) {
64 | // 简单检测当前格式
65 | const parts = expression.split(/\s+/)
66 |
67 | if (is6Field && parts.length === 5) {
68 | // 从 5 位转换为 6 位,添加秒 "0"
69 | cronInput.value = `0 ${expression}`
70 | } else if (!is6Field && parts.length === 6) {
71 | // 从 6 位转换为 5 位,移除秒
72 | cronInput.value = parts.slice(1).join(' ')
73 | }
74 | }
75 | })
76 |
77 | // 初始化示例列表 - 5 位格式
78 | examples.forEach(example => {
79 | const li = document.createElement('li')
80 | li.innerHTML = `${example.expression}: ${example.description}`
81 | li.addEventListener('click', () => {
82 | formatSwitch.checked = false
83 | formatLabel.textContent = '5位格式(分 时 日 月 周)'
84 | cronInput.placeholder = '例如: */5 * * * *'
85 | cronInput.value = example.expression
86 | parseCron()
87 | })
88 | examplesList.appendChild(li)
89 | })
90 |
91 | // 初始化示例列表 - 6 位格式
92 | examplesSix.forEach(example => {
93 | const li = document.createElement('li')
94 | li.innerHTML = `${example.expression}: ${example.description}`
95 | li.addEventListener('click', () => {
96 | formatSwitch.checked = true
97 | formatLabel.textContent = '6位格式(秒 分 时 日 月 周)'
98 | cronInput.placeholder = '例如: 0 */5 * * * *'
99 | cronInput.value = example.expression
100 | parseCron()
101 | })
102 | examplesListSix.appendChild(li)
103 | })
104 |
105 | // 初始化特殊字符列表
106 | specialChars.forEach(item => {
107 | const li = document.createElement('li')
108 | li.innerHTML = `${item.char}: ${item.description}`
109 | specialCharsList.appendChild(li)
110 | })
111 |
112 | // 选项卡切换
113 | tabs.forEach(tab => {
114 | tab.addEventListener('click', () => {
115 | // 移除所有选项卡的活动状态
116 | tabs.forEach(t => t.classList.remove('active'))
117 | tabContents.forEach(content => content.style.display = 'none')
118 |
119 | // 设置当前选项卡为活动状态
120 | tab.classList.add('active')
121 | const tabId = tab.getAttribute('data-tab')
122 | document.getElementById(tabId).style.display = 'block'
123 | })
124 | })
125 |
126 | // 设置默认选项卡
127 | tabs[0].click()
128 |
129 | // 输入框内容变化时实时解析
130 | cronInput.addEventListener('input', parseCron)
131 |
132 | // 解析 Cron 表达式
133 | function parseCron() {
134 | const expression = cronInput.value.trim()
135 |
136 | if (!expression) {
137 | resultArea.textContent = '请输入 Cron 表达式'
138 | resultArea.classList.add('error')
139 | return
140 | }
141 |
142 | const result = parser.parse(expression)
143 |
144 | if (result.error) {
145 | resultArea.textContent = `错误: ${result.error}`
146 | resultArea.classList.add('error')
147 | return
148 | }
149 |
150 | resultArea.classList.remove('error')
151 |
152 | // 构建结果文本
153 | let resultText = `格式: ${result.format === '6-field' ? '6位(秒 分 时 日 月 周)' : '5位(分 时 日 月 周)'}\n`
154 | resultText += `${result.description}\n\n最近10次执行时间\n`
155 |
156 | result.nextDates.forEach((date, index) => {
157 | resultText += `第${index + 1}次: ${parser.formatDate(date, result.format === '6-field')}\n`
158 | })
159 |
160 | resultArea.textContent = resultText
161 | }
162 |
163 | // 页面加载后立即解析一次
164 | parseCron()
165 | })
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |