index.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <ContentWrap>
  3. <!-- 列表 -->
  4. <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
  5. <template #toolbar_buttons>
  6. <!-- 操作:新增 -->
  7. <XButton
  8. type="primary"
  9. preIcon="ep:zoom-in"
  10. :title="t('action.add')"
  11. v-hasPermi="['infra:job:create']"
  12. @click="handleCreate()"
  13. />
  14. <!-- 操作:导出 -->
  15. <XButton
  16. type="warning"
  17. preIcon="ep:download"
  18. :title="t('action.export')"
  19. v-hasPermi="['infra:job:export']"
  20. @click="handleExport()"
  21. />
  22. <XButton
  23. type="info"
  24. preIcon="ep:zoom-in"
  25. title="执行日志"
  26. v-hasPermi="['infra:job:query']"
  27. @click="handleJobLog"
  28. />
  29. </template>
  30. <template #actionbtns_default="{ row }">
  31. <!-- 操作:修改 -->
  32. <XTextButton
  33. preIcon="ep:edit"
  34. :title="t('action.edit')"
  35. v-hasPermi="['infra:job:update']"
  36. @click="handleUpdate(row.id)"
  37. />
  38. <el-button
  39. link
  40. type="primary"
  41. v-hasPermi="['infra:job:update']"
  42. @click="handleChangeStatus(row)"
  43. >
  44. <Icon icon="ep:edit" class="mr-1px" />
  45. {{ row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}
  46. </el-button>
  47. <!-- 操作:详情 -->
  48. <XTextButton
  49. preIcon="ep:view"
  50. :title="t('action.detail')"
  51. v-hasPermi="['infra:job:query']"
  52. @click="handleDetail(row.id)"
  53. />
  54. <!-- 操作:删除 -->
  55. <XTextButton
  56. preIcon="ep:delete"
  57. :title="t('action.del')"
  58. v-hasPermi="['infra:job:delete']"
  59. @click="handleDelete(row.id)"
  60. />
  61. <!-- 操作:执行 -->
  62. <XTextButton
  63. preIcon="ep:view"
  64. title="执行一次"
  65. v-hasPermi="['infra:job:trigger']"
  66. @click="handleRun(row)"
  67. />
  68. <!-- 操作:日志 -->
  69. <XTextButton
  70. preIcon="ep:view"
  71. title="调度日志"
  72. v-hasPermi="['infra:job:query']"
  73. @click="handleJobLog(row.id)"
  74. />
  75. <!-- TODO @星语:执行一次、任务详情、调度日志,可以收成【更多】 -->
  76. </template>
  77. </vxe-grid>
  78. </ContentWrap>
  79. <XModal v-model="dialogVisible" :title="dialogTitle">
  80. <!-- 对话框(添加 / 修改) -->
  81. <Form
  82. v-if="['create', 'update'].includes(actionType)"
  83. :schema="allSchemas.formSchema"
  84. :rules="rules"
  85. ref="formRef"
  86. >
  87. <template #cronExpression>
  88. <Crontab v-model="cronExpression" :shortcuts="shortcuts" />
  89. </template>
  90. </Form>
  91. <!-- 对话框(详情) -->
  92. <Descriptions
  93. v-if="actionType === 'detail'"
  94. :schema="allSchemas.detailSchema"
  95. :data="detailRef"
  96. >
  97. <template #retryInterval="{ row }">
  98. <span>{{ row.retryInterval + '毫秒' }} </span>
  99. </template>
  100. <template #monitorTimeout="{ row }">
  101. <span>{{ row.monitorTimeout > 0 ? row.monitorTimeout + ' 毫秒' : '未开启' }}</span>
  102. </template>
  103. <template #nextTimes>
  104. <span>{{ Array.from(nextTimes, (x) => parseTime(x)).join('; ') }}</span>
  105. </template>
  106. </Descriptions>
  107. <!-- 操作按钮 -->
  108. <template #footer>
  109. <!-- 按钮:保存 -->
  110. <XButton
  111. v-if="['create', 'update'].includes(actionType)"
  112. type="primary"
  113. :title="t('action.save')"
  114. :loading="actionLoading"
  115. @click="submitForm()"
  116. />
  117. <!-- 按钮:关闭 -->
  118. <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
  119. </template>
  120. </XModal>
  121. </template>
  122. <script setup lang="ts" name="Job">
  123. import { ref, unref } from 'vue'
  124. import { useRouter } from 'vue-router'
  125. import { useI18n } from '@/hooks/web/useI18n'
  126. import { useMessage } from '@/hooks/web/useMessage'
  127. import { useVxeGrid } from '@/hooks/web/useVxeGrid'
  128. import { VxeGridInstance } from 'vxe-table'
  129. import { FormExpose } from '@/components/Form'
  130. import { Crontab } from '@/components/Crontab'
  131. import * as JobApi from '@/api/infra/job'
  132. import { rules, allSchemas } from './job.data'
  133. import { InfraJobStatusEnum } from '@/utils/constants'
  134. const { t } = useI18n() // 国际化
  135. const message = useMessage() // 消息弹窗
  136. const { push } = useRouter()
  137. // 列表相关的变量
  138. const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
  139. const { gridOptions, getList, deleteData, exportList } = useVxeGrid<JobApi.JobVO>({
  140. allSchemas: allSchemas,
  141. getListApi: JobApi.getJobPageApi,
  142. deleteApi: JobApi.deleteJobApi,
  143. exportListApi: JobApi.exportJobApi
  144. })
  145. // ========== CRUD 相关 ==========
  146. const actionLoading = ref(false) // 遮罩层
  147. const actionType = ref('') // 操作按钮的类型
  148. const dialogVisible = ref(false) // 是否显示弹出层
  149. const dialogTitle = ref('edit') // 弹出层标题
  150. const formRef = ref<FormExpose>() // 表单 Ref
  151. const detailRef = ref() // 详情 Ref
  152. const cronExpression = ref('')
  153. const nextTimes = ref([])
  154. const shortcuts = ref([
  155. {
  156. text: '每天8点和12点 (自定义追加)',
  157. value: '0 0 8,12 * * ?'
  158. }
  159. ])
  160. // 设置标题
  161. const setDialogTile = (type: string) => {
  162. dialogTitle.value = t('action.' + type)
  163. actionType.value = type
  164. dialogVisible.value = true
  165. }
  166. // 新增操作
  167. const handleCreate = () => {
  168. cronExpression.value = ''
  169. setDialogTile('create')
  170. }
  171. // 导出操作
  172. const handleExport = async () => {
  173. await exportList(xGrid, '定时任务.xls')
  174. }
  175. // 修改操作
  176. const handleUpdate = async (rowId: number) => {
  177. setDialogTile('update')
  178. // 设置数据
  179. const res = await JobApi.getJobApi(rowId)
  180. cronExpression.value = res.cronExpression
  181. unref(formRef)?.setValues(res)
  182. }
  183. // 详情操作
  184. const handleDetail = async (rowId: number) => {
  185. // 设置数据
  186. const res = await JobApi.getJobApi(rowId)
  187. detailRef.value = res
  188. // 后续执行时长
  189. const jobNextTime = await JobApi.getJobNextTimesApi(rowId)
  190. nextTimes.value = jobNextTime
  191. setDialogTile('detail')
  192. }
  193. const parseTime = (time) => {
  194. if (!time) {
  195. return null
  196. }
  197. const format = '{y}-{m}-{d} {h}:{i}:{s}'
  198. let date
  199. if (typeof time === 'object') {
  200. date = time
  201. } else {
  202. if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
  203. time = parseInt(time)
  204. } else if (typeof time === 'string') {
  205. time = time
  206. .replace(new RegExp(/-/gm), '/')
  207. .replace('T', ' ')
  208. .replace(new RegExp(/\.[\d]{3}/gm), '')
  209. }
  210. if (typeof time === 'number' && time.toString().length === 10) {
  211. time = time * 1000
  212. }
  213. date = new Date(time)
  214. }
  215. const formatObj = {
  216. y: date.getFullYear(),
  217. m: date.getMonth() + 1,
  218. d: date.getDate(),
  219. h: date.getHours(),
  220. i: date.getMinutes(),
  221. s: date.getSeconds(),
  222. a: date.getDay()
  223. }
  224. const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
  225. let value = formatObj[key]
  226. // Note: getDay() returns 0 on Sunday
  227. if (key === 'a') {
  228. return ['日', '一', '二', '三', '四', '五', '六'][value]
  229. }
  230. if (result.length > 0 && value < 10) {
  231. value = '0' + value
  232. }
  233. return value || 0
  234. })
  235. return time_str
  236. }
  237. // 删除操作
  238. const handleDelete = async (rowId: number) => {
  239. await deleteData(xGrid, rowId)
  240. }
  241. const handleChangeStatus = async (row: JobApi.JobVO) => {
  242. const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭'
  243. const status =
  244. row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP
  245. message
  246. .confirm('确认要' + text + '定时任务编号为"' + row.id + '"的数据项?', t('common.reminder'))
  247. .then(async () => {
  248. row.status =
  249. row.status === InfraJobStatusEnum.NORMAL
  250. ? InfraJobStatusEnum.NORMAL
  251. : InfraJobStatusEnum.STOP
  252. await JobApi.updateJobStatusApi(row.id, status)
  253. message.success(text + '成功')
  254. await getList(xGrid)
  255. })
  256. .catch(() => {
  257. row.status =
  258. row.status === InfraJobStatusEnum.NORMAL
  259. ? InfraJobStatusEnum.STOP
  260. : InfraJobStatusEnum.NORMAL
  261. })
  262. }
  263. // 执行日志
  264. const handleJobLog = (rowId: number) => {
  265. if (rowId) {
  266. push('/job/job-log?id=' + rowId)
  267. } else {
  268. push('/job/job-log')
  269. }
  270. }
  271. // 执行一次
  272. const handleRun = (row: JobApi.JobVO) => {
  273. message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder')).then(async () => {
  274. await JobApi.runJobApi(row.id)
  275. message.success('执行成功')
  276. await getList(xGrid)
  277. })
  278. }
  279. // 提交按钮
  280. const submitForm = async () => {
  281. const elForm = unref(formRef)?.getElFormRef()
  282. if (!elForm) return
  283. elForm.validate(async (valid) => {
  284. if (valid) {
  285. actionLoading.value = true
  286. // 提交请求
  287. try {
  288. const data = unref(formRef)?.formModel as JobApi.JobVO
  289. data.cronExpression = cronExpression.value
  290. if (actionType.value === 'create') {
  291. await JobApi.createJobApi(data)
  292. message.success(t('common.createSuccess'))
  293. } else {
  294. await JobApi.updateJobApi(data)
  295. message.success(t('common.updateSuccess'))
  296. }
  297. dialogVisible.value = false
  298. } finally {
  299. actionLoading.value = false
  300. await getList(xGrid)
  301. }
  302. }
  303. })
  304. }
  305. </script>