CycleConfig.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <template>
  2. <el-tabs v-model="tab">
  3. <el-tab-pane label="CRON表达式" name="cron">
  4. <div style="margin-bottom: 10px">
  5. <el-input
  6. v-model="cronStr"
  7. readonly
  8. style="width: 400px; font-weight: bold"
  9. :key="'cronStr'"
  10. />
  11. </div>
  12. <div style="display: flex; gap: 8px; margin-bottom: 8px">
  13. <el-input v-model="fields.second" placeholder="秒" style="width: 80px" :key="'second'" />
  14. <el-input v-model="fields.minute" placeholder="分" style="width: 80px" :key="'minute'" />
  15. <el-input v-model="fields.hour" placeholder="时" style="width: 80px" :key="'hour'" />
  16. <el-input v-model="fields.day" placeholder="天" style="width: 80px" :key="'day'" />
  17. <el-input v-model="fields.month" placeholder="月" style="width: 80px" :key="'month'" />
  18. <el-input v-model="fields.week" placeholder="周" style="width: 80px" :key="'week'" />
  19. <el-input v-model="fields.year" placeholder="年" style="width: 80px" :key="'year'" />
  20. </div>
  21. <el-tabs v-model="activeField" type="card" style="margin-bottom: 8px">
  22. <el-tab-pane v-for="f in cronFieldList" :label="f.label" :name="f.key" :key="f.key">
  23. <div style="margin-bottom: 8px">
  24. <el-radio-group v-model="cronMode[f.key]" :key="'radio-' + f.key">
  25. <el-radio label="every" :key="'every-' + f.key">每{{ f.label }}</el-radio>
  26. <el-radio label="range" :key="'range-' + f.key"
  27. >从
  28. <el-input-number
  29. v-model="cronRange[f.key][0]"
  30. :min="f.min"
  31. :max="f.max"
  32. size="small"
  33. style="width: 60px"
  34. :key="'range0-' + f.key"
  35. />
  36. <el-input-number
  37. v-model="cronRange[f.key][1]"
  38. :min="f.min"
  39. :max="f.max"
  40. size="small"
  41. style="width: 60px"
  42. :key="'range1-' + f.key"
  43. />
  44. 之间每{{ f.label }}</el-radio
  45. >
  46. <el-radio label="step" :key="'step-' + f.key"
  47. >从第
  48. <el-input-number
  49. v-model="cronStep[f.key][0]"
  50. :min="f.min"
  51. :max="f.max"
  52. size="small"
  53. style="width: 60px"
  54. :key="'step0-' + f.key"
  55. />
  56. 开始每
  57. <el-input-number
  58. v-model="cronStep[f.key][1]"
  59. :min="1"
  60. :max="f.max"
  61. size="small"
  62. style="width: 60px"
  63. :key="'step1-' + f.key"
  64. />
  65. {{ f.label }}</el-radio
  66. >
  67. <el-radio label="appoint" :key="'appoint-' + f.key">指定</el-radio>
  68. </el-radio-group>
  69. </div>
  70. <div v-if="cronMode[f.key] === 'appoint'">
  71. <el-checkbox-group v-model="cronAppoint[f.key]" :key="'group-' + f.key">
  72. <el-checkbox
  73. v-for="n in f.max + 1"
  74. :label="pad(n - 1)"
  75. :key="'cb-' + f.key + '-' + (n - 1)"
  76. >{{ pad(n - 1) }}</el-checkbox
  77. >
  78. </el-checkbox-group>
  79. </div>
  80. </el-tab-pane>
  81. </el-tabs>
  82. </el-tab-pane>
  83. <el-tab-pane label="标准格式" name="iso" :key="'iso-tab'">
  84. <div style="margin-bottom: 10px">
  85. <el-input
  86. v-model="isoStr"
  87. placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
  88. style="width: 400px; font-weight: bold"
  89. :key="'isoStr'"
  90. />
  91. </div>
  92. <div style="margin-bottom: 10px"
  93. >循环次数:<el-input-number v-model="repeat" :min="1" style="width: 100px" :key="'repeat'"
  94. /></div>
  95. <div style="margin-bottom: 10px"
  96. >日期时间:<el-date-picker
  97. v-model="isoDate"
  98. type="datetime"
  99. placeholder="选择日期时间"
  100. style="width: 200px"
  101. :key="'isoDate'"
  102. /></div>
  103. <div style="margin-bottom: 10px"
  104. >当前时长:<el-input
  105. v-model="isoDuration"
  106. placeholder="如P3DT30M30S"
  107. style="width: 200px"
  108. :key="'isoDuration'"
  109. /></div>
  110. <div>
  111. <div
  112. >秒:<el-button
  113. v-for="s in [5, 10, 30, 50]"
  114. @click="setDuration('S', s)"
  115. :key="'sec-' + s"
  116. >{{ s }}</el-button
  117. >自定义</div
  118. >
  119. <div
  120. >分:<el-button
  121. v-for="m in [5, 10, 30, 50]"
  122. @click="setDuration('M', m)"
  123. :key="'min-' + m"
  124. >{{ m }}</el-button
  125. >自定义</div
  126. >
  127. <div
  128. >小时:<el-button
  129. v-for="h in [4, 8, 12, 24]"
  130. @click="setDuration('H', h)"
  131. :key="'hour-' + h"
  132. >{{ h }}</el-button
  133. >自定义</div
  134. >
  135. <div
  136. >天:<el-button
  137. v-for="d in [1, 2, 3, 4]"
  138. @click="setDuration('D', d)"
  139. :key="'day-' + d"
  140. >{{ d }}</el-button
  141. >自定义</div
  142. >
  143. <div
  144. >月:<el-button
  145. v-for="mo in [1, 2, 3, 4]"
  146. @click="setDuration('M', mo)"
  147. :key="'mon-' + mo"
  148. >{{ mo }}</el-button
  149. >自定义</div
  150. >
  151. <div
  152. >年:<el-button
  153. v-for="y in [1, 2, 3, 4]"
  154. @click="setDuration('Y', y)"
  155. :key="'year-' + y"
  156. >{{ y }}</el-button
  157. >自定义</div
  158. >
  159. </div>
  160. </el-tab-pane>
  161. </el-tabs>
  162. </template>
  163. <script setup>
  164. import { ref, watch, computed } from 'vue'
  165. const props = defineProps({ value: String })
  166. const emit = defineEmits(['change'])
  167. const tab = ref('cron')
  168. const cronStr = ref(props.value || '* * * * * ?')
  169. const fields = ref({
  170. second: '*',
  171. minute: '*',
  172. hour: '*',
  173. day: '*',
  174. month: '*',
  175. week: '?',
  176. year: ''
  177. })
  178. const cronFieldList = [
  179. { key: 'second', label: '秒', min: 0, max: 59 },
  180. { key: 'minute', label: '分', min: 0, max: 59 },
  181. { key: 'hour', label: '时', min: 0, max: 23 },
  182. { key: 'day', label: '天', min: 1, max: 31 },
  183. { key: 'month', label: '月', min: 1, max: 12 },
  184. { key: 'week', label: '周', min: 1, max: 7 },
  185. { key: 'year', label: '年', min: 1970, max: 2099 }
  186. ]
  187. const activeField = ref('second')
  188. const cronMode = ref({
  189. second: 'appoint',
  190. minute: 'every',
  191. hour: 'every',
  192. day: 'every',
  193. month: 'every',
  194. week: 'every',
  195. year: 'every'
  196. })
  197. const cronAppoint = ref({
  198. second: ['00', '01'],
  199. minute: [],
  200. hour: [],
  201. day: [],
  202. month: [],
  203. week: [],
  204. year: []
  205. })
  206. const cronRange = ref({
  207. second: [0, 1],
  208. minute: [0, 1],
  209. hour: [0, 1],
  210. day: [1, 2],
  211. month: [1, 2],
  212. week: [1, 2],
  213. year: [1970, 1971]
  214. })
  215. const cronStep = ref({
  216. second: [1, 1],
  217. minute: [1, 1],
  218. hour: [1, 1],
  219. day: [1, 1],
  220. month: [1, 1],
  221. week: [1, 1],
  222. year: [1970, 1]
  223. })
  224. function pad(n) {
  225. return n < 10 ? '0' + n : '' + n
  226. }
  227. watch(
  228. [fields, cronMode, cronAppoint, cronRange, cronStep],
  229. () => {
  230. // 组装cron表达式
  231. let arr = cronFieldList.map((f) => {
  232. if (cronMode.value[f.key] === 'every') return '*'
  233. if (cronMode.value[f.key] === 'appoint') return cronAppoint.value[f.key].join(',') || '*'
  234. if (cronMode.value[f.key] === 'range')
  235. return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`
  236. if (cronMode.value[f.key] === 'step')
  237. return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`
  238. return fields.value[f.key] || '*'
  239. })
  240. // week和year特殊处理
  241. arr[5] = arr[5] || '?'
  242. cronStr.value = arr.join(' ')
  243. if (tab.value === 'cron') emit('change', cronStr.value)
  244. },
  245. { deep: true }
  246. )
  247. // 标准格式
  248. const isoStr = ref('')
  249. const repeat = ref(1)
  250. const isoDate = ref('')
  251. const isoDuration = ref('')
  252. function setDuration(type, val) {
  253. // 组装ISO 8601字符串
  254. let d = isoDuration.value
  255. if (!d.includes(type)) d += val + type
  256. else d = d.replace(new RegExp(`\\d+${type}`), val + type)
  257. isoDuration.value = d
  258. updateIsoStr()
  259. }
  260. function updateIsoStr() {
  261. let str = `R${repeat.value}`
  262. if (isoDate.value)
  263. str +=
  264. '/' +
  265. (typeof isoDate.value === 'string' ? isoDate.value : new Date(isoDate.value).toISOString())
  266. if (isoDuration.value) str += '/' + isoDuration.value
  267. isoStr.value = str
  268. if (tab.value === 'iso') emit('change', isoStr.value)
  269. }
  270. watch([repeat, isoDate, isoDuration], updateIsoStr)
  271. watch(
  272. () => props.value,
  273. (val) => {
  274. if (!val) return
  275. if (tab.value === 'cron') cronStr.value = val
  276. if (tab.value === 'iso') isoStr.value = val
  277. },
  278. { immediate: true }
  279. )
  280. </script>