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> |