| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- <template>
- <el-dialog :title="title" :visible.sync="visible" width="950px" append-to-body
- :close-on-click-modal="false" @close="handleClose"
- >
- <el-form ref="form" :model="form" :rules="rules" label-width="100px" v-loading="formLoading">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="计划名称" prop="planName">
- <el-input v-model="form.planName" placeholder="请输入计划名称" maxlength="64" show-word-limit/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="归属区域" prop="areaCode">
- <el-select v-model="form.areaCode" placeholder="请选择归属区域" style="width: 100%" filterable>
- <el-option v-for="item in flatAreaOptions" :key="item.id" :label="item.label" :value="item.id"/>
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="计划类型">
- <el-tag :type="form.planType === 2 ? '' : 'warning'">
- {{ form.planType === 2 ? '自动巡检' : '手动巡检' }}
- </el-tag>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="目标类型" prop="targetType">
- <el-radio-group v-model="form.targetType" @change="handleTargetTypeChange">
- <el-radio-button v-for="item in TARGET_TYPE_OPTIONS" :key="item.value" :label="item.value">
- {{ item.label }}
- </el-radio-button>
- </el-radio-group>
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="巡检目标" prop="targetCodeList">
- <target-selector ref="targetSelector" v-model="form.targetCodeList"
- :target-type="form.targetType" :area-code="form.areaCode" @change="handleTargetChange"
- />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12" v-if="form.planType === 1">
- <el-form-item label="默认执行人" prop="executor">
- <el-input v-model="form.executor" placeholder="请输入执行人姓名" maxlength="32"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="计划时间" prop="planTime">
- <el-date-picker v-model="form.planTime" type="datetime"
- format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm:00"
- placeholder="选择计划执行时间" style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <!-- 自动巡检:调度配置 -->
- <template v-if="form.planType === 2">
- <el-divider content-position="left"><span class="divider-title">定时调度配置</span></el-divider>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="Cron表达式" prop="cronExpression">
- <el-input v-model="form.cronExpression" placeholder="如:0 0 8 * * ?">
- <template slot="append">
- <el-button icon="el-icon-question" @click="showCronHelp">帮助</el-button>
- </template>
- </el-input>
- <div class="cron-preview" v-if="form.cronExpression">
- <i class="el-icon-time"></i>
- {{ cronDescription }}
- </div>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="启用调度">
- <el-switch v-model="form.scheduleEnabled" :active-value="1" :inactive-value="0"
- active-text="启用" inactive-text="禁用" active-color="#13ce66">
- </el-switch>
- <span class="schedule-tip" v-if="form.scheduleEnabled === 1 && !form.cronExpression">
- <i class="el-icon-warning" style="color: #E6A23C"></i>
- 请配置Cron表达式
- </span>
- </el-form-item>
- </el-col>
- </el-row>
- <!-- Cron快捷选择 -->
- <el-row :gutter="20">
- <el-col :span="24">
- <el-form-item label="快捷选择">
- <el-button-group>
- <el-button size="small" @click="setCron('0 0 8 * * ?')">每天8:00</el-button>
- <el-button size="small" @click="setCron('0 0 8,14 * * ?')">每天8:00/14:00</el-button>
- <el-button size="small" @click="setCron('0 0 9 * * 2')">每周一9:00</el-button>
- <el-button size="small" @click="setCron('0 0 9 1 * ?')">每月1号9:00</el-button>
- <el-button size="small" @click="setCron('0 */30 * * * ?')">每30分钟</el-button>
- <el-button size="small" @click="setCron('0 0 */2 * * ?')">每2小时</el-button>
- </el-button-group>
- </el-form-item>
- </el-col>
- </el-row>
- </template>
- <el-form-item label="计划描述" prop="description">
- <el-input v-model="form.description" type="textarea" :rows="2" maxlength="256" show-word-limit/>
- </el-form-item>
- <template v-if="form.planType === 2">
- <el-divider content-position="left"><span class="divider-title">巡检规则配置</span></el-divider>
- <rule-config ref="ruleConfig" v-model="form.rules"/>
- </template>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="handleClose">取 消</el-button>
- <el-button type="primary" @click="submitForm" :loading="submitting">确 定</el-button>
- </div>
- <!-- Cron帮助对话框 -->
- <el-dialog title="Cron表达式帮助" :visible.sync="cronHelpVisible" width="600px" append-to-body>
- <div class="cron-help">
- <h4>Cron表达式格式</h4>
- <p>格式: <code>秒 分 时 日 月 周 [年]</code></p>
- <el-table :data="cronFields" border size="small">
- <el-table-column label="字段" prop="field" width="80" />
- <el-table-column label="允许值" prop="allowed" width="200" />
- <el-table-column label="特殊字符" prop="special" />
- </el-table>
- <h4 style="margin-top: 20px">常用示例</h4>
- <el-table :data="cronExamples" border size="small">
- <el-table-column label="表达式" prop="cron" width="200" />
- <el-table-column label="说明" prop="desc" />
- </el-table>
- </div>
- <div slot="footer">
- <el-button type="primary" @click="cronHelpVisible = false">知道了</el-button>
- </div>
- </el-dialog>
- </el-dialog>
- </template>
- <script>
- import { getPlan, addPlan, updatePlan } from '@/api/inspection/plan'
- import { registerPlan, unregisterPlan } from '@/api/inspection/scheduler'
- import { PLAN_TYPE_OPTIONS, TARGET_TYPE_OPTIONS } from '@/enums/InspectionEnums'
- import TargetSelector from './TargetSelector.vue'
- import RuleConfig from './RuleConfig.vue'
- export default {
- name: 'PlanForm',
- components: { TargetSelector, RuleConfig },
- props: {
- areaOptions: { type: Array, default: () => [] }
- },
- data() {
- const validateTargets = (rule, value, callback) => {
- if (!value || value.length === 0) {
- callback(new Error('请选择至少一个巡检目标'))
- } else {
- callback()
- }
- }
- const validateCron = (rule, value, callback) => {
- if (this.form.scheduleEnabled === 1 && !value) {
- callback(new Error('启用调度时必须配置Cron表达式'))
- } else {
- callback()
- }
- }
- return {
- visible: false,
- formLoading: false,
- submitting: false,
- title: '',
- form: this.initForm(),
- rules: {
- planName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }],
- areaCode: [{ required: true, message: '请选择归属区域', trigger: 'change' }],
- targetType: [{ required: true, message: '请选择目标类型', trigger: 'change' }],
- targetCodeList: [{ required: true, validator: validateTargets, trigger: 'change' }],
- executor: [{ required: true, message: '请输入执行人', trigger: 'blur' }],
- cronExpression: [{ validator: validateCron, trigger: 'blur' }]
- },
- PLAN_TYPE_OPTIONS,
- TARGET_TYPE_OPTIONS,
- cronHelpVisible: false,
- cronFields: [
- { field: '秒', allowed: '0-59', special: ', - * /' },
- { field: '分', allowed: '0-59', special: ', - * /' },
- { field: '时', allowed: '0-23', special: ', - * /' },
- { field: '日', allowed: '1-31', special: ', - * ? / L W' },
- { field: '月', allowed: '1-12 或 JAN-DEC', special: ', - * /' },
- { field: '周', allowed: '1-7 或 SUN-SAT', special: ', - * ? / L #' }
- ],
- cronExamples: [
- { cron: '0 0 8 * * ?', desc: '每天早上8点执行' },
- { cron: '0 0 8,14 * * ?', desc: '每天8点和14点执行' },
- { cron: '0 0 9 * * 2', desc: '每周一早上9点执行' },
- { cron: '0 0 9 1 * ?', desc: '每月1号早上9点执行' },
- { cron: '0 */30 * * * ?', desc: '每30分钟执行一次' },
- { cron: '0 0 */2 * * ?', desc: '每2小时执行一次' }
- ]
- }
- },
- computed: {
- flatAreaOptions() {
- const result = []
- const flatten = (list, prefix = '') => {
- list.forEach(item => {
- result.push({ id: item.id, label: prefix + item.label })
- if (item.children && item.children.length > 0) flatten(item.children, prefix + item.label + ' / ')
- })
- }
- flatten(this.areaOptions)
- return result
- },
- cronDescription() {
- const cron = this.form.cronExpression
- if (!cron) return ''
- const parts = cron.split(' ')
- if (parts.length < 6) return '表达式格式不正确'
- const [second, minute, hour, day, month, week] = parts
- // 每天执行
- if (day === '*' && month === '*' && week === '?') {
- if (hour.includes(',')) {
- return `每天 ${hour.split(',').map(h => h + ':' + minute.padStart(2, '0')).join('、')} 执行`
- }
- if (hour.includes('/')) {
- return `每${hour.split('/')[1]}小时执行一次`
- }
- return `每天 ${hour}:${minute.padStart(2, '0')} 执行`
- }
- // 每周执行
- if (day === '?' && week !== '*') {
- const weekMap = { '1': '周日', '2': '周一', '3': '周二', '4': '周三', '5': '周四', '6': '周五', '7': '周六' }
- return `每${weekMap[week] || '周' + week} ${hour}:${minute.padStart(2, '0')} 执行`
- }
- // 每月执行
- if (day !== '*' && day !== '?') {
- return `每月${day}日 ${hour}:${minute.padStart(2, '0')} 执行`
- }
- // 分钟级
- if (minute.includes('/')) {
- return `每${minute.split('/')[1]}分钟执行一次`
- }
- return '自定义调度规则'
- }
- },
- methods: {
- initForm() {
- return {
- id: null,
- planCode: null,
- planName: '',
- areaCode: null,
- planType: 2,
- targetType: 2,
- targetCodeList: [],
- targetNames: '',
- planTime: null,
- cronExpression: '',
- scheduleEnabled: 0, // 新增字段:是否启用调度
- executor: '',
- description: '',
- rules: []
- }
- },
- async open(id, planType) {
- this.form = this.initForm()
- if (planType) {
- this.form.planType = planType
- }
- this.title = id ? '编辑巡检计划' : '新增巡检计划'
- this.visible = true
- if (id) {
- this.formLoading = true
- try {
- const res = await getPlan(id)
- const data = res.data || {}
- this.form = {
- ...data,
- targetCodeList: data.targetCodeList || [],
- scheduleEnabled: data.scheduleEnabled || 0
- }
- } catch (e) {
- this.$modal.msgError('获取计划详情失败')
- this.visible = false
- } finally {
- this.formLoading = false
- }
- }
- },
- handleClose() {
- this.visible = false
- this.$refs.form.resetFields()
- this.form = this.initForm()
- },
- handleTargetTypeChange() {
- this.form.targetCodeList = []
- this.form.targetNames = ''
- },
- handleTargetChange(names) {
- this.form.targetNames = names
- },
- setCron(cron) {
- this.form.cronExpression = cron
- },
- showCronHelp() {
- this.cronHelpVisible = true
- },
- submitForm() {
- this.$refs.form.validate(async (valid) => {
- if (!valid) return
- // 自动巡检校验
- if (this.form.planType === 2) {
- if (!this.form.rules || this.form.rules.length === 0) {
- this.$modal.msgWarning('自动巡检请至少配置一条巡检规则')
- return
- }
- if (this.form.scheduleEnabled === 1 && !this.form.cronExpression) {
- this.$modal.msgWarning('启用调度时请配置Cron表达式')
- return
- }
- }
- const data = {
- ...this.form,
- targetCodes: JSON.stringify(this.form.targetCodeList)
- }
- this.submitting = true
- try {
- const isEdit = !!this.form.id
- if (isEdit) {
- await updatePlan(data)
- } else {
- await addPlan(data)
- }
- // 如果是自动巡检且启用了调度,同步注册到调度器
- if (this.form.planType === 2 && this.form.scheduleEnabled === 1 && this.form.cronExpression) {
- try {
- await registerPlan(this.form.planCode || data.planCode)
- } catch (e) {
- console.warn('注册调度器失败,但计划已保存', e)
- }
- }
- this.$modal.msgSuccess(isEdit ? '修改成功' : '新增成功')
- this.handleClose()
- this.$emit('success')
- } catch (err) {
- this.$modal.msgError(err.message || '操作失败')
- } finally {
- this.submitting = false
- }
- })
- }
- }
- }
- </script>
- <style scoped>
- .divider-title {
- font-weight: bold;
- color: #409EFF;
- font-size: 14px;
- }
- .cron-preview {
- margin-top: 5px;
- font-size: 12px;
- color: #67C23A;
- }
- .schedule-tip {
- margin-left: 10px;
- font-size: 12px;
- color: #E6A23C;
- }
- .cron-help h4 {
- margin-bottom: 10px;
- color: #303133;
- }
- .cron-help code {
- background: #f5f7fa;
- padding: 2px 6px;
- border-radius: 3px;
- color: #409EFF;
- }
- </style>
|