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 | 2 | * Introduces component library styles on demand. |
3 | 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 | + | |
\ No newline at end of file | ... | ... |
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 | 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; |
12 | 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 | 22 | export { BasicForm }; | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import { CountdownInput } from '/@/components/CountDown'; |
30 | 30 | import ApiRadioGroup from './components/ApiRadioGroup.vue'; |
31 | 31 | //自定义组件 |
32 | 32 | import JAddInput from './jeecg/components/JAddInput.vue'; |
33 | +import { JEasyCron } from './jeecg/components/JEasyCron'; | |
33 | 34 | |
34 | 35 | const componentMap = new Map<ComponentType, Component>(); |
35 | 36 | |
... | ... | @@ -67,6 +68,7 @@ componentMap.set('InputCountDown', CountdownInput); |
67 | 68 | componentMap.set('Upload', BasicUpload); |
68 | 69 | //注册自定义组件 |
69 | 70 | componentMap.set('JAddInput', JAddInput); |
71 | +componentMap.set('JEasyCron', JEasyCron); | |
70 | 72 | |
71 | 73 | export function add(compName: ComponentType, component: Component) { |
72 | 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; | ... | ... |
... | ... | @@ -472,19 +472,49 @@ |
472 | 472 | }); |
473 | 473 | </script> |
474 | 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 | 519 | @prefix-cls: ~'@{namespace}-basic-tree'; |
490 | 520 | ... | ... |
1 | -// 全局按需注册组件 | |
2 | 1 | import type { App } from 'vue'; |
2 | +import { Icon } from './Icon'; | |
3 | 3 | import { Button } from './Button'; |
4 | 4 | import { |
5 | 5 | // Need |
6 | 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 | 38 | Input, |
39 | + Row, | |
40 | + Col, | |
41 | + Spin, | |
42 | + Space, | |
8 | 43 | Layout, |
44 | + Collapse, | |
45 | + Slider, | |
46 | + InputNumber, | |
47 | + Carousel, | |
48 | + Popconfirm, | |
49 | + Skeleton, | |
50 | + Cascader, | |
51 | + Rate, | |
9 | 52 | } from 'ant-design-vue'; |
10 | 53 | |
11 | -const compList = [AntButton.Group]; | |
54 | +const compList = [AntButton.Group, Icon]; | |
12 | 55 | |
13 | 56 | export function registerGlobComp(app: App) { |
14 | 57 | compList.forEach((comp) => { |
15 | 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 | } | ... | ... |
... | ... | @@ -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 | 78 | // Basic routing without permission |
58 | 79 | export const basicRoutes = [ |
59 | 80 | LoginRoute, |
60 | 81 | RootRoute, |
61 | 82 | AccountDetail, |
83 | + SchedueLog, | |
62 | 84 | ...mainOutRoutes, |
63 | 85 | REDIRECT_ROUTE, |
64 | 86 | PAGE_NOT_FOUND_ROUTE, | ... | ... |
... | ... | @@ -107,7 +107,7 @@ const setting: ProjectConfig = { |
107 | 107 | // Fold trigger position |
108 | 108 | trigger: TriggerEnum.HEADER, |
109 | 109 | // Turn on accordion mode, only show a menu |
110 | - accordion: false, | |
110 | + accordion: true, | |
111 | 111 | // Switch page to close menu |
112 | 112 | closeMixSidebarOnChange: false, |
113 | 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 | 40 | { label: 'Efento NB-IoT', value: 'EFENTO' }, |
41 | 41 | ], |
42 | 42 | }, |
43 | - colProps: { span: 11 }, | |
43 | + colProps: { span: 22}, | |
44 | 44 | }, |
45 | 45 | { |
46 | 46 | field: 'transportPayloadType', |
... | ... | @@ -53,7 +53,7 @@ export const CoapSchemas: FormSchema[] = [ |
53 | 53 | { label: 'PROTOBUF', value: 'PROTOBUF' }, |
54 | 54 | ], |
55 | 55 | }, |
56 | - colProps: { span: 11 }, | |
56 | + colProps: { span: 22 }, | |
57 | 57 | ifShow: ({ values }) => !isEfentoNb(values.coapDeviceType), |
58 | 58 | }, |
59 | 59 | { | ... | ... |
1 | 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 | 3 | <div style="margin-top: 1.2vh"> |
12 | 4 | <BasicForm :showResetButton="false" :showSubmitButton="false" @register="register" /> |
13 | 5 | </div> | ... | ... |
... | ... | @@ -16,13 +16,18 @@ |
16 | 16 | :options="selectOptions" |
17 | 17 | @change="handleDeviceChange" |
18 | 18 | mode="multiple" |
19 | + labelInValue | |
20 | + allowClear | |
21 | + @deselect="handleDeSelect" | |
22 | + notFoundContent="请选择设备" | |
19 | 23 | /> |
20 | 24 | <div style="margin-top: 1.5vh"></div> |
21 | 25 | <DeviceAttrCpns |
22 | 26 | ref="deviceAttrRef" |
23 | 27 | @change="handleChange" |
24 | 28 | :value="deviceList" |
25 | - :orgId="organizationId" | |
29 | + :orgId="organizationId || orgId" | |
30 | + :deSelectValue="deSelectValue" | |
26 | 31 | /> |
27 | 32 | </template> |
28 | 33 | </BasicForm> |
... | ... | @@ -64,9 +69,13 @@ |
64 | 69 | }); |
65 | 70 | }); |
66 | 71 | const handleDeviceChange = (e) => { |
67 | - console.log('e', e); | |
68 | 72 | deviceList.value = e; |
69 | 73 | }; |
74 | + const deSelectValue = ref(''); | |
75 | + const handleDeSelect = ({ key }) => { | |
76 | + //取消选中的key | |
77 | + deSelectValue.value = key; | |
78 | + }; | |
70 | 79 | const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({ |
71 | 80 | labelWidth: 120, |
72 | 81 | schemas: formSchema, |
... | ... | @@ -101,10 +110,22 @@ |
101 | 110 | }); |
102 | 111 | //TODO 模拟的数据 待服务端返回 |
103 | 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 | 130 | selectDevice.value = deviceIds; |
110 | 131 | //回显设备属性 TODO 模拟的数据 待服务端返回 |
... | ... | @@ -133,6 +154,7 @@ |
133 | 154 | deviceAttrRef.value?.echoDynamicInputFunc(deviceAttrData.value); |
134 | 155 | } else { |
135 | 156 | editId.value = ''; |
157 | + orgId.value = ''; | |
136 | 158 | selectDevice.value = []; |
137 | 159 | selectOptions.value = []; |
138 | 160 | deviceList.value = []; | ... | ... |
... | ... | @@ -12,14 +12,9 @@ import { |
12 | 12 | isMonth, |
13 | 13 | isEmpty, |
14 | 14 | isDefultWeek, |
15 | - isMin, | |
16 | - isMax, | |
17 | - isAvg, | |
18 | - isSum, | |
19 | - isCountAll, | |
15 | + isFixedWeek, | |
20 | 16 | } from './timeConfig'; |
21 | 17 | import { AggregateDataEnum } from '../../device/localtion/cpns/TimePeriodForm/config'; |
22 | -// import { JCronValidator } from '/@/components/Form'; | |
23 | 18 | |
24 | 19 | export enum SchemaFiled { |
25 | 20 | WAY = 'way', |
... | ... | @@ -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 | 207 | field: 'timeWeek', |
220 | 208 | component: 'Select', |
... | ... | @@ -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 | 346 | field: 'dataCompare', |
369 | 347 | label: '数据类型', |
... | ... | @@ -576,13 +554,7 @@ export const formSchema: QFormSchema[] = [ |
576 | 554 | }; |
577 | 555 | }, |
578 | 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 | 560 | field: 'interval', |
... | ... | @@ -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 | 1 | <template> |
2 | 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 | 5 | style="display: flex; margin-top: 0.25vh" |
6 | 6 | > |
7 | 7 | <a-input |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | style="width: 160px; margin-left: 1.8vw" |
17 | 17 | :options="selectOptions" |
18 | 18 | @change="emitChange" |
19 | + allowClear | |
19 | 20 | /> |
20 | 21 | </div> |
21 | 22 | </template> |
... | ... | @@ -29,9 +30,7 @@ |
29 | 30 | import { propTypes } from '/@/utils/propTypes'; |
30 | 31 | import { SelectTypes } from 'ant-design-vue/es/select'; |
31 | 32 | import { Select } from 'ant-design-vue'; |
32 | - import { screenLinkPageByDeptIdGetDevice } from '/@/api/ruleengine/ruleengineApi'; | |
33 | 33 | import { getAttribute } from '/@/api/ruleengine/ruleengineApi'; |
34 | - // import { getDeviceAttribute } from '/@/api/alarm/position'; | |
35 | 34 | |
36 | 35 | interface Params { |
37 | 36 | [x: string]: string; |
... | ... | @@ -42,17 +41,11 @@ |
42 | 41 | value: propTypes.array.def([]), |
43 | 42 | orgId: propTypes.string.def(''), |
44 | 43 | }); |
45 | - const emits = defineEmits(['change', 'update:value', 'dynamicReduceHeight', 'dynamicAddHeight']); | |
44 | + const emits = defineEmits(['change', 'update:value']); | |
46 | 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 | 49 | selectOptions.value = res.map((o) => { |
57 | 50 | return { |
58 | 51 | label: o, |
... | ... | @@ -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 | 65 | async function initVal() { |
80 | 66 | dynamicInput.params = []; |
81 | 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 | 72 | dynamicInput.params.push({ |
99 | - attribute: item.attribute, | |
73 | + attribute: '', | |
100 | 74 | device: item.label, |
101 | 75 | value: item.value, |
102 | 76 | }); |
... | ... | @@ -116,18 +90,19 @@ |
116 | 90 | }); |
117 | 91 | }); |
118 | 92 | } |
93 | + console.log('emitChange', obj); | |
119 | 94 | emits('change', obj); |
120 | 95 | emits('update:value', obj); |
121 | 96 | } |
122 | 97 | //回显 |
123 | 98 | const echoDynamicInputFunc = (o) => { |
124 | 99 | dynamicInput.params = []; |
125 | - dynamicInput.params = o.map((m) => { | |
126 | - return { | |
100 | + o.forEach((m: any) => { | |
101 | + dynamicInput.params.push({ | |
127 | 102 | device: m.name, |
128 | 103 | attribute: m.attribute, |
129 | 104 | value: m.device, |
130 | - }; | |
105 | + }); | |
131 | 106 | }); |
132 | 107 | }; |
133 | 108 | defineExpose({ |
... | ... | @@ -135,21 +110,5 @@ |
135 | 110 | }); |
136 | 111 | </script> |
137 | 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 | 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 | 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 | 48 | </template> |
69 | 49 | <template #status="{ record }"> | ... | ... |
... | ... | @@ -80,6 +80,7 @@ export enum TypeEnum { |
80 | 80 | IS_MONTH = 'month', |
81 | 81 | IS_EMPTY = 'NONE', |
82 | 82 | IS_DEFAULT_WEEK = 'defaultIsWeek', |
83 | + IS_FIXED_WEEK = '2', | |
83 | 84 | IS_MIN = 'MIN', |
84 | 85 | IS_MAX = 'MAX', |
85 | 86 | IS_AVG = 'AVG', |
... | ... | @@ -115,6 +116,9 @@ export const isEmpty = (type: string) => { |
115 | 116 | export const isDefultWeek = (type: string) => { |
116 | 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 | 123 | export const isMin = (type: string) => { |
120 | 124 | return type === TypeEnum.IS_MIN; | ... | ... |
... | ... | @@ -137,9 +137,6 @@ |
137 | 137 | delete newFieldValue.nameCity; |
138 | 138 | delete newFieldValue.nameCoun; |
139 | 139 | delete newFieldValue.nameTown; |
140 | - | |
141 | - console.log(newFieldValue.qrCode); | |
142 | - console.log(newFieldValue.codeTown); | |
143 | 140 | // 表单校验 |
144 | 141 | let validateArray = [ |
145 | 142 | 'name', |
... | ... | @@ -153,7 +150,7 @@ |
153 | 150 | 'tel', |
154 | 151 | 'id', |
155 | 152 | ]; |
156 | - if (newFieldValue.qrCode == undefined) { | |
153 | + if (newFieldValue.qrCode == undefined || newFieldValue.qrCode == '') { | |
157 | 154 | validateArray.push('qrcode'); |
158 | 155 | } else { |
159 | 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> | ... | ... |