Commit 78d0eebbc60c2a0cd5f94309064bfae0052c0873
Merge branch 'f-dev' into 'main'
feat:新增Cron组件 wip:报表功能开发中...... See merge request huang/yun-teng-iot-front!272
Showing
47 changed files
with
2722 additions
and
175 deletions
Too many changes to show.
To preserve performance only 47 of 48 files are displayed.
| @@ -2,21 +2,68 @@ | @@ -2,21 +2,68 @@ | ||
| 2 | * Introduces component library styles on demand. | 2 | * Introduces component library styles on demand. |
| 3 | * https://github.com/anncwb/vite-plugin-style-import | 3 | * https://github.com/anncwb/vite-plugin-style-import |
| 4 | */ | 4 | */ |
| 5 | + import styleImport from 'vite-plugin-style-import'; | ||
| 5 | 6 | ||
| 6 | -import styleImport from 'vite-plugin-style-import'; | ||
| 7 | - | ||
| 8 | -export function configStyleImportPlugin(isBuild: boolean) { | ||
| 9 | - if (!isBuild) return []; | ||
| 10 | - const styleImportPlugin = styleImport({ | ||
| 11 | - libs: [ | ||
| 12 | - { | ||
| 13 | - libraryName: 'ant-design-vue', | ||
| 14 | - esModule: true, | ||
| 15 | - resolveStyle: (name) => { | ||
| 16 | - return `ant-design-vue/es/${name}/style/index`; | ||
| 17 | - }, | ||
| 18 | - }, | ||
| 19 | - ], | ||
| 20 | - }); | ||
| 21 | - return styleImportPlugin; | ||
| 22 | -} | 7 | + export function configStyleImportPlugin(isBuild: boolean) { |
| 8 | + if (!isBuild) { | ||
| 9 | + return []; | ||
| 10 | + } | ||
| 11 | + const styleImportPlugin = styleImport({ | ||
| 12 | + libs: [ | ||
| 13 | + { | ||
| 14 | + libraryName: 'ant-design-vue', | ||
| 15 | + esModule: true, | ||
| 16 | + resolveStyle: (name) => { | ||
| 17 | + // 这里是“子组件”列表,无需额外引入样式文件 | ||
| 18 | + const ignoreList = [ | ||
| 19 | + 'typography-text', | ||
| 20 | + 'typography-title', | ||
| 21 | + 'typography-paragraph', | ||
| 22 | + 'typography-link', | ||
| 23 | + 'anchor-link', | ||
| 24 | + 'sub-menu', | ||
| 25 | + 'menu-item', | ||
| 26 | + 'menu-item-group', | ||
| 27 | + 'dropdown-button', | ||
| 28 | + 'breadcrumb-item', | ||
| 29 | + 'breadcrumb-separator', | ||
| 30 | + 'input-password', | ||
| 31 | + 'input-search', | ||
| 32 | + 'input-group', | ||
| 33 | + 'form-item', | ||
| 34 | + 'radio-group', | ||
| 35 | + 'checkbox-group', | ||
| 36 | + 'layout-sider', | ||
| 37 | + 'layout-content', | ||
| 38 | + 'layout-footer', | ||
| 39 | + 'layout-header', | ||
| 40 | + 'step', | ||
| 41 | + 'select-option', | ||
| 42 | + 'select-opt-group', | ||
| 43 | + 'card-grid', | ||
| 44 | + 'card-meta', | ||
| 45 | + 'collapse-panel', | ||
| 46 | + 'descriptions-item', | ||
| 47 | + 'list-item', | ||
| 48 | + 'list-item-meta', | ||
| 49 | + 'table-column', | ||
| 50 | + 'table-column-group', | ||
| 51 | + 'tab-pane', | ||
| 52 | + 'tab-content', | ||
| 53 | + 'timeline-item', | ||
| 54 | + 'tree-node', | ||
| 55 | + 'skeleton-input', | ||
| 56 | + 'skeleton-avatar', | ||
| 57 | + 'skeleton-title', | ||
| 58 | + 'skeleton-paragraph', | ||
| 59 | + 'skeleton-image', | ||
| 60 | + 'skeleton-button', | ||
| 61 | + ]; | ||
| 62 | + return ignoreList.includes(name) ? '' : `ant-design-vue/es/${name}/style/index`; | ||
| 63 | + }, | ||
| 64 | + }, | ||
| 65 | + ], | ||
| 66 | + }); | ||
| 67 | + return styleImportPlugin; | ||
| 68 | + } | ||
| 69 | + |
| @@ -43,6 +43,7 @@ | @@ -43,6 +43,7 @@ | ||
| 43 | "ant-design-vue": "2.2.6", | 43 | "ant-design-vue": "2.2.6", |
| 44 | "axios": "^0.21.1", | 44 | "axios": "^0.21.1", |
| 45 | "codemirror": "^5.62.2", | 45 | "codemirror": "^5.62.2", |
| 46 | + "cron-parser": "3.5.0", | ||
| 46 | "cropperjs": "^1.5.12", | 47 | "cropperjs": "^1.5.12", |
| 47 | "crypto-js": "^4.1.1", | 48 | "crypto-js": "^4.1.1", |
| 48 | "echarts": "^5.1.2", | 49 | "echarts": "^5.1.2", |
src/api/schedue/model/schedueModel.ts
0 → 100644
| 1 | +import { BasicPageParams } from '/@/api/model/baseModel'; | ||
| 2 | +export type ReportQueryParam = BasicPageParams & SchedueParam; | ||
| 3 | + | ||
| 4 | +export type SchedueParam = { | ||
| 5 | + status: true; | ||
| 6 | + jobName: string; | ||
| 7 | + jobGroup: string; | ||
| 8 | + orderFiled: string; | ||
| 9 | + orderType: string; | ||
| 10 | + data?: any; | ||
| 11 | + code?: number; | ||
| 12 | +}; | ||
| 13 | + | ||
| 14 | +export interface ReportModel { | ||
| 15 | + code?: number; | ||
| 16 | + concurrent: number; | ||
| 17 | + createTime: string; | ||
| 18 | + creator: string; | ||
| 19 | + cronExpression: string; | ||
| 20 | + defaultConfig: string; | ||
| 21 | + description: string; | ||
| 22 | + enabled: true; | ||
| 23 | + icon: string; | ||
| 24 | + id: string; | ||
| 25 | + invokeTarget: string; | ||
| 26 | + jobGroup: string; | ||
| 27 | + jobName: string; | ||
| 28 | + misfirePolicy: number; | ||
| 29 | + name: string; | ||
| 30 | + roleIds: [string]; | ||
| 31 | + status: number; | ||
| 32 | + tenantExpireTime: string; | ||
| 33 | + tenantId: string; | ||
| 34 | + tenantProfileId: string; | ||
| 35 | + tenantStatus: string; | ||
| 36 | + updateTime: string; | ||
| 37 | + updater: string; | ||
| 38 | +} |
src/api/schedue/schedueManager.ts
0 → 100644
| 1 | +import { defHttp } from '/@/utils/http/axios'; | ||
| 2 | +import { ReportModel, ReportQueryParam } from './model/schedueModel'; | ||
| 3 | + | ||
| 4 | +enum ReportManagerApi { | ||
| 5 | + GET_REPORT_API = '/monitor/job/page', | ||
| 6 | + POST_REPORT_API = '/monitor/job/add', | ||
| 7 | + DELETE_REPORT_API = '/monitor/job', | ||
| 8 | + PUT_REPORT_API = '/monitor/job/update', | ||
| 9 | + PUTID_REPORT_API = '/monitor/job', | ||
| 10 | + RUN_REPORT_API = '/monitor/job/run', | ||
| 11 | + JOB_LOG_DETAIL_API = '/monitor/jobLog/get/', | ||
| 12 | + JOB_LOG_PAGE_API = '/monitor/jobLog/page', | ||
| 13 | + DELETE_LOG_API = '/monitor/jobLog', | ||
| 14 | + POST_LOG_CLEAN_API = '/monitor/jobLog/clean', | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +//分页 | ||
| 18 | +export const scheduePage = (params: ReportQueryParam) => { | ||
| 19 | + return defHttp.get<ReportQueryParam>({ | ||
| 20 | + url: ReportManagerApi.GET_REPORT_API, | ||
| 21 | + params, | ||
| 22 | + }); | ||
| 23 | +}; | ||
| 24 | + | ||
| 25 | +//删除 | ||
| 26 | +export const deleteSchedueManage = (ids: string[]) => { | ||
| 27 | + return defHttp.delete({ | ||
| 28 | + url: ReportManagerApi.DELETE_REPORT_API, | ||
| 29 | + data: { | ||
| 30 | + ids: ids, | ||
| 31 | + }, | ||
| 32 | + }); | ||
| 33 | +}; | ||
| 34 | + | ||
| 35 | +// 创建 | ||
| 36 | +export const createOrEditSchedueManage = (data) => { | ||
| 37 | + return defHttp.post<ReportModel>({ | ||
| 38 | + url: ReportManagerApi.POST_REPORT_API, | ||
| 39 | + data, | ||
| 40 | + }); | ||
| 41 | +}; | ||
| 42 | + | ||
| 43 | +// 编辑 | ||
| 44 | +export const putSchedueConfigManage = (data) => { | ||
| 45 | + return defHttp.post<ReportModel>({ | ||
| 46 | + url: ReportManagerApi.PUT_REPORT_API, | ||
| 47 | + data, | ||
| 48 | + }); | ||
| 49 | +}; | ||
| 50 | + | ||
| 51 | +// 修改状态 id status | ||
| 52 | +export const putSchedueByidAndStatusManage = (id, status) => { | ||
| 53 | + return defHttp.put<ReportModel>({ | ||
| 54 | + url: ReportManagerApi.PUTID_REPORT_API + '/changeStatus/' + id + '/' + status, | ||
| 55 | + }); | ||
| 56 | +}; | ||
| 57 | +//执行一次 | ||
| 58 | +export const postRunSchedueConfigManage = (id) => { | ||
| 59 | + return defHttp.post<ReportModel>({ | ||
| 60 | + url: ReportManagerApi.RUN_REPORT_API+'/'+id, | ||
| 61 | + }); | ||
| 62 | +}; | ||
| 63 | + | ||
| 64 | +/** | ||
| 65 | + * 调度日志 | ||
| 66 | + */ | ||
| 67 | + | ||
| 68 | +//分页 | ||
| 69 | +export const schedueLogPage = (params: ReportQueryParam) => { | ||
| 70 | + return defHttp.get<ReportQueryParam>({ | ||
| 71 | + url: ReportManagerApi.JOB_LOG_PAGE_API, | ||
| 72 | + params, | ||
| 73 | + }); | ||
| 74 | +}; | ||
| 75 | + | ||
| 76 | +//删除 | ||
| 77 | +export const deleteSchedueLogManage = (ids: string[]) => { | ||
| 78 | + return defHttp.delete({ | ||
| 79 | + url: ReportManagerApi.DELETE_LOG_API, | ||
| 80 | + data: { | ||
| 81 | + ids: ids, | ||
| 82 | + }, | ||
| 83 | + }); | ||
| 84 | +}; | ||
| 85 | + | ||
| 86 | +// 清空 | ||
| 87 | +export const schedueLogCleanPage = () => { | ||
| 88 | + return defHttp.post<ReportModel>({ | ||
| 89 | + url: ReportManagerApi.POST_LOG_CLEAN_API, | ||
| 90 | + }); | ||
| 91 | +}; | ||
| 92 | + | ||
| 93 | +//详情 | ||
| 94 | +export const schedueLogDetailPage = (jobId, id) => { | ||
| 95 | + return defHttp.get<ReportQueryParam>({ | ||
| 96 | + url: ReportManagerApi.JOB_LOG_DETAIL_API + '/' + jobId + '/' + id, | ||
| 97 | + }); | ||
| 98 | +}; |
| @@ -11,5 +11,12 @@ export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.v | @@ -11,5 +11,12 @@ export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.v | ||
| 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; | 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; |
| 12 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | 12 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; |
| 13 | 13 | ||
| 14 | - | 14 | +//注册自定义组件 |
| 15 | +export { | ||
| 16 | + JEasyCron, | ||
| 17 | + JEasyCronInner, | ||
| 18 | + JEasyCronModal, | ||
| 19 | +} from '/@/components/Form/src/jeecg/components/JEasyCron'; | ||
| 20 | +// Jeecg自定义校验 | ||
| 21 | +export { JCronValidator } from '/@/components/Form/src/jeecg/components/JEasyCron'; | ||
| 15 | export { BasicForm }; | 22 | export { BasicForm }; |
| @@ -30,6 +30,7 @@ import { CountdownInput } from '/@/components/CountDown'; | @@ -30,6 +30,7 @@ import { CountdownInput } from '/@/components/CountDown'; | ||
| 30 | import ApiRadioGroup from './components/ApiRadioGroup.vue'; | 30 | import ApiRadioGroup from './components/ApiRadioGroup.vue'; |
| 31 | //自定义组件 | 31 | //自定义组件 |
| 32 | import JAddInput from './jeecg/components/JAddInput.vue'; | 32 | import JAddInput from './jeecg/components/JAddInput.vue'; |
| 33 | +import { JEasyCron } from './jeecg/components/JEasyCron'; | ||
| 33 | 34 | ||
| 34 | const componentMap = new Map<ComponentType, Component>(); | 35 | const componentMap = new Map<ComponentType, Component>(); |
| 35 | 36 | ||
| @@ -67,6 +68,7 @@ componentMap.set('InputCountDown', CountdownInput); | @@ -67,6 +68,7 @@ componentMap.set('InputCountDown', CountdownInput); | ||
| 67 | componentMap.set('Upload', BasicUpload); | 68 | componentMap.set('Upload', BasicUpload); |
| 68 | //注册自定义组件 | 69 | //注册自定义组件 |
| 69 | componentMap.set('JAddInput', JAddInput); | 70 | componentMap.set('JAddInput', JAddInput); |
| 71 | +componentMap.set('JEasyCron', JEasyCron); | ||
| 70 | 72 | ||
| 71 | export function add(compName: ComponentType, component: Component) { | 73 | export function add(compName: ComponentType, component: Component) { |
| 72 | componentMap.set(compName, component); | 74 | componentMap.set(compName, component); |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}`"> | ||
| 3 | + <div class="content"> | ||
| 4 | + <Tabs :size="`small`" v-model:activeKey="activeKey"> | ||
| 5 | + <TabPane tab="秒" key="second" v-if="!hideSecond"> | ||
| 6 | + <SecondUI v-model:value="second" :disabled="disabled" /> | ||
| 7 | + </TabPane> | ||
| 8 | + <TabPane tab="分" key="minute"> | ||
| 9 | + <MinuteUI v-model:value="minute" :disabled="disabled" /> | ||
| 10 | + </TabPane> | ||
| 11 | + <TabPane tab="时" key="hour"> | ||
| 12 | + <HourUI v-model:value="hour" :disabled="disabled" /> | ||
| 13 | + </TabPane> | ||
| 14 | + <TabPane tab="日" key="day"> | ||
| 15 | + <DayUI v-model:value="day" :week="week" :disabled="disabled" /> | ||
| 16 | + </TabPane> | ||
| 17 | + <TabPane tab="月" key="month"> | ||
| 18 | + <MonthUI v-model:value="month" :disabled="disabled" /> | ||
| 19 | + </TabPane> | ||
| 20 | + <TabPane tab="周" key="week"> | ||
| 21 | + <WeekUI v-model:value="week" :day="day" :disabled="disabled" /> | ||
| 22 | + </TabPane> | ||
| 23 | + <TabPane tab="年" key="year" v-if="!hideYear && !hideSecond"> | ||
| 24 | + <YearUI v-model:value="year" :disabled="disabled" /> | ||
| 25 | + </TabPane> | ||
| 26 | + </Tabs> | ||
| 27 | + <Divider /> | ||
| 28 | + <!-- 执行时间预览 --> | ||
| 29 | + <Row :gutter="8"> | ||
| 30 | + <Col :span="18" style="margin-top: 22px"> | ||
| 31 | + <Row :gutter="8"> | ||
| 32 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 33 | + <Input v-model:value="inputValues.second" @blur="onInputBlur"> | ||
| 34 | + <template #addonBefore> | ||
| 35 | + <span class="allow-click" @click="activeKey = 'second'">秒</span> | ||
| 36 | + </template> | ||
| 37 | + </Input> | ||
| 38 | + </Col> | ||
| 39 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 40 | + <Input v-model:value="inputValues.minute" @blur="onInputBlur"> | ||
| 41 | + <template #addonBefore> | ||
| 42 | + <span class="allow-click" @click="activeKey = 'minute'">分</span> | ||
| 43 | + </template> | ||
| 44 | + </Input> | ||
| 45 | + </Col> | ||
| 46 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 47 | + <Input v-model:value="inputValues.hour" @blur="onInputBlur"> | ||
| 48 | + <template #addonBefore> | ||
| 49 | + <span class="allow-click" @click="activeKey = 'hour'">时</span> | ||
| 50 | + </template> | ||
| 51 | + </Input> | ||
| 52 | + </Col> | ||
| 53 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 54 | + <Input v-model:value="inputValues.day" @blur="onInputBlur"> | ||
| 55 | + <template #addonBefore> | ||
| 56 | + <span class="allow-click" @click="activeKey = 'day'">日</span> | ||
| 57 | + </template> | ||
| 58 | + </Input> | ||
| 59 | + </Col> | ||
| 60 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 61 | + <Input v-model:value="inputValues.month" @blur="onInputBlur"> | ||
| 62 | + <template #addonBefore> | ||
| 63 | + <span class="allow-click" @click="activeKey = 'month'">月</span> | ||
| 64 | + </template> | ||
| 65 | + </Input> | ||
| 66 | + </Col> | ||
| 67 | + <Col :span="8" style="margin-bottom: 12px"> | ||
| 68 | + <Input v-model:value="inputValues.week" @blur="onInputBlur"> | ||
| 69 | + <template #addonBefore> | ||
| 70 | + <span class="allow-click" @click="activeKey = 'week'">周</span> | ||
| 71 | + </template> | ||
| 72 | + </Input> | ||
| 73 | + </Col> | ||
| 74 | + <Col :span="8"> | ||
| 75 | + <Input v-model:value="inputValues.year" @blur="onInputBlur"> | ||
| 76 | + <template #addonBefore> | ||
| 77 | + <span class="allow-click" @click="activeKey = 'year'">年</span> | ||
| 78 | + </template> | ||
| 79 | + </Input> | ||
| 80 | + </Col> | ||
| 81 | + <Col :span="16"> | ||
| 82 | + <Input v-model:value="inputValues.cron" @blur="onInputCronBlur"> | ||
| 83 | + <template #addonBefore> | ||
| 84 | + <Tooltip title="Cron表达式">式</Tooltip> | ||
| 85 | + </template> | ||
| 86 | + </Input> | ||
| 87 | + </Col> | ||
| 88 | + </Row> | ||
| 89 | + </Col> | ||
| 90 | + <Col :span="6"> | ||
| 91 | + <div>近十次执行时间(不含年)</div> | ||
| 92 | + <a-textarea type="textarea" :value="preTimeList" :rows="5" /> | ||
| 93 | + </Col> | ||
| 94 | + </Row> | ||
| 95 | + </div> | ||
| 96 | + </div> | ||
| 97 | +</template> | ||
| 98 | + | ||
| 99 | +<script lang="ts" setup> | ||
| 100 | + import { computed, reactive, ref, watch, provide } from 'vue'; | ||
| 101 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
| 102 | + import CronParser from 'cron-parser'; | ||
| 103 | + import SecondUI from './tabs/SecondUI.vue'; | ||
| 104 | + import MinuteUI from './tabs/MinuteUI.vue'; | ||
| 105 | + import HourUI from './tabs/HourUI.vue'; | ||
| 106 | + import DayUI from './tabs/DayUI.vue'; | ||
| 107 | + import MonthUI from './tabs/MonthUI.vue'; | ||
| 108 | + import WeekUI from './tabs/WeekUI.vue'; | ||
| 109 | + import YearUI from './tabs/YearUI.vue'; | ||
| 110 | + import { cronEmits, cronProps } from './easy.cron.data'; | ||
| 111 | + import { dateFormat, simpleDebounce } from '/@/utils/common/compUtils'; | ||
| 112 | + import { Row, Col, Input, Tabs, Divider, Tooltip } from 'ant-design-vue'; | ||
| 113 | + const { TabPane } = Tabs; | ||
| 114 | + const { prefixCls } = useDesign('easy-cron-inner'); | ||
| 115 | + provide('prefixCls', prefixCls); | ||
| 116 | + const emit = defineEmits([...cronEmits]); | ||
| 117 | + const props = defineProps({ ...cronProps }); | ||
| 118 | + const activeKey = ref(props.hideSecond ? 'minute' : 'second'); | ||
| 119 | + const second = ref('*'); | ||
| 120 | + const minute = ref('*'); | ||
| 121 | + const hour = ref('*'); | ||
| 122 | + const day = ref('*'); | ||
| 123 | + const month = ref('*'); | ||
| 124 | + const week = ref('?'); | ||
| 125 | + const year = ref('*'); | ||
| 126 | + const inputValues = reactive({ | ||
| 127 | + second: '', | ||
| 128 | + minute: '', | ||
| 129 | + hour: '', | ||
| 130 | + day: '', | ||
| 131 | + month: '', | ||
| 132 | + week: '', | ||
| 133 | + year: '', | ||
| 134 | + cron: '', | ||
| 135 | + }); | ||
| 136 | + const preTimeList = ref('执行预览,会忽略年份参数。'); | ||
| 137 | + | ||
| 138 | + //fix 去除字符串里的'W' 当选择工作日 会自动在后面加上W 比如1W 2W corn解析会报错 | ||
| 139 | + function Letter(str) { | ||
| 140 | + let result; | ||
| 141 | + let reg = /[W]+/; | ||
| 142 | + while ((result = str.match(reg))) { | ||
| 143 | + //判断str.match(reg)是否没有字母了 | ||
| 144 | + str = str.replace(result[0], ''); //替换掉字母 result[0] 是 str.match(reg)匹配到的字母 | ||
| 145 | + } | ||
| 146 | + return str; | ||
| 147 | + } | ||
| 148 | + // 最终生成的cron表达式 | ||
| 149 | + const cronValueInner = computed(() => { | ||
| 150 | + let result: string[] = []; | ||
| 151 | + if (!props.hideSecond) { | ||
| 152 | + result.push(second.value ? second.value : '*'); | ||
| 153 | + } | ||
| 154 | + result.push(minute.value ? minute.value : '*'); | ||
| 155 | + result.push(hour.value ? hour.value : '*'); | ||
| 156 | + result.push(day.value ? Letter(day.value) : '*'); | ||
| 157 | + result.push(month.value ? month.value : '*'); | ||
| 158 | + result.push(week.value ? week.value : '?'); | ||
| 159 | + if (!props.hideYear && !props.hideSecond) result.push(year.value ? year.value : '*'); | ||
| 160 | + return result.join(' '); | ||
| 161 | + }); | ||
| 162 | + // 不含年 | ||
| 163 | + const cronValueNoYear = computed(() => { | ||
| 164 | + const v = cronValueInner.value; | ||
| 165 | + if (props.hideYear || props.hideSecond) return v; | ||
| 166 | + const vs = v.split(' '); | ||
| 167 | + if (vs.length >= 6) { | ||
| 168 | + // 转成 Quartz 的规则 | ||
| 169 | + vs[5] = convertWeekToQuartz(vs[5]); | ||
| 170 | + } | ||
| 171 | + return vs.slice(0, vs.length - 1).join(' '); | ||
| 172 | + }); | ||
| 173 | + const calTriggerList = simpleDebounce(calTriggerListInner, 500); | ||
| 174 | + | ||
| 175 | + watch( | ||
| 176 | + () => props.value, | ||
| 177 | + (newVal) => { | ||
| 178 | + if (newVal === cronValueInner.value) { | ||
| 179 | + return; | ||
| 180 | + } | ||
| 181 | + formatValue(); | ||
| 182 | + } | ||
| 183 | + ); | ||
| 184 | + | ||
| 185 | + watch(cronValueInner, (newValue) => { | ||
| 186 | + calTriggerList(); | ||
| 187 | + emitValue(newValue); | ||
| 188 | + assignInput(); | ||
| 189 | + }); | ||
| 190 | + assignInput(); | ||
| 191 | + formatValue(); | ||
| 192 | + calTriggerListInner(); | ||
| 193 | + | ||
| 194 | + function assignInput() { | ||
| 195 | + inputValues.second = second.value; | ||
| 196 | + inputValues.minute = minute.value; | ||
| 197 | + inputValues.hour = hour.value; | ||
| 198 | + inputValues.day = Letter(day.value); | ||
| 199 | + inputValues.month = month.value; | ||
| 200 | + inputValues.week = week.value; | ||
| 201 | + inputValues.year = year.value; | ||
| 202 | + inputValues.cron = cronValueInner.value; | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + function formatValue() { | ||
| 206 | + if (!props.value) return; | ||
| 207 | + const values = props.value.split(' ').filter((item) => !!item); | ||
| 208 | + if (!values || values.length <= 0) return; | ||
| 209 | + let i = 0; | ||
| 210 | + if (!props.hideSecond) second.value = values[i++]; | ||
| 211 | + if (values.length > i) minute.value = values[i++]; | ||
| 212 | + if (values.length > i) hour.value = values[i++]; | ||
| 213 | + if (values.length > i) day.value = values[i++]; | ||
| 214 | + if (values.length > i) month.value = values[i++]; | ||
| 215 | + if (values.length > i) week.value = values[i++]; | ||
| 216 | + if (values.length > i) year.value = values[i]; | ||
| 217 | + assignInput(); | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + // Quartz 的规则: | ||
| 221 | + // 1 = 周日,2 = 周一,3 = 周二,4 = 周三,5 = 周四,6 = 周五,7 = 周六 | ||
| 222 | + function convertWeekToQuartz(week: string) { | ||
| 223 | + let convert = (v: string) => { | ||
| 224 | + if (v === '0') { | ||
| 225 | + return '1'; | ||
| 226 | + } | ||
| 227 | + if (v === '1') { | ||
| 228 | + return '0'; | ||
| 229 | + } | ||
| 230 | + return (Number.parseInt(v) - 1).toString(); | ||
| 231 | + }; | ||
| 232 | + // 匹配示例 1-7 or 1/7 | ||
| 233 | + let patten1 = /^([0-7])([-/])([0-7])$/; | ||
| 234 | + // 匹配示例 1,4,7 | ||
| 235 | + let patten2 = /^([0-7])(,[0-7])+$/; | ||
| 236 | + if (/^[0-7]$/.test(week)) { | ||
| 237 | + return convert(week); | ||
| 238 | + } else if (patten1.test(week)) { | ||
| 239 | + return week.replace(patten1, ($0, before, separator, after) => { | ||
| 240 | + if (separator === '/') { | ||
| 241 | + return convert(before) + separator + after; | ||
| 242 | + } else { | ||
| 243 | + return convert(before) + separator + convert(after); | ||
| 244 | + } | ||
| 245 | + }); | ||
| 246 | + } else if (patten2.test(week)) { | ||
| 247 | + return week | ||
| 248 | + .split(',') | ||
| 249 | + .map((v) => convert(v)) | ||
| 250 | + .join(','); | ||
| 251 | + } | ||
| 252 | + return week; | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + function calTriggerListInner() { | ||
| 256 | + // 设置了回调函数 | ||
| 257 | + if (props.remote) { | ||
| 258 | + props.remote(cronValueInner.value, +new Date(), (v) => { | ||
| 259 | + preTimeList.value = v; | ||
| 260 | + }); | ||
| 261 | + return; | ||
| 262 | + } | ||
| 263 | + const format = 'yyyy-MM-dd hh:mm:ss'; | ||
| 264 | + const options = { | ||
| 265 | + currentDate: dateFormat(new Date(), format), | ||
| 266 | + }; | ||
| 267 | + const iter = CronParser.parseExpression(cronValueNoYear.value, options); | ||
| 268 | + const result: string[] = []; | ||
| 269 | + for (let i = 1; i <= 10; i++) { | ||
| 270 | + result.push(dateFormat(new Date(iter.next() as any), format)); | ||
| 271 | + } | ||
| 272 | + preTimeList.value = result.length > 0 ? result.join('\n') : '无执行时间'; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + function onInputBlur() { | ||
| 276 | + second.value = inputValues.second; | ||
| 277 | + minute.value = inputValues.minute; | ||
| 278 | + hour.value = inputValues.hour; | ||
| 279 | + day.value = inputValues.day; | ||
| 280 | + month.value = inputValues.month; | ||
| 281 | + week.value = inputValues.week; | ||
| 282 | + year.value = inputValues.year; | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + function onInputCronBlur(event) { | ||
| 286 | + emitValue(event.target.value); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + function emitValue(value) { | ||
| 290 | + emit('change', value); | ||
| 291 | + emit('update:value', value); | ||
| 292 | + } | ||
| 293 | +</script> | ||
| 294 | +<style lang="less"> | ||
| 295 | + @import './easy.cron.inner.less'; | ||
| 296 | +</style> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}`"> | ||
| 3 | + <a-input :placeholder="placeholder" v-model:value="editCronValue" :disabled="disabled"> | ||
| 4 | + <template #addonAfter> | ||
| 5 | + <a class="open-btn" :disabled="disabled ? 'disabled' : null" @click="showConfigModal"> | ||
| 6 | + <Icon icon="ant-design:setting-outlined" /> | ||
| 7 | + <span>选择</span> | ||
| 8 | + </a> | ||
| 9 | + </template> | ||
| 10 | + </a-input> | ||
| 11 | + <EasyCronModal | ||
| 12 | + @register="registerModal" | ||
| 13 | + v-model:value="editCronValue" | ||
| 14 | + :exeStartTime="exeStartTime" | ||
| 15 | + :hideYear="hideYear" | ||
| 16 | + :remote="remote" | ||
| 17 | + :hideSecond="hideSecond" | ||
| 18 | + /> | ||
| 19 | + </div> | ||
| 20 | +</template> | ||
| 21 | + | ||
| 22 | +<script lang="ts" setup> | ||
| 23 | + import { ref, watch } from 'vue'; | ||
| 24 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
| 25 | + import { useModal } from '/@/components/Modal'; | ||
| 26 | + import { propTypes } from '/@/utils/propTypes'; | ||
| 27 | + import Icon from '/@/components/Icon/src/Icon.vue'; | ||
| 28 | + import EasyCronModal from './EasyCronModal.vue'; | ||
| 29 | + import { cronEmits, cronProps } from './easy.cron.data'; | ||
| 30 | + | ||
| 31 | + const { prefixCls } = useDesign('easy-cron-input'); | ||
| 32 | + const emit = defineEmits([...cronEmits]); | ||
| 33 | + const props = defineProps({ | ||
| 34 | + ...cronProps, | ||
| 35 | + placeholder: propTypes.string.def('请输入cron表达式'), | ||
| 36 | + exeStartTime: propTypes | ||
| 37 | + .oneOfType([propTypes.number, propTypes.string, propTypes.object]) | ||
| 38 | + .def(0), | ||
| 39 | + }); | ||
| 40 | + const [registerModal, { openModal }] = useModal(); | ||
| 41 | + const editCronValue = ref(props.value); | ||
| 42 | + | ||
| 43 | + watch( | ||
| 44 | + () => props.value, | ||
| 45 | + (newVal) => { | ||
| 46 | + if (newVal !== editCronValue.value) { | ||
| 47 | + editCronValue.value = newVal; | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + ); | ||
| 51 | + watch(editCronValue, (newVal) => { | ||
| 52 | + emit('change', newVal); | ||
| 53 | + emit('update:value', newVal); | ||
| 54 | + }); | ||
| 55 | + | ||
| 56 | + function showConfigModal() { | ||
| 57 | + if (!props.disabled) { | ||
| 58 | + openModal(); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | +</script> | ||
| 62 | + | ||
| 63 | +<style lang="less"> | ||
| 64 | + @import './easy.cron.input.less'; | ||
| 65 | +</style> |
| 1 | +<template> | ||
| 2 | + <BasicModal | ||
| 3 | + @register="registerModal" | ||
| 4 | + title="Cron表达式" | ||
| 5 | + width="800px" | ||
| 6 | + @cancel="onCancel" | ||
| 7 | + :footer="null" | ||
| 8 | + > | ||
| 9 | + <EasyCron v-bind="attrs" /> | ||
| 10 | + <div style="float: right; margin-top: 2vh"> | ||
| 11 | + <Button type="default" @click="onCancel" class="mr-2">取消</Button> | ||
| 12 | + <Button type="primary" @click="onOk" class="mr-2">确认</Button> | ||
| 13 | + </div> | ||
| 14 | + </BasicModal> | ||
| 15 | +</template> | ||
| 16 | + | ||
| 17 | +<script lang="ts"> | ||
| 18 | + import { defineComponent } from 'vue'; | ||
| 19 | + import { useAttrs } from '/@/hooks/core/useAttrs'; | ||
| 20 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 21 | + import EasyCron from './EasyCronInner.vue'; | ||
| 22 | + import { Button } from '/@/components/Button'; | ||
| 23 | + | ||
| 24 | + export default defineComponent({ | ||
| 25 | + name: 'EasyCronModal', | ||
| 26 | + inheritAttrs: false, | ||
| 27 | + components: { BasicModal, EasyCron, Button }, | ||
| 28 | + setup() { | ||
| 29 | + const attrs = useAttrs(); | ||
| 30 | + const [registerModal, { closeModal }] = useModalInner(); | ||
| 31 | + | ||
| 32 | + function onOk() { | ||
| 33 | + setTimeout(() => { | ||
| 34 | + closeModal(); | ||
| 35 | + }, 500); | ||
| 36 | + } | ||
| 37 | + function onCancel() { | ||
| 38 | + setTimeout(() => { | ||
| 39 | + closeModal(); | ||
| 40 | + }, 500); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + return { attrs, registerModal, onOk, onCancel }; | ||
| 44 | + }, | ||
| 45 | + }); | ||
| 46 | +</script> |
| 1 | +MIT License | ||
| 2 | + | ||
| 3 | +Copyright (c) 2019 知行合一 | ||
| 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. |
| 1 | +import { propTypes } from '/@/utils/propTypes'; | ||
| 2 | + | ||
| 3 | +export const cronEmits = ['change', 'update:value']; | ||
| 4 | +export const cronProps = { | ||
| 5 | + value: propTypes.string.def(''), | ||
| 6 | + disabled: propTypes.bool.def(false), | ||
| 7 | + hideSecond: propTypes.bool.def(false), | ||
| 8 | + hideYear: propTypes.bool.def(false), | ||
| 9 | + remote: propTypes.func, | ||
| 10 | +}; |
| 1 | +//noinspection LessUnresolvedVariable | ||
| 2 | +@prefix-cls: ~'@{namespace}-easy-cron-inner'; | ||
| 3 | + | ||
| 4 | +.@{prefix-cls} { | ||
| 5 | + .content { | ||
| 6 | + .ant-checkbox-wrapper + .ant-checkbox-wrapper { | ||
| 7 | + margin-left: 0; | ||
| 8 | + } | ||
| 9 | + } | ||
| 10 | + | ||
| 11 | + &-config-list { | ||
| 12 | + text-align: left; | ||
| 13 | + margin: 0 10px 10px 10px; | ||
| 14 | + | ||
| 15 | + .item { | ||
| 16 | + margin-top: 5px; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + .choice { | ||
| 20 | + padding: 5px 8px; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + .w60 { | ||
| 24 | + width: 60px; | ||
| 25 | + min-width: 60px; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + .w80 { | ||
| 29 | + width: 80px; | ||
| 30 | + min-width: 80px; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + .list { | ||
| 34 | + margin: 0 20px; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + .list-check-item { | ||
| 38 | + padding: 1px 3px; | ||
| 39 | + width: 4em; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + .list-cn .list-check-item { | ||
| 43 | + width: 5em; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + .tip-info { | ||
| 47 | + color: #999; | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + .allow-click { | ||
| 52 | + cursor: pointer; | ||
| 53 | + } | ||
| 54 | +} |
| 1 | +// 原开源项目地址:https://gitee.com/toktok/easy-cron | ||
| 2 | + | ||
| 3 | +export { default as JEasyCron } from './EasyCronInput.vue'; | ||
| 4 | +export { default as JEasyCronInner } from './EasyCronInner.vue'; | ||
| 5 | +export { default as JEasyCronModal } from './EasyCronModal.vue'; | ||
| 6 | +export { default as JCronValidator } from './validator'; |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">不设置</a-radio> | ||
| 6 | + <span class="tip-info">日和周只能设置其中之一</span> | ||
| 7 | + </div> | ||
| 8 | + <div class="item"> | ||
| 9 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每日</a-radio> | ||
| 10 | + </div> | ||
| 11 | + <div class="item"> | ||
| 12 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 13 | + <span> 从 </span> | ||
| 14 | + <InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 15 | + <span> 日 至 </span> | ||
| 16 | + <InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 17 | + <span> 日 </span> | ||
| 18 | + </div> | ||
| 19 | + <div class="item"> | ||
| 20 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 21 | + <span> 从 </span> | ||
| 22 | + <InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 23 | + <span> 日开始,间隔 </span> | ||
| 24 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 25 | + <span> 日 </span> | ||
| 26 | + </div> | ||
| 27 | + <div class="item"> | ||
| 28 | + <a-radio :value="TypeEnum.work" v-bind="beforeRadioAttrs">工作日</a-radio> | ||
| 29 | + <span> 本月 </span> | ||
| 30 | + <InputNumber v-model:value="valueWork" v-bind="typeWorkAttrs" /> | ||
| 31 | + <span> 日,最近的工作日 </span> | ||
| 32 | + </div> | ||
| 33 | + <div class="item"> | ||
| 34 | + <a-radio :value="TypeEnum.last" v-bind="beforeRadioAttrs">最后一日</a-radio> | ||
| 35 | + </div> | ||
| 36 | + <div class="item"> | ||
| 37 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 38 | + <div class="list"> | ||
| 39 | + <a-checkbox-group v-model:value="valueList"> | ||
| 40 | + <template v-for="i in specifyRange" :key="i"> | ||
| 41 | + <a-checkbox :value="i" v-bind="typeSpecifyAttrs">{{ i }}</a-checkbox> | ||
| 42 | + </template> | ||
| 43 | + </a-checkbox-group> | ||
| 44 | + </div> | ||
| 45 | + </div> | ||
| 46 | + </a-radio-group> | ||
| 47 | + </div> | ||
| 48 | +</template> | ||
| 49 | + | ||
| 50 | +<script lang="ts"> | ||
| 51 | + import { computed, defineComponent, watch } from 'vue'; | ||
| 52 | + import { InputNumber } from 'ant-design-vue'; | ||
| 53 | + import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin'; | ||
| 54 | + | ||
| 55 | + export default defineComponent({ | ||
| 56 | + name: 'DayUI', | ||
| 57 | + components: { InputNumber }, | ||
| 58 | + props: useTabProps({ | ||
| 59 | + defaultValue: '*', | ||
| 60 | + props: { | ||
| 61 | + week: { type: String, default: '?' }, | ||
| 62 | + }, | ||
| 63 | + }), | ||
| 64 | + emits: useTabEmits(), | ||
| 65 | + setup(props, context) { | ||
| 66 | + const disabledChoice = computed(() => { | ||
| 67 | + return (props.week && props.week !== '?') || props.disabled; | ||
| 68 | + }); | ||
| 69 | + const setup = useTabSetup(props, context, { | ||
| 70 | + defaultValue: '*', | ||
| 71 | + valueWork: 1, | ||
| 72 | + minValue: 1, | ||
| 73 | + maxValue: 31, | ||
| 74 | + valueRange: { start: 1, end: 31 }, | ||
| 75 | + valueLoop: { start: 1, interval: 1 }, | ||
| 76 | + disabled: disabledChoice, | ||
| 77 | + }); | ||
| 78 | + const typeWorkAttrs = computed(() => ({ | ||
| 79 | + disabled: setup.type.value !== TypeEnum.work || props.disabled || disabledChoice.value, | ||
| 80 | + ...setup.inputNumberAttrs.value, | ||
| 81 | + })); | ||
| 82 | + | ||
| 83 | + watch( | ||
| 84 | + () => props.week, | ||
| 85 | + () => { | ||
| 86 | + setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value); | ||
| 87 | + } | ||
| 88 | + ); | ||
| 89 | + | ||
| 90 | + return { ...setup, typeWorkAttrs }; | ||
| 91 | + }, | ||
| 92 | + }); | ||
| 93 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每时</a-radio> | ||
| 6 | + </div> | ||
| 7 | + <div class="item"> | ||
| 8 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 9 | + <span> 从 </span> | ||
| 10 | + <InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 11 | + <span> 时 至 </span> | ||
| 12 | + <InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 13 | + <span> 时 </span> | ||
| 14 | + </div> | ||
| 15 | + <div class="item"> | ||
| 16 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 17 | + <span> 从 </span> | ||
| 18 | + <InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 19 | + <span> 时开始,间隔 </span> | ||
| 20 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 21 | + <span> 时 </span> | ||
| 22 | + </div> | ||
| 23 | + <div class="item"> | ||
| 24 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 25 | + <div class="list"> | ||
| 26 | + <a-checkbox-group v-model:value="valueList"> | ||
| 27 | + <template v-for="i in specifyRange" :key="i"> | ||
| 28 | + <a-checkbox :value="i" v-bind="typeSpecifyAttrs">{{ i }}</a-checkbox> | ||
| 29 | + </template> | ||
| 30 | + </a-checkbox-group> | ||
| 31 | + </div> | ||
| 32 | + </div> | ||
| 33 | + </a-radio-group> | ||
| 34 | + </div> | ||
| 35 | +</template> | ||
| 36 | + | ||
| 37 | +<script lang="ts"> | ||
| 38 | + import { defineComponent } from 'vue'; | ||
| 39 | + import { InputNumber } from 'ant-design-vue'; | ||
| 40 | + import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin'; | ||
| 41 | + | ||
| 42 | + export default defineComponent({ | ||
| 43 | + name: 'HourUI', | ||
| 44 | + components: { InputNumber }, | ||
| 45 | + props: useTabProps({ | ||
| 46 | + defaultValue: '*', | ||
| 47 | + }), | ||
| 48 | + emits: useTabEmits(), | ||
| 49 | + setup(props, context) { | ||
| 50 | + return useTabSetup(props, context, { | ||
| 51 | + defaultValue: '*', | ||
| 52 | + minValue: 0, | ||
| 53 | + maxValue: 23, | ||
| 54 | + valueRange: { start: 0, end: 23 }, | ||
| 55 | + valueLoop: { start: 0, interval: 1 }, | ||
| 56 | + }); | ||
| 57 | + }, | ||
| 58 | + }); | ||
| 59 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每分</a-radio> | ||
| 6 | + </div> | ||
| 7 | + <div class="item"> | ||
| 8 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 9 | + <span> 从 </span> | ||
| 10 | + <InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 11 | + <span> 分 至 </span> | ||
| 12 | + <InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 13 | + <span> 分 </span> | ||
| 14 | + </div> | ||
| 15 | + <div class="item"> | ||
| 16 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 17 | + <span> 从 </span> | ||
| 18 | + <InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 19 | + <span> 分开始,间隔 </span> | ||
| 20 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 21 | + <span> 分 </span> | ||
| 22 | + </div> | ||
| 23 | + <div class="item"> | ||
| 24 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 25 | + <div class="list"> | ||
| 26 | + <a-checkbox-group v-model:value="valueList"> | ||
| 27 | + <template v-for="i in specifyRange" :key="i"> | ||
| 28 | + <a-checkbox :value="i" v-bind="typeSpecifyAttrs">{{ i }}</a-checkbox> | ||
| 29 | + </template> | ||
| 30 | + </a-checkbox-group> | ||
| 31 | + </div> | ||
| 32 | + </div> | ||
| 33 | + </a-radio-group> | ||
| 34 | + </div> | ||
| 35 | +</template> | ||
| 36 | + | ||
| 37 | +<script lang="ts"> | ||
| 38 | + import { defineComponent } from 'vue'; | ||
| 39 | + import { InputNumber } from 'ant-design-vue'; | ||
| 40 | + import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin'; | ||
| 41 | + | ||
| 42 | + export default defineComponent({ | ||
| 43 | + name: 'MinuteUI', | ||
| 44 | + components: { InputNumber }, | ||
| 45 | + props: useTabProps({ | ||
| 46 | + defaultValue: '*', | ||
| 47 | + }), | ||
| 48 | + emits: useTabEmits(), | ||
| 49 | + setup(props, context) { | ||
| 50 | + return useTabSetup(props, context, { | ||
| 51 | + defaultValue: '*', | ||
| 52 | + minValue: 0, | ||
| 53 | + maxValue: 59, | ||
| 54 | + valueRange: { start: 0, end: 59 }, | ||
| 55 | + valueLoop: { start: 0, interval: 1 }, | ||
| 56 | + }); | ||
| 57 | + }, | ||
| 58 | + }); | ||
| 59 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每月</a-radio> | ||
| 6 | + </div> | ||
| 7 | + <div class="item"> | ||
| 8 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 9 | + <span> 从 </span> | ||
| 10 | + <InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 11 | + <span> 月 至 </span> | ||
| 12 | + <InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 13 | + <span> 月 </span> | ||
| 14 | + </div> | ||
| 15 | + <div class="item"> | ||
| 16 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 17 | + <span> 从 </span> | ||
| 18 | + <InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 19 | + <span> 月开始,间隔 </span> | ||
| 20 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 21 | + <span> 月 </span> | ||
| 22 | + </div> | ||
| 23 | + <div class="item"> | ||
| 24 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 25 | + <div class="list"> | ||
| 26 | + <a-checkbox-group v-model:value="valueList"> | ||
| 27 | + <template v-for="i in specifyRange" :key="i"> | ||
| 28 | + <a-checkbox :value="i" v-bind="typeSpecifyAttrs">{{ i }}</a-checkbox> | ||
| 29 | + </template> | ||
| 30 | + </a-checkbox-group> | ||
| 31 | + </div> | ||
| 32 | + </div> | ||
| 33 | + </a-radio-group> | ||
| 34 | + </div> | ||
| 35 | +</template> | ||
| 36 | + | ||
| 37 | +<script lang="ts"> | ||
| 38 | + import { defineComponent } from 'vue'; | ||
| 39 | + import { InputNumber } from 'ant-design-vue'; | ||
| 40 | + import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin'; | ||
| 41 | + | ||
| 42 | + export default defineComponent({ | ||
| 43 | + name: 'MonthUI', | ||
| 44 | + components: { InputNumber }, | ||
| 45 | + props: useTabProps({ | ||
| 46 | + defaultValue: '*', | ||
| 47 | + }), | ||
| 48 | + emits: useTabEmits(), | ||
| 49 | + setup(props, context) { | ||
| 50 | + return useTabSetup(props, context, { | ||
| 51 | + defaultValue: '*', | ||
| 52 | + minValue: 1, | ||
| 53 | + maxValue: 12, | ||
| 54 | + valueRange: { start: 1, end: 12 }, | ||
| 55 | + valueLoop: { start: 1, interval: 1 }, | ||
| 56 | + }); | ||
| 57 | + }, | ||
| 58 | + }); | ||
| 59 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每秒</a-radio> | ||
| 6 | + </div> | ||
| 7 | + <div class="item"> | ||
| 8 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 9 | + <span> 从 </span> | ||
| 10 | + <InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 11 | + <span> 秒 至 </span> | ||
| 12 | + <InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 13 | + <span> 秒 </span> | ||
| 14 | + </div> | ||
| 15 | + <div class="item"> | ||
| 16 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 17 | + <span> 从 </span> | ||
| 18 | + <InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 19 | + <span> 秒开始,间隔 </span> | ||
| 20 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 21 | + <span> 秒 </span> | ||
| 22 | + </div> | ||
| 23 | + <div class="item"> | ||
| 24 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 25 | + <div class="list"> | ||
| 26 | + <a-checkbox-group v-model:value="valueList"> | ||
| 27 | + <template v-for="i in specifyRange" :key="i"> | ||
| 28 | + <a-checkbox :value="i" v-bind="typeSpecifyAttrs">{{ i }}</a-checkbox> | ||
| 29 | + </template> | ||
| 30 | + </a-checkbox-group> | ||
| 31 | + </div> | ||
| 32 | + </div> | ||
| 33 | + </a-radio-group> | ||
| 34 | + </div> | ||
| 35 | +</template> | ||
| 36 | + | ||
| 37 | +<script lang="ts"> | ||
| 38 | + import { defineComponent } from 'vue'; | ||
| 39 | + import { InputNumber } from 'ant-design-vue'; | ||
| 40 | + import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin'; | ||
| 41 | + | ||
| 42 | + export default defineComponent({ | ||
| 43 | + name: 'SecondUI', | ||
| 44 | + components: { InputNumber }, | ||
| 45 | + props: useTabProps({ | ||
| 46 | + defaultValue: '*', | ||
| 47 | + }), | ||
| 48 | + emits: useTabEmits(), | ||
| 49 | + setup(props, context) { | ||
| 50 | + return useTabSetup(props, context, { | ||
| 51 | + defaultValue: '*', | ||
| 52 | + minValue: 0, | ||
| 53 | + maxValue: 59, | ||
| 54 | + valueRange: { start: 0, end: 59 }, | ||
| 55 | + valueLoop: { start: 0, interval: 1 }, | ||
| 56 | + }); | ||
| 57 | + }, | ||
| 58 | + }); | ||
| 59 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">不设置</a-radio> | ||
| 6 | + <span class="tip-info">日和周只能设置其中之一</span> | ||
| 7 | + </div> | ||
| 8 | + <div class="item"> | ||
| 9 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 10 | + <span> 从 </span> | ||
| 11 | + <a-select | ||
| 12 | + v-model:value="valueRange.start" | ||
| 13 | + :options="weekOptions" | ||
| 14 | + v-bind="typeRangeSelectAttrs" | ||
| 15 | + /> | ||
| 16 | + <span> 至 </span> | ||
| 17 | + <a-select | ||
| 18 | + v-model:value="valueRange.end" | ||
| 19 | + :options="weekOptions" | ||
| 20 | + v-bind="typeRangeSelectAttrs" | ||
| 21 | + /> | ||
| 22 | + </div> | ||
| 23 | + <div class="item"> | ||
| 24 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 25 | + <span> 从 </span> | ||
| 26 | + <a-select | ||
| 27 | + v-model:value="valueLoop.start" | ||
| 28 | + :options="weekOptions" | ||
| 29 | + v-bind="typeLoopSelectAttrs" | ||
| 30 | + /> | ||
| 31 | + <span> 开始,间隔 </span> | ||
| 32 | + <InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 33 | + <span> 天 </span> | ||
| 34 | + </div> | ||
| 35 | + <div class="item"> | ||
| 36 | + <a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> | ||
| 37 | + <div class="list list-cn"> | ||
| 38 | + <a-checkbox-group v-model:value="valueList"> | ||
| 39 | + <template v-for="opt in weekOptions" :key="opt.label"> | ||
| 40 | + <a-checkbox :value="opt.value" v-bind="typeSpecifyAttrs">{{ opt.label }}</a-checkbox> | ||
| 41 | + </template> | ||
| 42 | + </a-checkbox-group> | ||
| 43 | + </div> | ||
| 44 | + </div> | ||
| 45 | + </a-radio-group> | ||
| 46 | + </div> | ||
| 47 | +</template> | ||
| 48 | + | ||
| 49 | +<script lang="ts"> | ||
| 50 | + import { computed, watch, defineComponent } from 'vue'; | ||
| 51 | + import { InputNumber } from 'ant-design-vue'; | ||
| 52 | + import { useTabProps, useTabEmits, useTabSetup, TypeEnum } from './useTabMixin'; | ||
| 53 | + | ||
| 54 | + const WEEK_MAP_EN = { | ||
| 55 | + '1': 'SUN', | ||
| 56 | + '2': 'MON', | ||
| 57 | + '3': 'TUE', | ||
| 58 | + '4': 'WED', | ||
| 59 | + '5': 'THU', | ||
| 60 | + '6': 'FRI', | ||
| 61 | + '7': 'SAT', | ||
| 62 | + }; | ||
| 63 | + | ||
| 64 | + const WEEK_MAP_CN = { | ||
| 65 | + '1': '周日', | ||
| 66 | + '2': '周一', | ||
| 67 | + '3': '周二', | ||
| 68 | + '4': '周三', | ||
| 69 | + '5': '周四', | ||
| 70 | + '6': '周五', | ||
| 71 | + '7': '周六', | ||
| 72 | + }; | ||
| 73 | + | ||
| 74 | + export default defineComponent({ | ||
| 75 | + name: 'WeekUI', | ||
| 76 | + components: { InputNumber }, | ||
| 77 | + props: useTabProps({ | ||
| 78 | + defaultValue: '?', | ||
| 79 | + props: { | ||
| 80 | + day: { type: String, default: '*' }, | ||
| 81 | + }, | ||
| 82 | + }), | ||
| 83 | + emits: useTabEmits(), | ||
| 84 | + setup(props, context) { | ||
| 85 | + const disabledChoice = computed(() => { | ||
| 86 | + return (props.day && props.day !== '?') || props.disabled; | ||
| 87 | + }); | ||
| 88 | + const setup = useTabSetup(props, context, { | ||
| 89 | + defaultType: TypeEnum.unset, | ||
| 90 | + defaultValue: '?', | ||
| 91 | + minValue: 1, | ||
| 92 | + maxValue: 7, | ||
| 93 | + // 0,7表示周日 1表示周一 | ||
| 94 | + valueRange: { start: 1, end: 7 }, | ||
| 95 | + valueLoop: { start: 2, interval: 1 }, | ||
| 96 | + disabled: disabledChoice, | ||
| 97 | + }); | ||
| 98 | + const weekOptions = computed(() => { | ||
| 99 | + let options: { label: string; value: number }[] = []; | ||
| 100 | + for (let weekKey of Object.keys(WEEK_MAP_CN)) { | ||
| 101 | + let weekName: string = WEEK_MAP_CN[weekKey]; | ||
| 102 | + options.push({ | ||
| 103 | + value: Number.parseInt(weekKey), | ||
| 104 | + label: weekName, | ||
| 105 | + }); | ||
| 106 | + } | ||
| 107 | + return options; | ||
| 108 | + }); | ||
| 109 | + | ||
| 110 | + const typeRangeSelectAttrs = computed(() => ({ | ||
| 111 | + class: ['w80'], | ||
| 112 | + disabled: setup.typeRangeAttrs.value.disabled, | ||
| 113 | + })); | ||
| 114 | + | ||
| 115 | + const typeLoopSelectAttrs = computed(() => ({ | ||
| 116 | + class: ['w80'], | ||
| 117 | + disabled: setup.typeLoopAttrs.value.disabled, | ||
| 118 | + })); | ||
| 119 | + | ||
| 120 | + watch( | ||
| 121 | + () => props.day, | ||
| 122 | + () => { | ||
| 123 | + setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value); | ||
| 124 | + } | ||
| 125 | + ); | ||
| 126 | + | ||
| 127 | + return { | ||
| 128 | + ...setup, | ||
| 129 | + weekOptions, | ||
| 130 | + typeLoopSelectAttrs, | ||
| 131 | + typeRangeSelectAttrs, | ||
| 132 | + WEEK_MAP_CN, | ||
| 133 | + WEEK_MAP_EN, | ||
| 134 | + }; | ||
| 135 | + }, | ||
| 136 | + }); | ||
| 137 | +</script> |
| 1 | +<template> | ||
| 2 | + <div :class="`${prefixCls}-config-list`"> | ||
| 3 | + <a-radio-group v-model:value="type"> | ||
| 4 | + <div class="item"> | ||
| 5 | + <a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每年</a-radio> | ||
| 6 | + </div> | ||
| 7 | + <div class="item"> | ||
| 8 | + <a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio> | ||
| 9 | + <span> 从 </span> | ||
| 10 | + <InputNumber class="w80" v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> | ||
| 11 | + <span> 年 至 </span> | ||
| 12 | + <InputNumber class="w80" v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> | ||
| 13 | + <span> 年 </span> | ||
| 14 | + </div> | ||
| 15 | + <div class="item"> | ||
| 16 | + <a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio> | ||
| 17 | + <span> 从 </span> | ||
| 18 | + <InputNumber class="w80" v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> | ||
| 19 | + <span> 年开始,间隔 </span> | ||
| 20 | + <InputNumber class="w80" v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> | ||
| 21 | + <span> 年 </span> | ||
| 22 | + </div> | ||
| 23 | + </a-radio-group> | ||
| 24 | + </div> | ||
| 25 | +</template> | ||
| 26 | + | ||
| 27 | +<script lang="ts"> | ||
| 28 | + import { defineComponent } from 'vue'; | ||
| 29 | + import { InputNumber } from 'ant-design-vue'; | ||
| 30 | + import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin'; | ||
| 31 | + | ||
| 32 | + export default defineComponent({ | ||
| 33 | + name: 'YearUI', | ||
| 34 | + components: { InputNumber }, | ||
| 35 | + props: useTabProps({ | ||
| 36 | + defaultValue: '*', | ||
| 37 | + }), | ||
| 38 | + emits: useTabEmits(), | ||
| 39 | + setup(props, context) { | ||
| 40 | + const nowYear = new Date().getFullYear(); | ||
| 41 | + return useTabSetup(props, context, { | ||
| 42 | + defaultValue: '*', | ||
| 43 | + minValue: 0, | ||
| 44 | + valueRange: { start: nowYear, end: nowYear + 100 }, | ||
| 45 | + valueLoop: { start: nowYear, interval: 1 }, | ||
| 46 | + }); | ||
| 47 | + }, | ||
| 48 | + }); | ||
| 49 | +</script> |
| 1 | +// 主要用于日和星期的互斥使用 | ||
| 2 | +import { computed, inject, reactive, ref, unref, watch } from 'vue'; | ||
| 3 | +import { propTypes } from '/@/utils/propTypes'; | ||
| 4 | + | ||
| 5 | +export enum TypeEnum { | ||
| 6 | + unset = 'UNSET', | ||
| 7 | + every = 'EVERY', | ||
| 8 | + range = 'RANGE', | ||
| 9 | + loop = 'LOOP', | ||
| 10 | + work = 'WORK', | ||
| 11 | + last = 'LAST', | ||
| 12 | + specify = 'SPECIFY', | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +// use 公共 props | ||
| 16 | +export function useTabProps(options) { | ||
| 17 | + const defaultValue = options?.defaultValue ?? '?'; | ||
| 18 | + return { | ||
| 19 | + value: propTypes.string.def(defaultValue), | ||
| 20 | + disabled: propTypes.bool.def(false), | ||
| 21 | + ...options?.props, | ||
| 22 | + }; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +// use 公共 emits | ||
| 26 | +export function useTabEmits() { | ||
| 27 | + return ['change', 'update:value']; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +// use 公共 setup | ||
| 31 | +export function useTabSetup(props, context, options) { | ||
| 32 | + const { emit } = context; | ||
| 33 | + const prefixCls = inject('prefixCls'); | ||
| 34 | + const defaultValue = ref(options?.defaultValue ?? '?'); | ||
| 35 | + // 类型 | ||
| 36 | + const type = ref(options.defaultType ?? TypeEnum.every); | ||
| 37 | + const valueList = ref<any[]>([]); | ||
| 38 | + // 对于不同的类型,所定义的值也有所不同 | ||
| 39 | + const valueRange = reactive(options.valueRange); | ||
| 40 | + const valueLoop = reactive(options.valueLoop); | ||
| 41 | + const valueWeek = reactive(options.valueWeek); | ||
| 42 | + const valueWork = ref(options.valueWork); | ||
| 43 | + const maxValue = ref(options.maxValue); | ||
| 44 | + const minValue = ref(options.minValue); | ||
| 45 | + | ||
| 46 | + // 根据不同的类型计算出的value | ||
| 47 | + const computeValue = computed(() => { | ||
| 48 | + let valueArray: any[] = []; | ||
| 49 | + switch (type.value) { | ||
| 50 | + case TypeEnum.unset: | ||
| 51 | + valueArray.push('?'); | ||
| 52 | + break; | ||
| 53 | + case TypeEnum.every: | ||
| 54 | + valueArray.push('*'); | ||
| 55 | + break; | ||
| 56 | + case TypeEnum.range: | ||
| 57 | + valueArray.push(`${valueRange.start}-${valueRange.end}`); | ||
| 58 | + break; | ||
| 59 | + case TypeEnum.loop: | ||
| 60 | + valueArray.push(`${valueLoop.start}/${valueLoop.interval}`); | ||
| 61 | + break; | ||
| 62 | + case TypeEnum.work: | ||
| 63 | + valueArray.push(`${valueWork.value}W`); | ||
| 64 | + break; | ||
| 65 | + case TypeEnum.last: | ||
| 66 | + valueArray.push('L'); | ||
| 67 | + break; | ||
| 68 | + case TypeEnum.specify: | ||
| 69 | + if (valueList.value.length === 0) { | ||
| 70 | + valueList.value.push(minValue.value); | ||
| 71 | + } | ||
| 72 | + valueArray.push(valueList.value.join(',')); | ||
| 73 | + break; | ||
| 74 | + default: | ||
| 75 | + valueArray.push(defaultValue.value); | ||
| 76 | + break; | ||
| 77 | + } | ||
| 78 | + return valueArray.length > 0 ? valueArray.join('') : defaultValue.value; | ||
| 79 | + }); | ||
| 80 | + // 指定值范围区间,介于最小值和最大值之间 | ||
| 81 | + const specifyRange = computed(() => { | ||
| 82 | + let range: number[] = []; | ||
| 83 | + if (maxValue.value != null) { | ||
| 84 | + for (let i = minValue.value; i <= maxValue.value; i++) { | ||
| 85 | + range.push(i); | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + return range; | ||
| 89 | + }); | ||
| 90 | + | ||
| 91 | + watch( | ||
| 92 | + () => props.value, | ||
| 93 | + (val) => { | ||
| 94 | + if (val !== computeValue.value) { | ||
| 95 | + parseValue(val); | ||
| 96 | + } | ||
| 97 | + }, | ||
| 98 | + { immediate: true } | ||
| 99 | + ); | ||
| 100 | + | ||
| 101 | + watch(computeValue, (v) => updateValue(v)); | ||
| 102 | + | ||
| 103 | + function updateValue(value) { | ||
| 104 | + emit('change', value); | ||
| 105 | + emit('update:value', value); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + /** | ||
| 109 | + * parseValue | ||
| 110 | + * @param value | ||
| 111 | + */ | ||
| 112 | + function parseValue(value) { | ||
| 113 | + if (value === computeValue.value) { | ||
| 114 | + return; | ||
| 115 | + } | ||
| 116 | + try { | ||
| 117 | + if (!value || value === defaultValue.value) { | ||
| 118 | + type.value = TypeEnum.every; | ||
| 119 | + } else if (value.indexOf('?') >= 0) { | ||
| 120 | + type.value = TypeEnum.unset; | ||
| 121 | + } else if (value.indexOf('-') >= 0) { | ||
| 122 | + type.value = TypeEnum.range; | ||
| 123 | + const values = value.split('-'); | ||
| 124 | + if (values.length >= 2) { | ||
| 125 | + valueRange.start = parseInt(values[0]); | ||
| 126 | + valueRange.end = parseInt(values[1]); | ||
| 127 | + } | ||
| 128 | + } else if (value.indexOf('/') >= 0) { | ||
| 129 | + type.value = TypeEnum.loop; | ||
| 130 | + const values = value.split('/'); | ||
| 131 | + if (values.length >= 2) { | ||
| 132 | + valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0]); | ||
| 133 | + valueLoop.interval = parseInt(values[1]); | ||
| 134 | + } | ||
| 135 | + } else if (value.indexOf('W') >= 0) { | ||
| 136 | + type.value = TypeEnum.work; | ||
| 137 | + const values = value.split('W'); | ||
| 138 | + if (!values[0] && !isNaN(values[0])) { | ||
| 139 | + valueWork.value = parseInt(values[0]); | ||
| 140 | + } | ||
| 141 | + } else if (value.indexOf('L') >= 0) { | ||
| 142 | + type.value = TypeEnum.last; | ||
| 143 | + } else if (value.indexOf(',') >= 0 || !isNaN(value)) { | ||
| 144 | + type.value = TypeEnum.specify; | ||
| 145 | + valueList.value = value.split(',').map((item) => parseInt(item)); | ||
| 146 | + } else { | ||
| 147 | + type.value = TypeEnum.every; | ||
| 148 | + } | ||
| 149 | + } catch (e) { | ||
| 150 | + type.value = TypeEnum.every; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + const beforeRadioAttrs = computed(() => ({ | ||
| 155 | + class: ['choice'], | ||
| 156 | + disabled: props.disabled || unref(options.disabled), | ||
| 157 | + })); | ||
| 158 | + const inputNumberAttrs = computed(() => ({ | ||
| 159 | + class: ['w60'], | ||
| 160 | + max: maxValue.value, | ||
| 161 | + min: minValue.value, | ||
| 162 | + precision: 0, | ||
| 163 | + })); | ||
| 164 | + const typeRangeAttrs = computed(() => ({ | ||
| 165 | + disabled: type.value !== TypeEnum.range || props.disabled || unref(options.disabled), | ||
| 166 | + ...inputNumberAttrs.value, | ||
| 167 | + })); | ||
| 168 | + const typeLoopAttrs = computed(() => ({ | ||
| 169 | + disabled: type.value !== TypeEnum.loop || props.disabled || unref(options.disabled), | ||
| 170 | + ...inputNumberAttrs.value, | ||
| 171 | + })); | ||
| 172 | + const typeSpecifyAttrs = computed(() => ({ | ||
| 173 | + disabled: type.value !== TypeEnum.specify || props.disabled || unref(options.disabled), | ||
| 174 | + class: ['list-check-item'], | ||
| 175 | + })); | ||
| 176 | + | ||
| 177 | + return { | ||
| 178 | + type, | ||
| 179 | + TypeEnum, | ||
| 180 | + prefixCls, | ||
| 181 | + defaultValue, | ||
| 182 | + valueRange, | ||
| 183 | + valueLoop, | ||
| 184 | + valueWeek, | ||
| 185 | + valueList, | ||
| 186 | + valueWork, | ||
| 187 | + maxValue, | ||
| 188 | + minValue, | ||
| 189 | + computeValue, | ||
| 190 | + specifyRange, | ||
| 191 | + updateValue, | ||
| 192 | + parseValue, | ||
| 193 | + beforeRadioAttrs, | ||
| 194 | + inputNumberAttrs, | ||
| 195 | + typeRangeAttrs, | ||
| 196 | + typeLoopAttrs, | ||
| 197 | + typeSpecifyAttrs, | ||
| 198 | + }; | ||
| 199 | +} |
| 1 | +import CronParser from 'cron-parser'; | ||
| 2 | +import type { ValidatorRule } from 'ant-design-vue/lib/form/interface'; | ||
| 3 | + | ||
| 4 | +const cronRule: ValidatorRule = { | ||
| 5 | + validator({}, value) { | ||
| 6 | + // 没填写就不校验 | ||
| 7 | + if (!value) { | ||
| 8 | + return Promise.resolve(); | ||
| 9 | + } | ||
| 10 | + const values: string[] = value.split(' ').filter((item) => !!item); | ||
| 11 | + if (values.length > 7) { | ||
| 12 | + return Promise.reject('Cron表达式最多7项!'); | ||
| 13 | + } | ||
| 14 | + // 检查第7项 | ||
| 15 | + let val: string = value; | ||
| 16 | + if (values.length === 7) { | ||
| 17 | + const year = values[6]; | ||
| 18 | + if (year !== '*' && year !== '?') { | ||
| 19 | + let yearValues: string[] = []; | ||
| 20 | + if (year.indexOf('-') >= 0) { | ||
| 21 | + yearValues = year.split('-'); | ||
| 22 | + } else if (year.indexOf('/')) { | ||
| 23 | + yearValues = year.split('/'); | ||
| 24 | + } else { | ||
| 25 | + yearValues = [year]; | ||
| 26 | + } | ||
| 27 | + // 判断是否都是数字 | ||
| 28 | + const checkYear = yearValues.some((item) => isNaN(Number(item))); | ||
| 29 | + if (checkYear) { | ||
| 30 | + return Promise.reject('Cron表达式参数[年]错误:' + year); | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + // 取其中的前六项 | ||
| 34 | + val = values.slice(0, 6).join(' '); | ||
| 35 | + } | ||
| 36 | + // 6位 没有年 | ||
| 37 | + // 5位没有秒、年 | ||
| 38 | + try { | ||
| 39 | + const iter = CronParser.parseExpression(val); | ||
| 40 | + iter.next(); | ||
| 41 | + return Promise.resolve(); | ||
| 42 | + } catch (e) { | ||
| 43 | + return Promise.reject('Cron表达式错误:' + e); | ||
| 44 | + } | ||
| 45 | + }, | ||
| 46 | +}; | ||
| 47 | + | ||
| 48 | +export default cronRule.validator; |
| @@ -82,6 +82,7 @@ export interface ColEx { | @@ -82,6 +82,7 @@ export interface ColEx { | ||
| 82 | 82 | ||
| 83 | export type ComponentType = | 83 | export type ComponentType = |
| 84 | | 'Input' | 84 | | 'Input' |
| 85 | + | 'JEasyCron' | ||
| 85 | | 'ApiRadioGroup' | 86 | | 'ApiRadioGroup' |
| 86 | | 'InputGroup' | 87 | | 'InputGroup' |
| 87 | | 'InputPassword' | 88 | | 'InputPassword' |
| @@ -472,19 +472,49 @@ | @@ -472,19 +472,49 @@ | ||
| 472 | }); | 472 | }); |
| 473 | </script> | 473 | </script> |
| 474 | <style lang="less"> | 474 | <style lang="less"> |
| 475 | - .fold-left { | ||
| 476 | - z-index: 1; | ||
| 477 | - cursor: pointer; | ||
| 478 | - position: absolute; | ||
| 479 | - top: 0.85rem; | ||
| 480 | - left: 1.1vw; | 475 | + // 使用媒体查询兼容 1920 1280 1024 800 |
| 476 | + @media screen and (max-width: 1980px) { | ||
| 477 | + .fold-left { | ||
| 478 | + z-index: 1; | ||
| 479 | + cursor: pointer; | ||
| 480 | + position: absolute; | ||
| 481 | + top: 0.85rem; | ||
| 482 | + left: 1.1vw; | ||
| 483 | + } | ||
| 484 | + .fold-right { | ||
| 485 | + z-index: 1; | ||
| 486 | + cursor: pointer; | ||
| 487 | + position: absolute; | ||
| 488 | + top: 0.85rem; | ||
| 489 | + left: 18.2vw; | ||
| 490 | + } | ||
| 491 | + } | ||
| 492 | + @media screen and (max-width: 1280px) { | ||
| 493 | + .fold-right { | ||
| 494 | + z-index: 1; | ||
| 495 | + cursor: pointer; | ||
| 496 | + position: absolute; | ||
| 497 | + top: 0.85rem; | ||
| 498 | + left: 17.5vw; | ||
| 499 | + } | ||
| 500 | + } | ||
| 501 | + @media screen and (max-width: 1024px) { | ||
| 502 | + .fold-right { | ||
| 503 | + z-index: 1; | ||
| 504 | + cursor: pointer; | ||
| 505 | + position: absolute; | ||
| 506 | + top: 0.85rem; | ||
| 507 | + left: 14.2vw; | ||
| 508 | + } | ||
| 481 | } | 509 | } |
| 482 | - .fold-right { | ||
| 483 | - z-index: 1; | ||
| 484 | - cursor: pointer; | ||
| 485 | - position: absolute; | ||
| 486 | - top: 0.85rem; | ||
| 487 | - left: 18.2vw; | 510 | + @media screen and (max-width: 800px) { |
| 511 | + .fold-right { | ||
| 512 | + z-index: 1; | ||
| 513 | + cursor: pointer; | ||
| 514 | + position: absolute; | ||
| 515 | + top: 0.85rem; | ||
| 516 | + left: 18vw; | ||
| 517 | + } | ||
| 488 | } | 518 | } |
| 489 | @prefix-cls: ~'@{namespace}-basic-tree'; | 519 | @prefix-cls: ~'@{namespace}-basic-tree'; |
| 490 | 520 |
| 1 | -// 全局按需注册组件 | ||
| 2 | import type { App } from 'vue'; | 1 | import type { App } from 'vue'; |
| 2 | +import { Icon } from './Icon'; | ||
| 3 | import { Button } from './Button'; | 3 | import { Button } from './Button'; |
| 4 | import { | 4 | import { |
| 5 | // Need | 5 | // Need |
| 6 | Button as AntButton, | 6 | Button as AntButton, |
| 7 | + Select, | ||
| 8 | + Alert, | ||
| 9 | + Checkbox, | ||
| 10 | + DatePicker, | ||
| 11 | + Radio, | ||
| 12 | + Switch, | ||
| 13 | + Card, | ||
| 14 | + List, | ||
| 15 | + Tabs, | ||
| 16 | + Descriptions, | ||
| 17 | + Tree, | ||
| 18 | + Table, | ||
| 19 | + Divider, | ||
| 20 | + Modal, | ||
| 21 | + Drawer, | ||
| 22 | + TreeSelect, | ||
| 23 | + Dropdown, | ||
| 24 | + Tag, | ||
| 25 | + Tooltip, | ||
| 26 | + Badge, | ||
| 27 | + Popover, | ||
| 28 | + Upload, | ||
| 29 | + Transfer, | ||
| 30 | + Steps, | ||
| 31 | + PageHeader, | ||
| 32 | + Result, | ||
| 33 | + Empty, | ||
| 34 | + Avatar, | ||
| 35 | + Menu, | ||
| 36 | + Breadcrumb, | ||
| 37 | + Form, | ||
| 7 | Input, | 38 | Input, |
| 39 | + Row, | ||
| 40 | + Col, | ||
| 41 | + Spin, | ||
| 42 | + Space, | ||
| 8 | Layout, | 43 | Layout, |
| 44 | + Collapse, | ||
| 45 | + Slider, | ||
| 46 | + InputNumber, | ||
| 47 | + Carousel, | ||
| 48 | + Popconfirm, | ||
| 49 | + Skeleton, | ||
| 50 | + Cascader, | ||
| 51 | + Rate, | ||
| 9 | } from 'ant-design-vue'; | 52 | } from 'ant-design-vue'; |
| 10 | 53 | ||
| 11 | -const compList = [AntButton.Group]; | 54 | +const compList = [AntButton.Group, Icon]; |
| 12 | 55 | ||
| 13 | export function registerGlobComp(app: App) { | 56 | export function registerGlobComp(app: App) { |
| 14 | compList.forEach((comp) => { | 57 | compList.forEach((comp) => { |
| 15 | app.component(comp.name || comp.displayName, comp); | 58 | app.component(comp.name || comp.displayName, comp); |
| 16 | }); | 59 | }); |
| 17 | 60 | ||
| 18 | - app.use(Input).use(Button).use(Layout); | 61 | + app |
| 62 | + .use(Select) | ||
| 63 | + .use(Alert) | ||
| 64 | + .use(Button) | ||
| 65 | + .use(Breadcrumb) | ||
| 66 | + .use(Checkbox) | ||
| 67 | + .use(DatePicker) | ||
| 68 | + .use(Radio) | ||
| 69 | + .use(Switch) | ||
| 70 | + .use(Card) | ||
| 71 | + .use(List) | ||
| 72 | + .use(Descriptions) | ||
| 73 | + .use(Tree) | ||
| 74 | + .use(TreeSelect) | ||
| 75 | + .use(Table) | ||
| 76 | + .use(Divider) | ||
| 77 | + .use(Modal) | ||
| 78 | + .use(Drawer) | ||
| 79 | + .use(Dropdown) | ||
| 80 | + .use(Tag) | ||
| 81 | + .use(Tooltip) | ||
| 82 | + .use(Badge) | ||
| 83 | + .use(Popover) | ||
| 84 | + .use(Upload) | ||
| 85 | + .use(Transfer) | ||
| 86 | + .use(Steps) | ||
| 87 | + .use(PageHeader) | ||
| 88 | + .use(Result) | ||
| 89 | + .use(Empty) | ||
| 90 | + .use(Avatar) | ||
| 91 | + .use(Menu) | ||
| 92 | + .use(Tabs) | ||
| 93 | + .use(Form) | ||
| 94 | + .use(Input) | ||
| 95 | + .use(Row) | ||
| 96 | + .use(Col) | ||
| 97 | + .use(Spin) | ||
| 98 | + .use(Space) | ||
| 99 | + .use(Layout) | ||
| 100 | + .use(Collapse) | ||
| 101 | + .use(Slider) | ||
| 102 | + .use(InputNumber) | ||
| 103 | + .use(Carousel) | ||
| 104 | + .use(Popconfirm) | ||
| 105 | + .use(Skeleton) | ||
| 106 | + .use(Cascader) | ||
| 107 | + .use(Rate); | ||
| 19 | } | 108 | } |
| @@ -174,6 +174,9 @@ export default { | @@ -174,6 +174,9 @@ export default { | ||
| 174 | menu: 'Menu management', | 174 | menu: 'Menu management', |
| 175 | role: 'Role management', | 175 | role: 'Role management', |
| 176 | }, | 176 | }, |
| 177 | + report: { | ||
| 178 | + schedue_log: 'Schedue Log', | ||
| 179 | + }, | ||
| 177 | table: { | 180 | table: { |
| 178 | table: 'Table', | 181 | table: 'Table', |
| 179 | 182 |
| @@ -166,6 +166,9 @@ export default { | @@ -166,6 +166,9 @@ export default { | ||
| 166 | menu: '菜单管理', | 166 | menu: '菜单管理', |
| 167 | role: '角色管理', | 167 | role: '角色管理', |
| 168 | }, | 168 | }, |
| 169 | + report: { | ||
| 170 | + schedue_log: '调度日志', | ||
| 171 | + }, | ||
| 169 | table: { | 172 | table: { |
| 170 | table: 'Table', | 173 | table: 'Table', |
| 171 | basic: '基础表格', | 174 | basic: '基础表格', |
| @@ -53,12 +53,34 @@ export const AccountDetail: AppRouteRecordRaw = { | @@ -53,12 +53,34 @@ export const AccountDetail: AppRouteRecordRaw = { | ||
| 53 | }, | 53 | }, |
| 54 | ], | 54 | ], |
| 55 | }; | 55 | }; |
| 56 | +// 调度日志静态路由 | ||
| 57 | +export const SchedueLog: AppRouteRecordRaw = { | ||
| 58 | + path: '/report', | ||
| 59 | + name: '报表管理', | ||
| 60 | + component: LAYOUT, | ||
| 61 | + meta: { | ||
| 62 | + title: '报表管理', | ||
| 63 | + hideBreadcrumb: true, | ||
| 64 | + hideChildrenInMenu: true, | ||
| 65 | + }, | ||
| 66 | + children: [ | ||
| 67 | + { | ||
| 68 | + path: 'schedue_log/:id', | ||
| 69 | + name: 'Schedue_Log', | ||
| 70 | + component: () => import('../../views/system/scheduled/SchedueLog.vue'), | ||
| 71 | + meta: { | ||
| 72 | + title: '调度日志', | ||
| 73 | + }, | ||
| 74 | + }, | ||
| 75 | + ], | ||
| 76 | +}; | ||
| 56 | 77 | ||
| 57 | // Basic routing without permission | 78 | // Basic routing without permission |
| 58 | export const basicRoutes = [ | 79 | export const basicRoutes = [ |
| 59 | LoginRoute, | 80 | LoginRoute, |
| 60 | RootRoute, | 81 | RootRoute, |
| 61 | AccountDetail, | 82 | AccountDetail, |
| 83 | + SchedueLog, | ||
| 62 | ...mainOutRoutes, | 84 | ...mainOutRoutes, |
| 63 | REDIRECT_ROUTE, | 85 | REDIRECT_ROUTE, |
| 64 | PAGE_NOT_FOUND_ROUTE, | 86 | PAGE_NOT_FOUND_ROUTE, |
| @@ -107,7 +107,7 @@ const setting: ProjectConfig = { | @@ -107,7 +107,7 @@ const setting: ProjectConfig = { | ||
| 107 | // Fold trigger position | 107 | // Fold trigger position |
| 108 | trigger: TriggerEnum.HEADER, | 108 | trigger: TriggerEnum.HEADER, |
| 109 | // Turn on accordion mode, only show a menu | 109 | // Turn on accordion mode, only show a menu |
| 110 | - accordion: false, | 110 | + accordion: true, |
| 111 | // Switch page to close menu | 111 | // Switch page to close menu |
| 112 | closeMixSidebarOnChange: false, | 112 | closeMixSidebarOnChange: false, |
| 113 | // Module opening method ‘click’ |'hover' | 113 | // Module opening method ‘click’ |'hover' |
src/utils/common/compUtils.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * 简单实现防抖方法 | ||
| 3 | + * | ||
| 4 | + * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。 | ||
| 5 | + * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。 | ||
| 6 | + * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。 | ||
| 7 | + * | ||
| 8 | + * @param fn 要防抖的函数 | ||
| 9 | + * @param delay 防抖的毫秒数 | ||
| 10 | + * @returns {Function} | ||
| 11 | + */ | ||
| 12 | +export function simpleDebounce(fn, delay = 100) { | ||
| 13 | + let timer: any | null = null; | ||
| 14 | + return function () { | ||
| 15 | + let args = arguments; | ||
| 16 | + if (timer) { | ||
| 17 | + clearTimeout(timer); | ||
| 18 | + } | ||
| 19 | + timer = setTimeout(() => { | ||
| 20 | + // @ts-ignore | ||
| 21 | + fn.apply(this, args); | ||
| 22 | + }, delay); | ||
| 23 | + }; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +// /** | ||
| 27 | +// * 日期格式化 | ||
| 28 | +// * @param date 日期 | ||
| 29 | +// * @param block 格式化字符串 | ||
| 30 | +// */ | ||
| 31 | +export function dateFormat(date, block) { | ||
| 32 | + if (!date) { | ||
| 33 | + return ''; | ||
| 34 | + } | ||
| 35 | + let format = block || 'yyyy-MM-dd'; | ||
| 36 | + date = new Date(date); | ||
| 37 | + const map = { | ||
| 38 | + M: date.getMonth() + 1, // 月份 | ||
| 39 | + d: date.getDate(), // 日 | ||
| 40 | + h: date.getHours(), // 小时 | ||
| 41 | + m: date.getMinutes(), // 分 | ||
| 42 | + s: date.getSeconds(), // 秒 | ||
| 43 | + q: Math.floor((date.getMonth() + 3) / 3), // 季度 | ||
| 44 | + S: date.getMilliseconds(), // 毫秒 | ||
| 45 | + }; | ||
| 46 | + format = format.replace(/([yMdhmsqS])+/g, (all, t) => { | ||
| 47 | + let v = map[t]; | ||
| 48 | + if (v !== undefined) { | ||
| 49 | + if (all.length > 1) { | ||
| 50 | + v = `0${v}`; | ||
| 51 | + v = v.substr(v.length - 2); | ||
| 52 | + } | ||
| 53 | + return v; | ||
| 54 | + } else if (t === 'y') { | ||
| 55 | + return date | ||
| 56 | + .getFullYear() | ||
| 57 | + .toString() | ||
| 58 | + .substr(4 - all.length); | ||
| 59 | + } | ||
| 60 | + return all; | ||
| 61 | + }); | ||
| 62 | + return format; | ||
| 63 | +} | ||
| 64 | + |
| @@ -40,7 +40,7 @@ export const CoapSchemas: FormSchema[] = [ | @@ -40,7 +40,7 @@ export const CoapSchemas: FormSchema[] = [ | ||
| 40 | { label: 'Efento NB-IoT', value: 'EFENTO' }, | 40 | { label: 'Efento NB-IoT', value: 'EFENTO' }, |
| 41 | ], | 41 | ], |
| 42 | }, | 42 | }, |
| 43 | - colProps: { span: 11 }, | 43 | + colProps: { span: 22}, |
| 44 | }, | 44 | }, |
| 45 | { | 45 | { |
| 46 | field: 'transportPayloadType', | 46 | field: 'transportPayloadType', |
| @@ -53,7 +53,7 @@ export const CoapSchemas: FormSchema[] = [ | @@ -53,7 +53,7 @@ export const CoapSchemas: FormSchema[] = [ | ||
| 53 | { label: 'PROTOBUF', value: 'PROTOBUF' }, | 53 | { label: 'PROTOBUF', value: 'PROTOBUF' }, |
| 54 | ], | 54 | ], |
| 55 | }, | 55 | }, |
| 56 | - colProps: { span: 11 }, | 56 | + colProps: { span: 22 }, |
| 57 | ifShow: ({ values }) => !isEfentoNb(values.coapDeviceType), | 57 | ifShow: ({ values }) => !isEfentoNb(values.coapDeviceType), |
| 58 | }, | 58 | }, |
| 59 | { | 59 | { |
| 1 | <template> | 1 | <template> |
| 2 | - <div | ||
| 3 | - style=" | ||
| 4 | - margin-top: -5vh; | ||
| 5 | - padding-left: 1.5vw; | ||
| 6 | - border: 1px solid gray; | ||
| 7 | - margin-left: 0.1vw; | ||
| 8 | - border-radius: 5px; | ||
| 9 | - " | ||
| 10 | - > | 2 | + <div style="margin-top: -5vh; border: 1px solid gray; border-radius: 5px"> |
| 11 | <div style="margin-top: 1.2vh"> | 3 | <div style="margin-top: 1.2vh"> |
| 12 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="register" /> | 4 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="register" /> |
| 13 | </div> | 5 | </div> |
| @@ -16,13 +16,18 @@ | @@ -16,13 +16,18 @@ | ||
| 16 | :options="selectOptions" | 16 | :options="selectOptions" |
| 17 | @change="handleDeviceChange" | 17 | @change="handleDeviceChange" |
| 18 | mode="multiple" | 18 | mode="multiple" |
| 19 | + labelInValue | ||
| 20 | + allowClear | ||
| 21 | + @deselect="handleDeSelect" | ||
| 22 | + notFoundContent="请选择设备" | ||
| 19 | /> | 23 | /> |
| 20 | <div style="margin-top: 1.5vh"></div> | 24 | <div style="margin-top: 1.5vh"></div> |
| 21 | <DeviceAttrCpns | 25 | <DeviceAttrCpns |
| 22 | ref="deviceAttrRef" | 26 | ref="deviceAttrRef" |
| 23 | @change="handleChange" | 27 | @change="handleChange" |
| 24 | :value="deviceList" | 28 | :value="deviceList" |
| 25 | - :orgId="organizationId" | 29 | + :orgId="organizationId || orgId" |
| 30 | + :deSelectValue="deSelectValue" | ||
| 26 | /> | 31 | /> |
| 27 | </template> | 32 | </template> |
| 28 | </BasicForm> | 33 | </BasicForm> |
| @@ -64,9 +69,13 @@ | @@ -64,9 +69,13 @@ | ||
| 64 | }); | 69 | }); |
| 65 | }); | 70 | }); |
| 66 | const handleDeviceChange = (e) => { | 71 | const handleDeviceChange = (e) => { |
| 67 | - console.log('e', e); | ||
| 68 | deviceList.value = e; | 72 | deviceList.value = e; |
| 69 | }; | 73 | }; |
| 74 | + const deSelectValue = ref(''); | ||
| 75 | + const handleDeSelect = ({ key }) => { | ||
| 76 | + //取消选中的key | ||
| 77 | + deSelectValue.value = key; | ||
| 78 | + }; | ||
| 70 | const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({ | 79 | const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({ |
| 71 | labelWidth: 120, | 80 | labelWidth: 120, |
| 72 | schemas: formSchema, | 81 | schemas: formSchema, |
| @@ -101,10 +110,22 @@ | @@ -101,10 +110,22 @@ | ||
| 101 | }); | 110 | }); |
| 102 | //TODO 模拟的数据 待服务端返回 | 111 | //TODO 模拟的数据 待服务端返回 |
| 103 | const deviceIds: any = [ | 112 | const deviceIds: any = [ |
| 104 | - '8943f0b0-f1f7-11ec-98ad-a9680487d1e0', | ||
| 105 | - '8f5b4280-f29e-11ec-98ad-a9680487d1e0', | ||
| 106 | - '54e199d0-f1f7-11ec-98ad-a9680487d1e0', | ||
| 107 | - '6d9043f0-f1f7-11ec-98ad-a9680487d1e0', | 113 | + { |
| 114 | + label: '奥迪网关子设备', | ||
| 115 | + key: '8a4cc9a0-f201-11ec-98ad-a9680487d1e0', | ||
| 116 | + }, | ||
| 117 | + { | ||
| 118 | + label: '宝马默认设备', | ||
| 119 | + key: '8943f0b0-f1f7-11ec-98ad-a9680487d1e0', | ||
| 120 | + }, | ||
| 121 | + { | ||
| 122 | + label: '奔驰默认设备', | ||
| 123 | + key: '6d9043f0-f1f7-11ec-98ad-a9680487d1e0', | ||
| 124 | + }, | ||
| 125 | + { | ||
| 126 | + label: '新增奥迪测试设备', | ||
| 127 | + key: '8f5b4280-f29e-11ec-98ad-a9680487d1e0', | ||
| 128 | + }, | ||
| 108 | ]; | 129 | ]; |
| 109 | selectDevice.value = deviceIds; | 130 | selectDevice.value = deviceIds; |
| 110 | //回显设备属性 TODO 模拟的数据 待服务端返回 | 131 | //回显设备属性 TODO 模拟的数据 待服务端返回 |
| @@ -133,6 +154,7 @@ | @@ -133,6 +154,7 @@ | ||
| 133 | deviceAttrRef.value?.echoDynamicInputFunc(deviceAttrData.value); | 154 | deviceAttrRef.value?.echoDynamicInputFunc(deviceAttrData.value); |
| 134 | } else { | 155 | } else { |
| 135 | editId.value = ''; | 156 | editId.value = ''; |
| 157 | + orgId.value = ''; | ||
| 136 | selectDevice.value = []; | 158 | selectDevice.value = []; |
| 137 | selectOptions.value = []; | 159 | selectOptions.value = []; |
| 138 | deviceList.value = []; | 160 | deviceList.value = []; |
| @@ -12,14 +12,9 @@ import { | @@ -12,14 +12,9 @@ import { | ||
| 12 | isMonth, | 12 | isMonth, |
| 13 | isEmpty, | 13 | isEmpty, |
| 14 | isDefultWeek, | 14 | isDefultWeek, |
| 15 | - isMin, | ||
| 16 | - isMax, | ||
| 17 | - isAvg, | ||
| 18 | - isSum, | ||
| 19 | - isCountAll, | 15 | + isFixedWeek, |
| 20 | } from './timeConfig'; | 16 | } from './timeConfig'; |
| 21 | import { AggregateDataEnum } from '../../device/localtion/cpns/TimePeriodForm/config'; | 17 | import { AggregateDataEnum } from '../../device/localtion/cpns/TimePeriodForm/config'; |
| 22 | -// import { JCronValidator } from '/@/components/Form'; | ||
| 23 | 18 | ||
| 24 | export enum SchemaFiled { | 19 | export enum SchemaFiled { |
| 25 | WAY = 'way', | 20 | WAY = 'way', |
| @@ -208,13 +203,6 @@ export const formSchema: QFormSchema[] = [ | @@ -208,13 +203,6 @@ export const formSchema: QFormSchema[] = [ | ||
| 208 | ], | 203 | ], |
| 209 | }, | 204 | }, |
| 210 | }, | 205 | }, |
| 211 | - // { | ||
| 212 | - // field: 'cronExpression', | ||
| 213 | - // label: 'Cron表达式', | ||
| 214 | - // component: 'JEasyCron', | ||
| 215 | - // defaultValue: '* * * * * ? *', | ||
| 216 | - // rules: [{ required: true, message: '请输入Cron表达式' }, { validator: JCronValidator }], | ||
| 217 | - // }, | ||
| 218 | { | 206 | { |
| 219 | field: 'timeWeek', | 207 | field: 'timeWeek', |
| 220 | component: 'Select', | 208 | component: 'Select', |
| @@ -354,16 +342,6 @@ export const formSchema: QFormSchema[] = [ | @@ -354,16 +342,6 @@ export const formSchema: QFormSchema[] = [ | ||
| 354 | }; | 342 | }; |
| 355 | }, | 343 | }, |
| 356 | }, | 344 | }, |
| 357 | - // { | ||
| 358 | - // field: 'deviceAttr', | ||
| 359 | - // label: '设备属性', | ||
| 360 | - // component: 'Select', | ||
| 361 | - // componentProps: { | ||
| 362 | - // placeholder: '请选择设备属性', | ||
| 363 | - // }, | ||
| 364 | - // colProps: { span: 24 }, | ||
| 365 | - // slot: 'deviceAttr', | ||
| 366 | - // }, | ||
| 367 | { | 345 | { |
| 368 | field: 'dataCompare', | 346 | field: 'dataCompare', |
| 369 | label: '数据类型', | 347 | label: '数据类型', |
| @@ -576,13 +554,7 @@ export const formSchema: QFormSchema[] = [ | @@ -576,13 +554,7 @@ export const formSchema: QFormSchema[] = [ | ||
| 576 | }; | 554 | }; |
| 577 | }, | 555 | }, |
| 578 | colProps: { span: 24 }, | 556 | colProps: { span: 24 }, |
| 579 | - ifShow: ({ values }) => | ||
| 580 | - (!isEmpty(values.agg) && !isDefultWeek(values.defaultWeek)) || | ||
| 581 | - isMin(values.agg) || | ||
| 582 | - isMax(values.agg) || | ||
| 583 | - isAvg(values.agg) || | ||
| 584 | - isSum(values.agg) || | ||
| 585 | - isCountAll(values.agg), | 557 | + ifShow: ({ values }) => isFixedWeek(values.defaultWeek) && !isEmpty(values.agg), |
| 586 | }, | 558 | }, |
| 587 | { | 559 | { |
| 588 | field: 'interval', | 560 | field: 'interval', |
| @@ -600,12 +572,6 @@ export const formSchema: QFormSchema[] = [ | @@ -600,12 +572,6 @@ export const formSchema: QFormSchema[] = [ | ||
| 600 | }, | 572 | }, |
| 601 | ], | 573 | ], |
| 602 | }, | 574 | }, |
| 603 | - ifShow: ({ values }) => | ||
| 604 | - (!isEmpty(values.agg) && !isDefultWeek(values.defaultWeek)) || | ||
| 605 | - isMin(values.agg) || | ||
| 606 | - isMax(values.agg) || | ||
| 607 | - isAvg(values.agg) || | ||
| 608 | - isSum(values.agg) || | ||
| 609 | - isCountAll(values.agg), | 575 | + ifShow: ({ values }) => isFixedWeek(values.defaultWeek) && !isEmpty(values.agg), |
| 610 | }, | 576 | }, |
| 611 | ]; | 577 | ]; |
| 1 | <template> | 1 | <template> |
| 2 | <div | 2 | <div |
| 3 | - v-for="(param, index) in dynamicInput.params" | ||
| 4 | - :key="index" | 3 | + v-for="param in dynamicInput.params" |
| 4 | + :key="param.key" | ||
| 5 | style="display: flex; margin-top: 0.25vh" | 5 | style="display: flex; margin-top: 0.25vh" |
| 6 | > | 6 | > |
| 7 | <a-input | 7 | <a-input |
| @@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
| 16 | style="width: 160px; margin-left: 1.8vw" | 16 | style="width: 160px; margin-left: 1.8vw" |
| 17 | :options="selectOptions" | 17 | :options="selectOptions" |
| 18 | @change="emitChange" | 18 | @change="emitChange" |
| 19 | + allowClear | ||
| 19 | /> | 20 | /> |
| 20 | </div> | 21 | </div> |
| 21 | </template> | 22 | </template> |
| @@ -29,9 +30,7 @@ | @@ -29,9 +30,7 @@ | ||
| 29 | import { propTypes } from '/@/utils/propTypes'; | 30 | import { propTypes } from '/@/utils/propTypes'; |
| 30 | import { SelectTypes } from 'ant-design-vue/es/select'; | 31 | import { SelectTypes } from 'ant-design-vue/es/select'; |
| 31 | import { Select } from 'ant-design-vue'; | 32 | import { Select } from 'ant-design-vue'; |
| 32 | - import { screenLinkPageByDeptIdGetDevice } from '/@/api/ruleengine/ruleengineApi'; | ||
| 33 | import { getAttribute } from '/@/api/ruleengine/ruleengineApi'; | 33 | import { getAttribute } from '/@/api/ruleengine/ruleengineApi'; |
| 34 | - // import { getDeviceAttribute } from '/@/api/alarm/position'; | ||
| 35 | 34 | ||
| 36 | interface Params { | 35 | interface Params { |
| 37 | [x: string]: string; | 36 | [x: string]: string; |
| @@ -42,17 +41,11 @@ | @@ -42,17 +41,11 @@ | ||
| 42 | value: propTypes.array.def([]), | 41 | value: propTypes.array.def([]), |
| 43 | orgId: propTypes.string.def(''), | 42 | orgId: propTypes.string.def(''), |
| 44 | }); | 43 | }); |
| 45 | - const emits = defineEmits(['change', 'update:value', 'dynamicReduceHeight', 'dynamicAddHeight']); | 44 | + const emits = defineEmits(['change', 'update:value']); |
| 46 | const selectOptions = ref<SelectTypes['options']>([]); | 45 | const selectOptions = ref<SelectTypes['options']>([]); |
| 47 | - //动态数据 | ||
| 48 | - const dynamicInput: UnwrapRef<{ params: Params[] }> = reactive({ params: [] }); | ||
| 49 | - //监听传入数据value | ||
| 50 | - watchEffect(() => { | ||
| 51 | - initVal(); | ||
| 52 | - }); | ||
| 53 | //获取属性 | 46 | //获取属性 |
| 54 | - const getAttr = async (orgId, value) => { | ||
| 55 | - const res = await getAttribute(orgId, value.join(',')); | 47 | + const getAttr = async (orgId, deviceId) => { |
| 48 | + const res = await getAttribute(orgId, deviceId.join(',')); | ||
| 56 | selectOptions.value = res.map((o) => { | 49 | selectOptions.value = res.map((o) => { |
| 57 | return { | 50 | return { |
| 58 | label: o, | 51 | label: o, |
| @@ -60,43 +53,24 @@ | @@ -60,43 +53,24 @@ | ||
| 60 | }; | 53 | }; |
| 61 | }); | 54 | }); |
| 62 | }; | 55 | }; |
| 63 | - //获取设备 | ||
| 64 | - const deviceOptions: any = ref([]); | ||
| 65 | - const getDevice = async (orgId) => { | ||
| 66 | - const { items } = await screenLinkPageByDeptIdGetDevice({ | ||
| 67 | - organizationId: orgId, | ||
| 68 | - }); | ||
| 69 | - deviceOptions.value = items.map((item) => { | ||
| 70 | - return { | ||
| 71 | - label: item.name, | ||
| 72 | - value: item.tbDeviceId, | ||
| 73 | - }; | ||
| 74 | - }); | ||
| 75 | - }; | 56 | + //动态数据 |
| 57 | + const dynamicInput: UnwrapRef<{ params: Params[] }> = reactive({ params: [] }); | ||
| 58 | + //监听传入数据value | ||
| 59 | + watchEffect(() => { | ||
| 60 | + initVal(); | ||
| 61 | + }); | ||
| 76 | /** | 62 | /** |
| 77 | * 初始化数值 | 63 | * 初始化数值 |
| 78 | */ | 64 | */ |
| 79 | async function initVal() { | 65 | async function initVal() { |
| 80 | dynamicInput.params = []; | 66 | dynamicInput.params = []; |
| 81 | if (props.value && props.orgId) { | 67 | if (props.value && props.orgId) { |
| 82 | - const getPropsDevice = props.value; | ||
| 83 | - const getPropsOrgId = props.orgId; | ||
| 84 | - await getAttr(getPropsOrgId, getPropsDevice); | ||
| 85 | - await getDevice(getPropsOrgId); | ||
| 86 | - const temp: any = []; | ||
| 87 | - deviceOptions.value.forEach((f) => { | ||
| 88 | - getPropsDevice.forEach((f1) => { | ||
| 89 | - if (f1 == f.value) { | ||
| 90 | - temp.push({ | ||
| 91 | - label: f.label, | ||
| 92 | - value: f.value, | ||
| 93 | - }); | ||
| 94 | - } | ||
| 95 | - }); | ||
| 96 | - }); | ||
| 97 | - temp.forEach((item: Params) => { | 68 | + let jsonObj = props.value; |
| 69 | + const deviceId = jsonObj.map((m: any) => m.value); | ||
| 70 | + await getAttr(props.orgId, deviceId); | ||
| 71 | + jsonObj.forEach((item: any) => { | ||
| 98 | dynamicInput.params.push({ | 72 | dynamicInput.params.push({ |
| 99 | - attribute: item.attribute, | 73 | + attribute: '', |
| 100 | device: item.label, | 74 | device: item.label, |
| 101 | value: item.value, | 75 | value: item.value, |
| 102 | }); | 76 | }); |
| @@ -116,18 +90,19 @@ | @@ -116,18 +90,19 @@ | ||
| 116 | }); | 90 | }); |
| 117 | }); | 91 | }); |
| 118 | } | 92 | } |
| 93 | + console.log('emitChange', obj); | ||
| 119 | emits('change', obj); | 94 | emits('change', obj); |
| 120 | emits('update:value', obj); | 95 | emits('update:value', obj); |
| 121 | } | 96 | } |
| 122 | //回显 | 97 | //回显 |
| 123 | const echoDynamicInputFunc = (o) => { | 98 | const echoDynamicInputFunc = (o) => { |
| 124 | dynamicInput.params = []; | 99 | dynamicInput.params = []; |
| 125 | - dynamicInput.params = o.map((m) => { | ||
| 126 | - return { | 100 | + o.forEach((m: any) => { |
| 101 | + dynamicInput.params.push({ | ||
| 127 | device: m.name, | 102 | device: m.name, |
| 128 | attribute: m.attribute, | 103 | attribute: m.attribute, |
| 129 | value: m.device, | 104 | value: m.device, |
| 130 | - }; | 105 | + }); |
| 131 | }); | 106 | }); |
| 132 | }; | 107 | }; |
| 133 | defineExpose({ | 108 | defineExpose({ |
| @@ -135,21 +110,5 @@ | @@ -135,21 +110,5 @@ | ||
| 135 | }); | 110 | }); |
| 136 | </script> | 111 | </script> |
| 137 | <style scoped lang="css"> | 112 | <style scoped lang="css"> |
| 138 | - .dynamic-delete-button { | ||
| 139 | - cursor: pointer; | ||
| 140 | - position: relative; | ||
| 141 | - top: 4px; | ||
| 142 | - font-size: 24px; | ||
| 143 | - color: #999; | ||
| 144 | - transition: all 0.3s; | ||
| 145 | - } | ||
| 146 | - | ||
| 147 | - .dynamic-delete-button:hover { | ||
| 148 | - color: #777; | ||
| 149 | - } | ||
| 150 | - | ||
| 151 | - .dynamic-delete-button[disabled] { | ||
| 152 | - cursor: not-allowed; | ||
| 153 | - opacity: 0.5; | ||
| 154 | - } | 113 | + @import './deviceAttrCpns.css'; |
| 155 | </style> | 114 | </style> |
| 1 | +.dynamic-delete-button { | ||
| 2 | + cursor: pointer; | ||
| 3 | + position: relative; | ||
| 4 | + top: 4px; | ||
| 5 | + font-size: 24px; | ||
| 6 | + color: #999; | ||
| 7 | + transition: all 0.3s; | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +.dynamic-delete-button:hover { | ||
| 11 | + color: #777; | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +.dynamic-delete-button[disabled] { | ||
| 15 | + cursor: not-allowed; | ||
| 16 | + opacity: 0.5; | ||
| 17 | +} |
| @@ -44,26 +44,6 @@ | @@ -44,26 +44,6 @@ | ||
| 44 | }, | 44 | }, |
| 45 | }, | 45 | }, |
| 46 | ]" | 46 | ]" |
| 47 | - :dropDownActions="[ | ||
| 48 | - { | ||
| 49 | - label: '执行一次', | ||
| 50 | - popConfirm: { | ||
| 51 | - title: '是否执行一次?', | ||
| 52 | - }, | ||
| 53 | - }, | ||
| 54 | - { | ||
| 55 | - label: '任务详细', | ||
| 56 | - popConfirm: { | ||
| 57 | - title: '任务详细', | ||
| 58 | - }, | ||
| 59 | - }, | ||
| 60 | - { | ||
| 61 | - label: '调度日志', | ||
| 62 | - popConfirm: { | ||
| 63 | - title: '调度日志', | ||
| 64 | - }, | ||
| 65 | - }, | ||
| 66 | - ]" | ||
| 67 | /> | 47 | /> |
| 68 | </template> | 48 | </template> |
| 69 | <template #status="{ record }"> | 49 | <template #status="{ record }"> |
| @@ -80,6 +80,7 @@ export enum TypeEnum { | @@ -80,6 +80,7 @@ export enum TypeEnum { | ||
| 80 | IS_MONTH = 'month', | 80 | IS_MONTH = 'month', |
| 81 | IS_EMPTY = 'NONE', | 81 | IS_EMPTY = 'NONE', |
| 82 | IS_DEFAULT_WEEK = 'defaultIsWeek', | 82 | IS_DEFAULT_WEEK = 'defaultIsWeek', |
| 83 | + IS_FIXED_WEEK = '2', | ||
| 83 | IS_MIN = 'MIN', | 84 | IS_MIN = 'MIN', |
| 84 | IS_MAX = 'MAX', | 85 | IS_MAX = 'MAX', |
| 85 | IS_AVG = 'AVG', | 86 | IS_AVG = 'AVG', |
| @@ -115,6 +116,9 @@ export const isEmpty = (type: string) => { | @@ -115,6 +116,9 @@ export const isEmpty = (type: string) => { | ||
| 115 | export const isDefultWeek = (type: string) => { | 116 | export const isDefultWeek = (type: string) => { |
| 116 | return type === TypeEnum.IS_DEFAULT_WEEK; | 117 | return type === TypeEnum.IS_DEFAULT_WEEK; |
| 117 | }; | 118 | }; |
| 119 | +export const isFixedWeek = (type: string) => { | ||
| 120 | + return type === TypeEnum.IS_FIXED_WEEK; | ||
| 121 | +}; | ||
| 118 | 122 | ||
| 119 | export const isMin = (type: string) => { | 123 | export const isMin = (type: string) => { |
| 120 | return type === TypeEnum.IS_MIN; | 124 | return type === TypeEnum.IS_MIN; |
| @@ -137,9 +137,6 @@ | @@ -137,9 +137,6 @@ | ||
| 137 | delete newFieldValue.nameCity; | 137 | delete newFieldValue.nameCity; |
| 138 | delete newFieldValue.nameCoun; | 138 | delete newFieldValue.nameCoun; |
| 139 | delete newFieldValue.nameTown; | 139 | delete newFieldValue.nameTown; |
| 140 | - | ||
| 141 | - console.log(newFieldValue.qrCode); | ||
| 142 | - console.log(newFieldValue.codeTown); | ||
| 143 | // 表单校验 | 140 | // 表单校验 |
| 144 | let validateArray = [ | 141 | let validateArray = [ |
| 145 | 'name', | 142 | 'name', |
| @@ -153,7 +150,7 @@ | @@ -153,7 +150,7 @@ | ||
| 153 | 'tel', | 150 | 'tel', |
| 154 | 'id', | 151 | 'id', |
| 155 | ]; | 152 | ]; |
| 156 | - if (newFieldValue.qrCode == undefined) { | 153 | + if (newFieldValue.qrCode == undefined || newFieldValue.qrCode == '') { |
| 157 | validateArray.push('qrcode'); | 154 | validateArray.push('qrcode'); |
| 158 | } else { | 155 | } else { |
| 159 | const findExistIndex = validateArray.findIndex((o) => o == 'qrcode'); | 156 | const findExistIndex = validateArray.findIndex((o) => o == 'qrcode'); |
src/views/system/scheduled/SchedueLog.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <BasicModal | ||
| 4 | + v-bind="$attrs" | ||
| 5 | + width="110rem" | ||
| 6 | + :height="heightNum" | ||
| 7 | + @register="register" | ||
| 8 | + title="调度日志" | ||
| 9 | + @cancel="handleCancel" | ||
| 10 | + :showOkBtn="false" | ||
| 11 | + destroyOnClose | ||
| 12 | + > | ||
| 13 | + <div> | ||
| 14 | + <BasicTable @register="registerTable"> | ||
| 15 | + <template #toolbar> | ||
| 16 | + <Popconfirm | ||
| 17 | + title="您确定要清空全部数据" | ||
| 18 | + ok-text="确定" | ||
| 19 | + cancel-text="取消" | ||
| 20 | + @confirm="handleClear" | ||
| 21 | + > | ||
| 22 | + <a-button type="primary"> 清空 </a-button> | ||
| 23 | + </Popconfirm> | ||
| 24 | + <Popconfirm title="您确定要批量删除数据" ok-text="确定" cancel-text="取消"> | ||
| 25 | + <a-button type="primary" color="error" :disabled="hasBatchDelete"> | ||
| 26 | + 批量删除 | ||
| 27 | + </a-button> | ||
| 28 | + </Popconfirm> | ||
| 29 | + </template> | ||
| 30 | + <template #action="{ record }"> | ||
| 31 | + <TableAction | ||
| 32 | + :actions="[ | ||
| 33 | + { | ||
| 34 | + label: '查看', | ||
| 35 | + icon: 'clarity:note-edit-line', | ||
| 36 | + onClick: handleView.bind(null, record), | ||
| 37 | + }, | ||
| 38 | + { | ||
| 39 | + label: '删除', | ||
| 40 | + icon: 'ant-design:delete-outlined', | ||
| 41 | + color: 'error', | ||
| 42 | + popConfirm: { | ||
| 43 | + title: '是否确认删除', | ||
| 44 | + confirm: handleDeleteOrBatchDelete.bind(null, record), | ||
| 45 | + }, | ||
| 46 | + }, | ||
| 47 | + ]" | ||
| 48 | + /> | ||
| 49 | + </template> | ||
| 50 | + </BasicTable> | ||
| 51 | + <ScheduleLogViewModal @register="registerModalScheduleLogView" /> | ||
| 52 | + </div> | ||
| 53 | + </BasicModal> | ||
| 54 | + </div> | ||
| 55 | +</template> | ||
| 56 | +<script setup lang="ts"> | ||
| 57 | + import { ref, nextTick } from 'vue'; | ||
| 58 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 59 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | ||
| 60 | + import { columnSchedue, searchSchedueFormSchema } from './config.data'; | ||
| 61 | + import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | ||
| 62 | + import { | ||
| 63 | + deleteSchedueLogManage, | ||
| 64 | + schedueLogPage, | ||
| 65 | + schedueLogCleanPage, | ||
| 66 | + } from '/@/api/schedue/schedueManager'; | ||
| 67 | + import { Popconfirm } from 'ant-design-vue'; | ||
| 68 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 69 | + import ScheduleLogViewModal from './ScheduleLogViewModal.vue'; | ||
| 70 | + import { useModal } from '/@/components/Modal'; | ||
| 71 | + | ||
| 72 | + const { createMessage } = useMessage(); | ||
| 73 | + const heightNum = ref(800); | ||
| 74 | + const [registerTable, { setProps, reload, getForm }] = useTable({ | ||
| 75 | + title: '调度日志列表', | ||
| 76 | + api: schedueLogPage, | ||
| 77 | + columns: columnSchedue, | ||
| 78 | + showIndexColumn: false, | ||
| 79 | + clickToRowSelect: false, | ||
| 80 | + useSearchForm: true, | ||
| 81 | + ellipsis: true, | ||
| 82 | + showTableSetting: true, | ||
| 83 | + bordered: true, | ||
| 84 | + formConfig: { | ||
| 85 | + labelWidth: 120, | ||
| 86 | + schemas: searchSchedueFormSchema, | ||
| 87 | + fieldMapToTime: [['sendTime', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss']], | ||
| 88 | + }, | ||
| 89 | + rowKey: 'id', | ||
| 90 | + actionColumn: { | ||
| 91 | + width: 200, | ||
| 92 | + title: '操作', | ||
| 93 | + dataIndex: 'action', | ||
| 94 | + slots: { customRender: 'action' }, | ||
| 95 | + fixed: 'right', | ||
| 96 | + }, | ||
| 97 | + }); | ||
| 98 | + // 刷新 | ||
| 99 | + const handleSuccess = () => { | ||
| 100 | + reload(); | ||
| 101 | + }; | ||
| 102 | + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | ||
| 103 | + deleteSchedueLogManage, | ||
| 104 | + handleSuccess, | ||
| 105 | + setProps | ||
| 106 | + ); | ||
| 107 | + | ||
| 108 | + const [register] = useModalInner(() => { | ||
| 109 | + nextTick(() => { | ||
| 110 | + setProps(selectionOptions); | ||
| 111 | + setProps({ | ||
| 112 | + rowKey: 'id', | ||
| 113 | + }); | ||
| 114 | + //重置清空搜索表单 | ||
| 115 | + const { resetFields } = getForm(); | ||
| 116 | + resetFields(); | ||
| 117 | + }); | ||
| 118 | + }); | ||
| 119 | + const handleCancel = () => {}; | ||
| 120 | + const handleClear = async () => { | ||
| 121 | + await schedueLogCleanPage(); | ||
| 122 | + createMessage.success(`清空成功`); | ||
| 123 | + handleSuccess(); | ||
| 124 | + }; | ||
| 125 | + const [registerModalScheduleLogView, { openModal: openModalLogView }] = useModal(); | ||
| 126 | + const handleView = (record: Recordable) => { | ||
| 127 | + openModalLogView(true, { | ||
| 128 | + isUpdate: true, | ||
| 129 | + record, | ||
| 130 | + }); | ||
| 131 | + }; | ||
| 132 | +</script> | ||
| 133 | +<style lang="less" scoped></style> |
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <BasicModal | ||
| 4 | + v-bind="$attrs" | ||
| 5 | + width="62rem" | ||
| 6 | + :height="heightNum" | ||
| 7 | + @register="register" | ||
| 8 | + title="调度日志详细信息" | ||
| 9 | + @cancel="handleCancel" | ||
| 10 | + :showOkBtn="false" | ||
| 11 | + style="font-size: 12px" | ||
| 12 | + > | ||
| 13 | + <div> | ||
| 14 | + <Description :column="3" size="middle" @register="registeDesc" /> | ||
| 15 | + </div> | ||
| 16 | + </BasicModal> | ||
| 17 | + </div> | ||
| 18 | +</template> | ||
| 19 | +<script setup lang="ts"> | ||
| 20 | + import { ref, nextTick, reactive } from 'vue'; | ||
| 21 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 22 | + import { scheduleLogDetailSchema } from './config.data'; | ||
| 23 | + import { Description } from '/@/components/Description/index'; | ||
| 24 | + import { useDescription } from '/@/components/Description'; | ||
| 25 | + import { schedueLogDetailPage } from '/@/api/schedue/schedueManager'; | ||
| 26 | + | ||
| 27 | + const heightNum = ref(800); | ||
| 28 | + let personData = reactive({}); | ||
| 29 | + const [registeDesc, { setDescProps }] = useDescription({ | ||
| 30 | + title: '调度日志详细信息', | ||
| 31 | + data: personData, | ||
| 32 | + schema: scheduleLogDetailSchema, | ||
| 33 | + column: 3, | ||
| 34 | + }); | ||
| 35 | + const [register] = useModalInner(async (data) => { | ||
| 36 | + const res = await schedueLogDetailPage(data.record.jobId, data.record.id); | ||
| 37 | + if (res.code === 200) { | ||
| 38 | + nextTick(() => { | ||
| 39 | + for (let i in res.data) { | ||
| 40 | + Reflect.set(personData, i, res.data[i]); | ||
| 41 | + } | ||
| 42 | + setDescProps(personData); | ||
| 43 | + }); | ||
| 44 | + } | ||
| 45 | + }); | ||
| 46 | + const handleCancel = () => {}; | ||
| 47 | +</script> | ||
| 48 | +<style lang="less" scoped> | ||
| 49 | + :deep(.vben-basic-title-normal) { | ||
| 50 | + font-size: 16px; | ||
| 51 | + font-weight: 500; | ||
| 52 | + } | ||
| 53 | + :deep(.vben-collapse-container__header) { | ||
| 54 | + border-bottom: none; | ||
| 55 | + } | ||
| 56 | +</style> |
| 1 | +<template> | ||
| 2 | + <BasicDrawer | ||
| 3 | + v-bind="$attrs" | ||
| 4 | + @register="registerDrawer" | ||
| 5 | + showFooter | ||
| 6 | + :title="getTitle" | ||
| 7 | + width="30%" | ||
| 8 | + @ok="handleSubmit" | ||
| 9 | + > | ||
| 10 | + <BasicForm @register="registerForm" /> | ||
| 11 | + </BasicDrawer> | ||
| 12 | +</template> | ||
| 13 | +<script lang="ts" setup> | ||
| 14 | + import { ref, computed, unref, reactive } from 'vue'; | ||
| 15 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
| 16 | + import { formSchema } from './config.form.data'; | ||
| 17 | + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | ||
| 18 | + import { createOrEditSchedueManage, putSchedueConfigManage } from '/@/api/schedue/schedueManager'; | ||
| 19 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 20 | + | ||
| 21 | + const emit = defineEmits(['success', 'register']); | ||
| 22 | + const isUpdate = ref(true); | ||
| 23 | + const editId = ref(''); | ||
| 24 | + const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({ | ||
| 25 | + labelWidth: 120, | ||
| 26 | + schemas: formSchema, | ||
| 27 | + showActionButtonGroup: false, | ||
| 28 | + fieldMapToTime: [['timeZone', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss']], | ||
| 29 | + }); | ||
| 30 | + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { | ||
| 31 | + await resetFields(); | ||
| 32 | + setDrawerProps({ confirmLoading: false }); | ||
| 33 | + isUpdate.value = !!data?.isUpdate; | ||
| 34 | + if (unref(isUpdate)) { | ||
| 35 | + //回显基础数据 | ||
| 36 | + editId.value = data.record.id; | ||
| 37 | + await setFieldsValue(data.record); | ||
| 38 | + } else { | ||
| 39 | + editId.value = ''; | ||
| 40 | + } | ||
| 41 | + }); | ||
| 42 | + const getTitle = computed(() => (!unref(isUpdate) ? '新增定时任务' : '编辑定时任务')); | ||
| 43 | + let postObj: any = reactive({}); | ||
| 44 | + async function handleSubmit() { | ||
| 45 | + setDrawerProps({ confirmLoading: true }); | ||
| 46 | + try { | ||
| 47 | + const { createMessage } = useMessage(); | ||
| 48 | + const values = await validate(); | ||
| 49 | + if (!values) return; | ||
| 50 | + postObj = { | ||
| 51 | + ...values, | ||
| 52 | + ...{ id: editId.value !== '' ? editId.value : '' }, | ||
| 53 | + }; | ||
| 54 | + let saveMessage = '添加成功'; | ||
| 55 | + let updateMessage = '修改成功'; | ||
| 56 | + editId.value !== '' | ||
| 57 | + ? await putSchedueConfigManage(postObj) | ||
| 58 | + : await createOrEditSchedueManage(postObj); | ||
| 59 | + | ||
| 60 | + closeDrawer(); | ||
| 61 | + emit('success'); | ||
| 62 | + createMessage.success(unref(isUpdate) ? updateMessage : saveMessage); | ||
| 63 | + } finally { | ||
| 64 | + setTimeout(() => { | ||
| 65 | + setDrawerProps({ confirmLoading: false }); | ||
| 66 | + }, 300); | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | +</script> |
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <BasicModal | ||
| 4 | + v-bind="$attrs" | ||
| 5 | + width="62rem" | ||
| 6 | + :height="heightNum" | ||
| 7 | + @register="register" | ||
| 8 | + title="任务详细" | ||
| 9 | + @cancel="handleCancel" | ||
| 10 | + :showOkBtn="false" | ||
| 11 | + style="font-size: 12px" | ||
| 12 | + > | ||
| 13 | + <div> | ||
| 14 | + <Description :column="3" size="middle" @register="registeDesc" /> | ||
| 15 | + </div> | ||
| 16 | + </BasicModal> | ||
| 17 | + </div> | ||
| 18 | +</template> | ||
| 19 | +<script setup lang="ts"> | ||
| 20 | + import { ref, nextTick, reactive } from 'vue'; | ||
| 21 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 22 | + import { personSchema } from './config.data'; | ||
| 23 | + import { Description } from '/@/components/Description/index'; | ||
| 24 | + import { useDescription } from '/@/components/Description'; | ||
| 25 | + | ||
| 26 | + const heightNum = ref(800); | ||
| 27 | + let personData = reactive({}); | ||
| 28 | + const [registeDesc, { setDescProps }] = useDescription({ | ||
| 29 | + title: '任务详细信息', | ||
| 30 | + data: personData, | ||
| 31 | + schema: personSchema, | ||
| 32 | + column: 3, | ||
| 33 | + }); | ||
| 34 | + const [register] = useModalInner((data) => { | ||
| 35 | + nextTick(() => { | ||
| 36 | + //回显 | ||
| 37 | + for (let i in data.record) { | ||
| 38 | + Reflect.set(personData, i, data.record[i]); | ||
| 39 | + } | ||
| 40 | + setDescProps(personData); | ||
| 41 | + }); | ||
| 42 | + }); | ||
| 43 | + const handleCancel = () => {}; | ||
| 44 | +</script> | ||
| 45 | +<style lang="less" scoped> | ||
| 46 | + :deep(.vben-basic-title-normal) { | ||
| 47 | + font-size: 16px; | ||
| 48 | + font-weight: 500; | ||
| 49 | + } | ||
| 50 | + :deep(.vben-collapse-container__header) { | ||
| 51 | + border-bottom: none; | ||
| 52 | + } | ||
| 53 | +</style> |
src/views/system/scheduled/config.data.ts
0 → 100644
| 1 | +import { DescItem } from '/@/components/Description/index'; | ||
| 2 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | ||
| 3 | +import moment from 'moment'; | ||
| 4 | +import { h } from 'vue'; | ||
| 5 | +import { Tag } from 'ant-design-vue'; | ||
| 6 | + | ||
| 7 | +//任务详细配置 | ||
| 8 | +export const personSchema: DescItem[] = [ | ||
| 9 | + { | ||
| 10 | + field: 'jobGroup', | ||
| 11 | + label: '任务分组:', | ||
| 12 | + render: (_, data) => { | ||
| 13 | + return data.jobGroup == 'Default' ? '默认' : data.jobGroup == 'System' ? '系统' : '报表'; | ||
| 14 | + }, | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + field: 'jobName', | ||
| 18 | + label: '任务名称:', | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + field: 'createTime', | ||
| 22 | + label: '创建时间:', | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + field: 'cronExpression', | ||
| 26 | + label: 'cron表达式:', | ||
| 27 | + }, | ||
| 28 | + { | ||
| 29 | + field: 'b6', | ||
| 30 | + label: '下次执行时间:', | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + field: 'invokeTarget', | ||
| 34 | + label: '调用目标方法:', | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + field: 'status', | ||
| 38 | + label: '任务状态:', | ||
| 39 | + render: (_, data) => { | ||
| 40 | + return data.status == 1 ? '启用' : '禁用'; | ||
| 41 | + }, | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + field: 'b9', | ||
| 45 | + label: '是否并发:', | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + field: 'b10', | ||
| 49 | + label: '执行策略:', | ||
| 50 | + }, | ||
| 51 | +]; | ||
| 52 | + | ||
| 53 | +// 调度日志表格配置 | ||
| 54 | +export const columnSchedue: BasicColumn[] = [ | ||
| 55 | + { | ||
| 56 | + title: '任务名称', | ||
| 57 | + dataIndex: 'jobName', | ||
| 58 | + width: 120, | ||
| 59 | + }, | ||
| 60 | + { | ||
| 61 | + title: '任务组名', | ||
| 62 | + dataIndex: 'jobGroup', | ||
| 63 | + width: 120, | ||
| 64 | + format: (_text: string, record: Recordable) => { | ||
| 65 | + return record.jobGroup === 'Default' | ||
| 66 | + ? '默认' | ||
| 67 | + : record.jobGroup === 'System' | ||
| 68 | + ? '系统' | ||
| 69 | + : '报表'; | ||
| 70 | + }, | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + title: '调用目标字符串', | ||
| 74 | + dataIndex: 'invokeTarget', | ||
| 75 | + width: 120, | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + title: '日志信息', | ||
| 79 | + dataIndex: 'jobMessage', | ||
| 80 | + width: 160, | ||
| 81 | + }, | ||
| 82 | + { | ||
| 83 | + title: '执行状态', | ||
| 84 | + dataIndex: 'status', | ||
| 85 | + width: 160, | ||
| 86 | + customRender: ({ record }) => { | ||
| 87 | + const status = record.status; | ||
| 88 | + const success = status === 1; | ||
| 89 | + const color = success ? 'green' : 'red'; | ||
| 90 | + const successText: string = '成功'; | ||
| 91 | + const failedText: string = '失败'; | ||
| 92 | + const text = success ? successText : failedText; | ||
| 93 | + return h(Tag, { color: color }, () => text); | ||
| 94 | + }, | ||
| 95 | + }, | ||
| 96 | + { | ||
| 97 | + title: '执行时间', | ||
| 98 | + dataIndex: 'startTime', | ||
| 99 | + width: 180, | ||
| 100 | + }, | ||
| 101 | +]; | ||
| 102 | + | ||
| 103 | +// 调度日志表格查询配置 | ||
| 104 | +export const searchSchedueFormSchema: FormSchema[] = [ | ||
| 105 | + { | ||
| 106 | + field: 'jobName', | ||
| 107 | + label: '任务名称', | ||
| 108 | + component: 'Input', | ||
| 109 | + colProps: { span: 4 }, | ||
| 110 | + componentProps: { | ||
| 111 | + maxLength: 36, | ||
| 112 | + placeholder: '请输入任务名称', | ||
| 113 | + }, | ||
| 114 | + }, | ||
| 115 | + { | ||
| 116 | + field: 'jobGroup', | ||
| 117 | + label: '任务组名', | ||
| 118 | + component: 'Select', | ||
| 119 | + colProps: { span: 4 }, | ||
| 120 | + componentProps: { | ||
| 121 | + options: [ | ||
| 122 | + { | ||
| 123 | + label: '默认', | ||
| 124 | + value: 'Default', | ||
| 125 | + }, | ||
| 126 | + { | ||
| 127 | + label: '系统', | ||
| 128 | + value: 'System', | ||
| 129 | + }, | ||
| 130 | + { | ||
| 131 | + label: '报表', | ||
| 132 | + value: 'Report', | ||
| 133 | + }, | ||
| 134 | + ], | ||
| 135 | + placeholder: '请选择任务组名', | ||
| 136 | + }, | ||
| 137 | + }, | ||
| 138 | + { | ||
| 139 | + field: 'status', | ||
| 140 | + label: '执行状态', | ||
| 141 | + component: 'Select', | ||
| 142 | + colProps: { span: 4 }, | ||
| 143 | + componentProps: { | ||
| 144 | + options: [ | ||
| 145 | + { | ||
| 146 | + label: '成功', | ||
| 147 | + value: 1, | ||
| 148 | + }, | ||
| 149 | + { | ||
| 150 | + label: '失败', | ||
| 151 | + value: 0, | ||
| 152 | + }, | ||
| 153 | + ], | ||
| 154 | + placeholder: '请选择执行状态', | ||
| 155 | + }, | ||
| 156 | + }, | ||
| 157 | + { | ||
| 158 | + field: 'sendTime', | ||
| 159 | + label: '执行时间', | ||
| 160 | + component: 'RangePicker', | ||
| 161 | + componentProps: { | ||
| 162 | + showTime: { | ||
| 163 | + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], | ||
| 164 | + }, | ||
| 165 | + }, | ||
| 166 | + colProps: { span: 4 }, | ||
| 167 | + }, | ||
| 168 | +]; | ||
| 169 | +// 调度日志详情 | ||
| 170 | +export const scheduleLogDetailSchema: DescItem[] = [ | ||
| 171 | + { | ||
| 172 | + field: 'jobName', | ||
| 173 | + label: '任务名称:', | ||
| 174 | + }, | ||
| 175 | + { | ||
| 176 | + field: 'jobGroup', | ||
| 177 | + label: '任务组名:', | ||
| 178 | + render: (_, data) => { | ||
| 179 | + return data.jobGroup == 'Default' ? '默认' : data.jobGroup == 'System' ? '系统' : '报表'; | ||
| 180 | + }, | ||
| 181 | + }, | ||
| 182 | + { | ||
| 183 | + field: 'invokeTarget', | ||
| 184 | + label: '调用目标字符串:', | ||
| 185 | + }, | ||
| 186 | + { | ||
| 187 | + field: 'jobMessage', | ||
| 188 | + label: '日志信息:', | ||
| 189 | + }, | ||
| 190 | + { | ||
| 191 | + field: 'status', | ||
| 192 | + label: '执行状态:', | ||
| 193 | + render: (_, data) => { | ||
| 194 | + return data.status == 1 ? '成功' : '失败'; | ||
| 195 | + }, | ||
| 196 | + }, | ||
| 197 | + { | ||
| 198 | + field: 'startTime', | ||
| 199 | + label: '执行时间:', | ||
| 200 | + }, | ||
| 201 | +]; |
| 1 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | ||
| 2 | +import type { FormSchema as QFormSchema } from '/@/components/Form/index'; | ||
| 3 | +import { JCronValidator } from '/@/components/Form'; | ||
| 4 | + | ||
| 5 | +// 定时任务表格配置 | ||
| 6 | +export const columnSchedue: BasicColumn[] = [ | ||
| 7 | + { | ||
| 8 | + title: '任务名称', | ||
| 9 | + dataIndex: 'jobName', | ||
| 10 | + width: 120, | ||
| 11 | + }, | ||
| 12 | + { | ||
| 13 | + title: '任务组名', | ||
| 14 | + dataIndex: 'jobGroup', | ||
| 15 | + width: 120, | ||
| 16 | + format: (_text: string, record: Recordable) => { | ||
| 17 | + return record.jobGroup === 'Default' | ||
| 18 | + ? '默认' | ||
| 19 | + : record.jobGroup === 'System' | ||
| 20 | + ? '系统' | ||
| 21 | + : '报表'; | ||
| 22 | + }, | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + title: '调用目标字符串', | ||
| 26 | + dataIndex: 'invokeTarget', | ||
| 27 | + width: 120, | ||
| 28 | + }, | ||
| 29 | + { | ||
| 30 | + title: 'cron执行表达式', | ||
| 31 | + dataIndex: 'cronExpression', | ||
| 32 | + width: 160, | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + title: '状态', | ||
| 36 | + dataIndex: 'status', | ||
| 37 | + width: 160, | ||
| 38 | + slots: { customRender: 'status' }, | ||
| 39 | + }, | ||
| 40 | +]; | ||
| 41 | + | ||
| 42 | +// 定时任务格查询配置 | ||
| 43 | +export const searchSchedueFormSchema: FormSchema[] = [ | ||
| 44 | + { | ||
| 45 | + field: 'jobName', | ||
| 46 | + label: '任务名称', | ||
| 47 | + component: 'Input', | ||
| 48 | + colProps: { span: 6 }, | ||
| 49 | + componentProps: { | ||
| 50 | + maxLength: 36, | ||
| 51 | + placeholder: '请输入任务名称', | ||
| 52 | + }, | ||
| 53 | + }, | ||
| 54 | + { | ||
| 55 | + field: 'jobGroup', | ||
| 56 | + label: '任务组名', | ||
| 57 | + component: 'Select', | ||
| 58 | + colProps: { span: 6 }, | ||
| 59 | + componentProps: { | ||
| 60 | + options: [ | ||
| 61 | + { | ||
| 62 | + label: '默认', | ||
| 63 | + value: 'Default', | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + label: '系统', | ||
| 67 | + value: 'System', | ||
| 68 | + }, | ||
| 69 | + { | ||
| 70 | + label: '报表', | ||
| 71 | + value: 'Report', | ||
| 72 | + }, | ||
| 73 | + ], | ||
| 74 | + placeholder: '请选择任务组名', | ||
| 75 | + }, | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + field: 'status', | ||
| 79 | + label: '任务状态', | ||
| 80 | + component: 'Select', | ||
| 81 | + colProps: { span: 6 }, | ||
| 82 | + componentProps: { | ||
| 83 | + options: [ | ||
| 84 | + { | ||
| 85 | + label: '正常', | ||
| 86 | + value: 1, | ||
| 87 | + }, | ||
| 88 | + { | ||
| 89 | + label: '暂停', | ||
| 90 | + value: 0, | ||
| 91 | + }, | ||
| 92 | + ], | ||
| 93 | + placeholder: '请选择任务状态', | ||
| 94 | + }, | ||
| 95 | + }, | ||
| 96 | +]; | ||
| 97 | + | ||
| 98 | +// 新增编辑配置 | ||
| 99 | +export const formSchema: QFormSchema[] = [ | ||
| 100 | + { | ||
| 101 | + field: 'jobName', | ||
| 102 | + label: '任务名称', | ||
| 103 | + colProps: { span: 24 }, | ||
| 104 | + required: true, | ||
| 105 | + component: 'Input', | ||
| 106 | + componentProps: { | ||
| 107 | + maxLength: 255, | ||
| 108 | + placeholder: '请输入任务名称', | ||
| 109 | + }, | ||
| 110 | + }, | ||
| 111 | + { | ||
| 112 | + field: 'jobGroup', | ||
| 113 | + component: 'Select', | ||
| 114 | + label: '任务分组', | ||
| 115 | + colProps: { | ||
| 116 | + span: 24, | ||
| 117 | + }, | ||
| 118 | + componentProps: { | ||
| 119 | + placeholder: '请选择任务分组', | ||
| 120 | + options: [ | ||
| 121 | + { | ||
| 122 | + label: '默认', | ||
| 123 | + value: 'Default', | ||
| 124 | + }, | ||
| 125 | + { | ||
| 126 | + label: '系统', | ||
| 127 | + value: 'System', | ||
| 128 | + }, | ||
| 129 | + { | ||
| 130 | + label: '报表', | ||
| 131 | + value: 'Report', | ||
| 132 | + }, | ||
| 133 | + ], | ||
| 134 | + }, | ||
| 135 | + }, | ||
| 136 | + { | ||
| 137 | + field: 'invokeTarget', | ||
| 138 | + label: ' 调用方法', | ||
| 139 | + helpMessage: [ | ||
| 140 | + 'Bean调用示例:reportTask.noParams()Class类调用示例:reportTask.noParams()参数说明:支持字符串,布尔类型,长整型,浮点型,整型', | ||
| 141 | + ], | ||
| 142 | + colProps: { span: 24 }, | ||
| 143 | + required: true, | ||
| 144 | + component: 'Input', | ||
| 145 | + componentProps: { | ||
| 146 | + maxLength: 255, | ||
| 147 | + placeholder: '请输入调用方法', | ||
| 148 | + }, | ||
| 149 | + }, | ||
| 150 | + { | ||
| 151 | + field: 'cronExpression', | ||
| 152 | + label: 'Cron表达式', | ||
| 153 | + component: 'JEasyCron', | ||
| 154 | + defaultValue: '* * * * * ? *', | ||
| 155 | + colProps: { | ||
| 156 | + span: 24, | ||
| 157 | + }, | ||
| 158 | + rules: [{ required: true, message: '请输入Cron表达式' }, { validator: JCronValidator }], | ||
| 159 | + }, | ||
| 160 | + { | ||
| 161 | + field: 'field10', | ||
| 162 | + component: 'RadioButtonGroup', | ||
| 163 | + label: '执行策略', | ||
| 164 | + colProps: { | ||
| 165 | + span: 24, | ||
| 166 | + }, | ||
| 167 | + defaultValue: '1', | ||
| 168 | + componentProps: { | ||
| 169 | + options: [ | ||
| 170 | + { | ||
| 171 | + label: '立即执行', | ||
| 172 | + value: '1', | ||
| 173 | + }, | ||
| 174 | + { | ||
| 175 | + label: '执行一次', | ||
| 176 | + value: '2', | ||
| 177 | + }, | ||
| 178 | + { | ||
| 179 | + label: '放弃执行', | ||
| 180 | + value: '3', | ||
| 181 | + }, | ||
| 182 | + ], | ||
| 183 | + }, | ||
| 184 | + }, | ||
| 185 | + { | ||
| 186 | + field: 'field11', | ||
| 187 | + component: 'RadioButtonGroup', | ||
| 188 | + label: '是否并发', | ||
| 189 | + colProps: { | ||
| 190 | + span: 24, | ||
| 191 | + }, | ||
| 192 | + defaultValue: '2', | ||
| 193 | + componentProps: { | ||
| 194 | + options: [ | ||
| 195 | + { | ||
| 196 | + label: '允许', | ||
| 197 | + value: '1', | ||
| 198 | + }, | ||
| 199 | + { | ||
| 200 | + label: '禁止', | ||
| 201 | + value: '2', | ||
| 202 | + }, | ||
| 203 | + ], | ||
| 204 | + }, | ||
| 205 | + }, | ||
| 206 | + { | ||
| 207 | + field: 'status', | ||
| 208 | + component: 'RadioGroup', | ||
| 209 | + label: '状态', | ||
| 210 | + colProps: { | ||
| 211 | + span: 24, | ||
| 212 | + }, | ||
| 213 | + defaultValue: 1, | ||
| 214 | + componentProps: { | ||
| 215 | + options: [ | ||
| 216 | + { | ||
| 217 | + label: '正常', | ||
| 218 | + value: 1, | ||
| 219 | + }, | ||
| 220 | + { | ||
| 221 | + label: '暂停', | ||
| 222 | + value: 0, | ||
| 223 | + }, | ||
| 224 | + ], | ||
| 225 | + }, | ||
| 226 | + }, | ||
| 227 | +]; |
src/views/system/scheduled/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <BasicTable @register="registerTable"> | ||
| 4 | + <template #toolbar> | ||
| 5 | + <Authority value="api:yt:schedule:post"> | ||
| 6 | + <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增 </a-button> | ||
| 7 | + </Authority> | ||
| 8 | + <Authority value="api:yt:schedule:get"> | ||
| 9 | + <a-button type="primary"> 导出 </a-button> | ||
| 10 | + </Authority> | ||
| 11 | + <Authority value="api:yt:schedule:delete"> | ||
| 12 | + <Popconfirm title="您确定要批量删除数据" ok-text="确定" cancel-text="取消"> | ||
| 13 | + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button> | ||
| 14 | + </Popconfirm> | ||
| 15 | + </Authority> | ||
| 16 | + </template> | ||
| 17 | + <template #action="{ record }"> | ||
| 18 | + <TableAction | ||
| 19 | + :actions="[ | ||
| 20 | + { | ||
| 21 | + label: '编辑', | ||
| 22 | + icon: 'clarity:note-edit-line', | ||
| 23 | + auth: 'api:yt:schedule:update', | ||
| 24 | + onClick: handleCreateOrEdit.bind(null, record), | ||
| 25 | + }, | ||
| 26 | + { | ||
| 27 | + label: '删除', | ||
| 28 | + icon: 'ant-design:delete-outlined', | ||
| 29 | + color: 'error', | ||
| 30 | + auth: 'api:yt:schedule:delete', | ||
| 31 | + popConfirm: { | ||
| 32 | + title: '是否确认删除', | ||
| 33 | + confirm: handleDeleteOrBatchDelete.bind(null, record), | ||
| 34 | + }, | ||
| 35 | + }, | ||
| 36 | + ]" | ||
| 37 | + :dropDownActions="[ | ||
| 38 | + { | ||
| 39 | + label: '执行一次', | ||
| 40 | + icon: 'ant-design:caret-right-filled', | ||
| 41 | + popConfirm: { | ||
| 42 | + title: '确认要立即执行一次' + '“' + record.jobName + '”' + '任务吗?', | ||
| 43 | + confirm: handleRunOne.bind(null, record), | ||
| 44 | + }, | ||
| 45 | + }, | ||
| 46 | + { | ||
| 47 | + label: '任务详细', | ||
| 48 | + icon: 'ant-design:eye-outlined', | ||
| 49 | + onClick: handleTaskDetailModal.bind(null, record), | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + label: '调度日志', | ||
| 53 | + icon: 'ant-design:insert-row-below-outlined', | ||
| 54 | + onClick: handleSchedulingLogFunc.bind(null, record), | ||
| 55 | + }, | ||
| 56 | + ]" | ||
| 57 | + /> | ||
| 58 | + </template> | ||
| 59 | + <template #status="{ record }"> | ||
| 60 | + <Switch | ||
| 61 | + :disabled="disabledSwitch" | ||
| 62 | + :checked="record.status === 1" | ||
| 63 | + :loading="record.pendingStatus" | ||
| 64 | + checkedChildren="启用" | ||
| 65 | + unCheckedChildren="禁用" | ||
| 66 | + @change="(checked:boolean)=>statusChange(checked,record)" | ||
| 67 | + /> | ||
| 68 | + </template> | ||
| 69 | + </BasicTable> | ||
| 70 | + <ScheduledDrawer @register="registerDrawer" @success="handleSuccess" /> | ||
| 71 | + <TaskDetailPreviewModal @register="registerModalTaskDetail" /> | ||
| 72 | + <SchedueLog @register="registerModalSchedueLog" /> | ||
| 73 | + </div> | ||
| 74 | +</template> | ||
| 75 | +<script setup lang="ts"> | ||
| 76 | + import { nextTick, ref } from 'vue'; | ||
| 77 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | ||
| 78 | + import { columnSchedue, searchSchedueFormSchema } from './config.form.data'; | ||
| 79 | + import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | ||
| 80 | + import { | ||
| 81 | + deleteSchedueManage, | ||
| 82 | + scheduePage, | ||
| 83 | + putSchedueByidAndStatusManage, | ||
| 84 | + postRunSchedueConfigManage, | ||
| 85 | + } from '/@/api/schedue/schedueManager'; | ||
| 86 | + import { Popconfirm, Switch } from 'ant-design-vue'; | ||
| 87 | + import { useModal } from '/@/components/Modal'; | ||
| 88 | + import TaskDetailPreviewModal from './TaskDetailPreviewModal.vue'; | ||
| 89 | + import SchedueLog from './SchedueLog.vue'; | ||
| 90 | + import ScheduledDrawer from './ScheduledDrawer.vue'; | ||
| 91 | + import { useDrawer } from '/@/components/Drawer'; | ||
| 92 | + import { Authority } from '/@/components/Authority'; | ||
| 93 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 94 | + | ||
| 95 | + const disabledSwitch = ref(false); | ||
| 96 | + const { createMessage } = useMessage(); | ||
| 97 | + const [registerTable, { setProps, reload }] = useTable({ | ||
| 98 | + title: '定时任务列表', | ||
| 99 | + api: scheduePage, | ||
| 100 | + columns: columnSchedue, | ||
| 101 | + showIndexColumn: false, | ||
| 102 | + clickToRowSelect: false, | ||
| 103 | + useSearchForm: true, | ||
| 104 | + ellipsis: true, | ||
| 105 | + showTableSetting: true, | ||
| 106 | + bordered: true, | ||
| 107 | + formConfig: { | ||
| 108 | + labelWidth: 120, | ||
| 109 | + schemas: searchSchedueFormSchema, | ||
| 110 | + fieldMapToTime: [['sendTime', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss']], | ||
| 111 | + }, | ||
| 112 | + actionColumn: { | ||
| 113 | + width: 200, | ||
| 114 | + title: '操作', | ||
| 115 | + dataIndex: 'action', | ||
| 116 | + slots: { customRender: 'action' }, | ||
| 117 | + fixed: 'right', | ||
| 118 | + }, | ||
| 119 | + }); | ||
| 120 | + // 刷新 | ||
| 121 | + const handleSuccess = () => { | ||
| 122 | + reload(); | ||
| 123 | + }; | ||
| 124 | + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | ||
| 125 | + deleteSchedueManage, | ||
| 126 | + handleSuccess, | ||
| 127 | + setProps | ||
| 128 | + ); | ||
| 129 | + | ||
| 130 | + nextTick(() => { | ||
| 131 | + setProps(selectionOptions); | ||
| 132 | + }); | ||
| 133 | + const [registerDrawer, { openDrawer }] = useDrawer(); | ||
| 134 | + const [registerModalTaskDetail, { openModal: openModalTaskDetail }] = useModal(); | ||
| 135 | + const [registerModalSchedueLog, { openModal: openModalSchedueLog }] = useModal(); | ||
| 136 | + const handleSchedulingLogFunc = (record: Recordable) => { | ||
| 137 | + openModalSchedueLog(true, { | ||
| 138 | + isUpdate: 2, | ||
| 139 | + record, | ||
| 140 | + }); | ||
| 141 | + }; | ||
| 142 | + const handleTaskDetailModal = (record: Recordable) => { | ||
| 143 | + openModalTaskDetail(true, { | ||
| 144 | + isUpdate: true, | ||
| 145 | + record, | ||
| 146 | + }); | ||
| 147 | + }; | ||
| 148 | + // 新增或编辑 | ||
| 149 | + const handleCreateOrEdit = (record: Recordable | null) => { | ||
| 150 | + if (record) { | ||
| 151 | + openDrawer(true, { | ||
| 152 | + isUpdate: true, | ||
| 153 | + record, | ||
| 154 | + }); | ||
| 155 | + } else { | ||
| 156 | + openDrawer(true, { | ||
| 157 | + isUpdate: false, | ||
| 158 | + }); | ||
| 159 | + } | ||
| 160 | + }; | ||
| 161 | + | ||
| 162 | + const statusChange = async (checked, record) => { | ||
| 163 | + try { | ||
| 164 | + setProps({ | ||
| 165 | + loading: true, | ||
| 166 | + }); | ||
| 167 | + disabledSwitch.value = true; | ||
| 168 | + const newStatus = checked ? 1 : 0; | ||
| 169 | + const res = await putSchedueByidAndStatusManage(record.id, newStatus); | ||
| 170 | + if (res && newStatus) { | ||
| 171 | + createMessage.success(`启用成功`); | ||
| 172 | + } else { | ||
| 173 | + createMessage.success('禁用成功'); | ||
| 174 | + } | ||
| 175 | + } finally { | ||
| 176 | + setTimeout(() => { | ||
| 177 | + setProps({ | ||
| 178 | + loading: false, | ||
| 179 | + }); | ||
| 180 | + disabledSwitch.value = false; | ||
| 181 | + }, 500); | ||
| 182 | + reload(); | ||
| 183 | + } | ||
| 184 | + }; | ||
| 185 | + const handleRunOne = async (record: Recordable) => { | ||
| 186 | + const res = await postRunSchedueConfigManage(record.id); | ||
| 187 | + if (res?.code === 200) { | ||
| 188 | + createMessage.success(`执行一次任务"${record.jobName}"成功`); | ||
| 189 | + } | ||
| 190 | + }; | ||
| 191 | +</script> | ||
| 192 | +<style lang="less" scoped></style> |