mirror of
https://gitee.com/myxzgzs/boyue-ui-admin-vue3
synced 2025-08-08 16:32:43 +08:00
Merge branch 'master' of https://github.com/yudaocode/yudao-ui-admin-vue3
This commit is contained in:
commit
9ee8f8e1ba
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -62,16 +62,16 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[less]": {
|
"[less]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
@ -86,8 +86,9 @@
|
|||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.fixAll.stylelint": "explicit"
|
"source.fixAll.stylelint": "explicit"
|
||||||
},
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "octref.vetur"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"i18n-ally.localesPaths": ["src/locales"],
|
"i18n-ally.localesPaths": ["src/locales"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
|
@ -68,6 +68,11 @@
|
|||||||
:business-object="elementBusinessObject"
|
:business-object="elementBusinessObject"
|
||||||
/>
|
/>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
|
<!-- 新增的时间事件配置项 -->
|
||||||
|
<el-collapse-item v-if="elementType === 'IntermediateCatchEvent'" name="timeEvent">
|
||||||
|
<template #title><Icon icon="ep:timer" />时间事件</template>
|
||||||
|
<TimeEventConfig :businessObject="bpmnElement.value?.businessObject" :key="elementId" />
|
||||||
|
</el-collapse-item>
|
||||||
</el-collapse>
|
</el-collapse>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -83,6 +88,8 @@ import ElementProperties from './properties/ElementProperties.vue'
|
|||||||
// import ElementForm from './form/ElementForm.vue'
|
// import ElementForm from './form/ElementForm.vue'
|
||||||
import UserTaskListeners from './listeners/UserTaskListeners.vue'
|
import UserTaskListeners from './listeners/UserTaskListeners.vue'
|
||||||
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
|
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
|
||||||
|
import TimeEventConfig from "./time-event-config/TimeEventConfig.vue"
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
|
||||||
defineOptions({ name: 'MyPropertiesPanel' })
|
defineOptions({ name: 'MyPropertiesPanel' })
|
||||||
|
|
||||||
@ -121,6 +128,8 @@ const formVisible = ref(false) // 表单配置
|
|||||||
const bpmnElement = ref()
|
const bpmnElement = ref()
|
||||||
const isReady = ref(false)
|
const isReady = ref(false)
|
||||||
|
|
||||||
|
const type = ref('time')
|
||||||
|
const condition = ref('')
|
||||||
provide('prefix', props.prefix)
|
provide('prefix', props.prefix)
|
||||||
provide('width', props.width)
|
provide('width', props.width)
|
||||||
|
|
||||||
@ -255,4 +264,54 @@ watch(
|
|||||||
activeTab.value = 'base'
|
activeTab.value = 'base'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function updateNode() {
|
||||||
|
const moddle = window.bpmnInstances?.moddle
|
||||||
|
const modeling = window.bpmnInstances?.modeling
|
||||||
|
const elementRegistry = window.bpmnInstances?.elementRegistry
|
||||||
|
if (!moddle || !modeling || !elementRegistry) return
|
||||||
|
|
||||||
|
const element = elementRegistry.get(props.businessObject.id)
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
let timerDef = moddle.create('bpmn:TimerEventDefinition', {})
|
||||||
|
if (type.value === 'time') {
|
||||||
|
timerDef.timeDate = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'duration') {
|
||||||
|
timerDef.timeDuration = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'cycle') {
|
||||||
|
timerDef.timeCycle = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
modeling.updateModdleProperties(
|
||||||
|
element,
|
||||||
|
element.businessObject,
|
||||||
|
{
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('当前eventDefinitions:', element.businessObject.eventDefinitions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化和监听
|
||||||
|
function syncFromBusinessObject() {
|
||||||
|
if (props.businessObject) {
|
||||||
|
const timerDef = (props.businessObject.eventDefinitions || [])[0]
|
||||||
|
if (timerDef) {
|
||||||
|
if (timerDef.timeDate) {
|
||||||
|
type.value = 'time'
|
||||||
|
condition.value = timerDef.timeDate.body
|
||||||
|
} else if (timerDef.timeDuration) {
|
||||||
|
type.value = 'duration'
|
||||||
|
condition.value = timerDef.timeDuration.body
|
||||||
|
} else if (timerDef.timeCycle) {
|
||||||
|
type.value = 'cycle'
|
||||||
|
condition.value = timerDef.timeCycle.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(syncFromBusinessObject)
|
||||||
|
watch(() => props.businessObject, syncFromBusinessObject, { deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<el-tabs v-model="tab">
|
||||||
|
<el-tab-pane label="CRON表达式" name="cron">
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<el-input v-model="cronStr" readonly style="width: 400px; font-weight: bold;" :key="'cronStr'" />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
|
||||||
|
<el-input v-model="fields.second" placeholder="秒" style="width: 80px;" :key="'second'" />
|
||||||
|
<el-input v-model="fields.minute" placeholder="分" style="width: 80px;" :key="'minute'" />
|
||||||
|
<el-input v-model="fields.hour" placeholder="时" style="width: 80px;" :key="'hour'" />
|
||||||
|
<el-input v-model="fields.day" placeholder="天" style="width: 80px;" :key="'day'" />
|
||||||
|
<el-input v-model="fields.month" placeholder="月" style="width: 80px;" :key="'month'" />
|
||||||
|
<el-input v-model="fields.week" placeholder="周" style="width: 80px;" :key="'week'" />
|
||||||
|
<el-input v-model="fields.year" placeholder="年" style="width: 80px;" :key="'year'" />
|
||||||
|
</div>
|
||||||
|
<el-tabs v-model="activeField" type="card" style="margin-bottom: 8px;">
|
||||||
|
<el-tab-pane v-for="f in cronFieldList" :label="f.label" :name="f.key" :key="f.key">
|
||||||
|
<div style="margin-bottom: 8px;">
|
||||||
|
<el-radio-group v-model="cronMode[f.key]" :key="'radio-'+f.key">
|
||||||
|
<el-radio label="every" :key="'every-'+f.key">每{{f.label}}</el-radio>
|
||||||
|
<el-radio label="range" :key="'range-'+f.key">从 <el-input-number v-model="cronRange[f.key][0]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'range0-'+f.key" /> 到 <el-input-number v-model="cronRange[f.key][1]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'range1-'+f.key" /> 之间每{{f.label}}</el-radio>
|
||||||
|
<el-radio label="step" :key="'step-'+f.key">从第 <el-input-number v-model="cronStep[f.key][0]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'step0-'+f.key" /> 开始每 <el-input-number v-model="cronStep[f.key][1]" :min="1" :max="f.max" size="small" style="width:60px" :key="'step1-'+f.key" /> {{f.label}}</el-radio>
|
||||||
|
<el-radio label="appoint" :key="'appoint-'+f.key">指定</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div v-if="cronMode[f.key]==='appoint'">
|
||||||
|
<el-checkbox-group v-model="cronAppoint[f.key]" :key="'group-'+f.key">
|
||||||
|
<el-checkbox v-for="n in f.max+1" :label="pad(n-1)" :key="'cb-'+f.key+'-'+(n-1)">{{pad(n-1)}}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="标准格式" name="iso" :key="'iso-tab'">
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<el-input v-model="isoStr" placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S" style="width: 400px; font-weight: bold;" :key="'isoStr'" />
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 10px;">循环次数:<el-input-number v-model="repeat" :min="1" style="width: 100px;" :key="'repeat'" /></div>
|
||||||
|
<div style="margin-bottom: 10px;">日期时间:<el-date-picker v-model="isoDate" type="datetime" placeholder="选择日期时间" style="width: 200px;" :key="'isoDate'" /></div>
|
||||||
|
<div style="margin-bottom: 10px;">当前时长:<el-input v-model="isoDuration" placeholder="如P3DT30M30S" style="width: 200px;" :key="'isoDuration'" /></div>
|
||||||
|
<div>
|
||||||
|
<div>秒:<el-button v-for="s in [5,10,30,50]" @click="setDuration('S',s)" :key="'sec-'+s">{{s}}</el-button>自定义</div>
|
||||||
|
<div>分:<el-button v-for="m in [5,10,30,50]" @click="setDuration('M',m)" :key="'min-'+m">{{m}}</el-button>自定义</div>
|
||||||
|
<div>小时:<el-button v-for="h in [4,8,12,24]" @click="setDuration('H',h)" :key="'hour-'+h">{{h}}</el-button>自定义</div>
|
||||||
|
<div>天:<el-button v-for="d in [1,2,3,4]" @click="setDuration('D',d)" :key="'day-'+d">{{d}}</el-button>自定义</div>
|
||||||
|
<div>月:<el-button v-for="mo in [1,2,3,4]" @click="setDuration('M',mo)" :key="'mon-'+mo">{{mo}}</el-button>自定义</div>
|
||||||
|
<div>年:<el-button v-for="y in [1,2,3,4]" @click="setDuration('Y',y)" :key="'year-'+y">{{y}}</el-button>自定义</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
const props = defineProps({ value: String })
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const tab = ref('cron')
|
||||||
|
const cronStr = ref(props.value || '* * * * * ?')
|
||||||
|
const fields = ref({ second: '*', minute: '*', hour: '*', day: '*', month: '*', week: '?', year: '' })
|
||||||
|
const cronFieldList = [
|
||||||
|
{ key: 'second', label: '秒', min: 0, max: 59 },
|
||||||
|
{ key: 'minute', label: '分', min: 0, max: 59 },
|
||||||
|
{ key: 'hour', label: '时', min: 0, max: 23 },
|
||||||
|
{ key: 'day', label: '天', min: 1, max: 31 },
|
||||||
|
{ key: 'month', label: '月', min: 1, max: 12 },
|
||||||
|
{ key: 'week', label: '周', min: 1, max: 7 },
|
||||||
|
{ key: 'year', label: '年', min: 1970, max: 2099 }
|
||||||
|
]
|
||||||
|
const activeField = ref('second')
|
||||||
|
const cronMode = ref({ second: 'appoint', minute: 'every', hour: 'every', day: 'every', month: 'every', week: 'every', year: 'every' })
|
||||||
|
const cronAppoint = ref({ second: ['00','01'], minute: [], hour: [], day: [], month: [], week: [], year: [] })
|
||||||
|
const cronRange = ref({ second: [0,1], minute: [0,1], hour: [0,1], day: [1,2], month: [1,2], week: [1,2], year: [1970,1971] })
|
||||||
|
const cronStep = ref({ second: [1,1], minute: [1,1], hour: [1,1], day: [1,1], month: [1,1], week: [1,1], year: [1970,1] })
|
||||||
|
|
||||||
|
function pad(n) { return n<10 ? '0'+n : ''+n }
|
||||||
|
|
||||||
|
watch([fields, cronMode, cronAppoint, cronRange, cronStep], () => {
|
||||||
|
// 组装cron表达式
|
||||||
|
let arr = cronFieldList.map(f => {
|
||||||
|
if (cronMode.value[f.key]==='every') return '*'
|
||||||
|
if (cronMode.value[f.key]==='appoint') return cronAppoint.value[f.key].join(',') || '*'
|
||||||
|
if (cronMode.value[f.key]==='range') return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`
|
||||||
|
if (cronMode.value[f.key]==='step') return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`
|
||||||
|
return fields.value[f.key] || '*'
|
||||||
|
})
|
||||||
|
// week和year特殊处理
|
||||||
|
arr[5] = arr[5] || '?'
|
||||||
|
cronStr.value = arr.join(' ')
|
||||||
|
if (tab.value==='cron') emit('change', cronStr.value)
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 标准格式
|
||||||
|
const isoStr = ref('')
|
||||||
|
const repeat = ref(1)
|
||||||
|
const isoDate = ref('')
|
||||||
|
const isoDuration = ref('')
|
||||||
|
function setDuration(type, val) {
|
||||||
|
// 组装ISO 8601字符串
|
||||||
|
let d = isoDuration.value
|
||||||
|
if (!d.includes(type)) d += val + type
|
||||||
|
else d = d.replace(new RegExp(`\\d+${type}`), val + type)
|
||||||
|
isoDuration.value = d
|
||||||
|
updateIsoStr()
|
||||||
|
}
|
||||||
|
function updateIsoStr() {
|
||||||
|
let str = `R${repeat.value}`
|
||||||
|
if (isoDate.value) str += '/' + (typeof isoDate.value==='string'?isoDate.value:new Date(isoDate.value).toISOString())
|
||||||
|
if (isoDuration.value) str += '/' + isoDuration.value
|
||||||
|
isoStr.value = str
|
||||||
|
if (tab.value==='iso') emit('change', isoStr.value)
|
||||||
|
}
|
||||||
|
watch([repeat, isoDate, isoDuration], updateIsoStr)
|
||||||
|
watch(() => props.value, (val) => {
|
||||||
|
if (!val) return
|
||||||
|
if (tab.value==='cron') cronStr.value = val
|
||||||
|
if (tab.value==='iso') isoStr.value = val
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="margin-bottom: 10px;">当前选择:<el-input v-model="isoString" readonly style="width: 300px;" /></div>
|
||||||
|
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px;">
|
||||||
|
<span>{{ unit.label }}:</span>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button v-for="val in unit.presets" :key="val" size="mini" @click="setUnit(unit.key, val)">{{ val }}</el-button>
|
||||||
|
<el-input v-model.number="custom[unit.key]" size="mini" style="width: 60px; margin-left: 8px;" placeholder="自定义" @change="setUnit(unit.key, custom[unit.key])" />
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
const props = defineProps({ value: String })
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ key: 'Y', label: '年', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'M', label: '月', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'D', label: '天', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'H', label: '时', presets: [4, 8, 12, 24] },
|
||||||
|
{ key: 'm', label: '分', presets: [5, 10, 30, 50] },
|
||||||
|
{ key: 'S', label: '秒', presets: [5, 10, 30, 50] }
|
||||||
|
]
|
||||||
|
const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' })
|
||||||
|
const isoString = ref('')
|
||||||
|
|
||||||
|
function setUnit(key, val) {
|
||||||
|
if (!val || isNaN(val)) {
|
||||||
|
custom.value[key] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
custom.value[key] = val
|
||||||
|
updateIsoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIsoString() {
|
||||||
|
let str = 'P'
|
||||||
|
if (custom.value.Y) str += custom.value.Y + 'Y'
|
||||||
|
if (custom.value.M) str += custom.value.M + 'M'
|
||||||
|
if (custom.value.D) str += custom.value.D + 'D'
|
||||||
|
if (custom.value.H || custom.value.m || custom.value.S) str += 'T'
|
||||||
|
if (custom.value.H) str += custom.value.H + 'H'
|
||||||
|
if (custom.value.m) str += custom.value.m + 'M'
|
||||||
|
if (custom.value.S) str += custom.value.S + 'S'
|
||||||
|
isoString.value = str === 'P' ? '' : str
|
||||||
|
emit('change', isoString.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (val) => {
|
||||||
|
if (!val) return
|
||||||
|
// 解析ISO 8601字符串到custom
|
||||||
|
const match = val.match(/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/)
|
||||||
|
if (match) {
|
||||||
|
custom.value.Y = match[1] || ''
|
||||||
|
custom.value.M = match[2] || ''
|
||||||
|
custom.value.D = match[3] || ''
|
||||||
|
custom.value.H = match[4] || ''
|
||||||
|
custom.value.m = match[5] || ''
|
||||||
|
custom.value.S = match[6] || ''
|
||||||
|
updateIsoString()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panel-tab__content">
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<span>类型:</span>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button size="mini" :type="type==='time'?'primary':''" @click="setType('time')">时间</el-button>
|
||||||
|
<el-button size="mini" :type="type==='duration'?'primary':''" @click="setType('duration')">持续</el-button>
|
||||||
|
<el-button size="mini" :type="type==='cycle'?'primary':''" @click="setType('cycle')">循环</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-icon v-if="valid" color="green" style="margin-left:8px"><CircleCheckFilled /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 10px; display: flex; align-items: center;">
|
||||||
|
<span>条件:</span>
|
||||||
|
<el-input
|
||||||
|
v-model="condition"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
style="width: calc(100% - 100px);"
|
||||||
|
:readonly="type !== 'duration' && type !== 'cycle'"
|
||||||
|
@focus="handleInputFocus"
|
||||||
|
@blur="updateNode"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<el-tooltip v-if="!valid" content="格式错误" placement="top">
|
||||||
|
<el-icon color="orange"><WarningFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :content="helpText" placement="top">
|
||||||
|
<el-icon color="#409EFF" style="cursor: pointer" @click="showHelp = true"><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'time'"
|
||||||
|
@click="showDatePicker = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:calendar" />
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'duration'"
|
||||||
|
@click="showDurationDialog = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:timer" />
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'cycle'"
|
||||||
|
@click="showCycleDialog = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:setting" />
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<!-- 时间选择器 -->
|
||||||
|
<el-dialog v-model="showDatePicker" title="选择时间" width="400px" @close="showDatePicker=false">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateValue"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择日期时间"
|
||||||
|
style="width: 100%;"
|
||||||
|
@change="onDateChange"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showDatePicker=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onDateConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 持续时长选择器 -->
|
||||||
|
<el-dialog v-model="showDurationDialog" title="时间配置" width="600px" @close="showDurationDialog=false">
|
||||||
|
<DurationConfig :value="condition" @change="onDurationChange" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showDurationDialog=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onDurationConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 循环配置器 -->
|
||||||
|
<el-dialog v-model="showCycleDialog" title="时间配置" width="800px" @close="showCycleDialog=false">
|
||||||
|
<CycleConfig :value="condition" @change="onCycleChange" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showCycleDialog=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onCycleConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 帮助说明 -->
|
||||||
|
<el-dialog v-model="showHelp" title="格式说明" width="600px" @close="showHelp=false">
|
||||||
|
<div v-html="helpHtml"></div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showHelp=false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { CircleCheckFilled, WarningFilled, QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
import DurationConfig from './DurationConfig.vue'
|
||||||
|
import CycleConfig from './CycleConfig.vue'
|
||||||
|
import { createListenerObject, updateElementExtensions } from '../../utils'
|
||||||
|
const bpmnInstances = () => (window as any).bpmnInstances
|
||||||
|
const props = defineProps({ businessObject: Object })
|
||||||
|
const type = ref('time')
|
||||||
|
const condition = ref('')
|
||||||
|
const valid = ref(true)
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const showDurationDialog = ref(false)
|
||||||
|
const showCycleDialog = ref(false)
|
||||||
|
const showHelp = ref(false)
|
||||||
|
const dateValue = ref(null)
|
||||||
|
const bpmnElement = ref(null)
|
||||||
|
|
||||||
|
const placeholder = computed(() => {
|
||||||
|
if (type.value === 'time') return '请输入时间'
|
||||||
|
if (type.value === 'duration') return '请输入持续时长'
|
||||||
|
if (type.value === 'cycle') return '请输入循环表达式'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const helpText = computed(() => {
|
||||||
|
if (type.value === 'time') return '选择具体时间'
|
||||||
|
if (type.value === 'duration') return 'ISO 8601格式,如PT1H'
|
||||||
|
if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const helpHtml = computed(() => {
|
||||||
|
if (type.value === 'duration') {
|
||||||
|
return `指定定时器之前要等待多长时间。S表示秒,M表示分,D表示天;P表示时间段,T表示精确到时间的时间段。<br>
|
||||||
|
时间格式依然为ISO 8601格式,一年两个月三天四小时五分六秒内,可以写成P1Y2M3DT4H5M6S。<br>
|
||||||
|
P是开始标记,T是时间和日期分割标记,没有日期只有时间T是不能省去的,比如1小时执行一次应写成PT1H。`
|
||||||
|
}
|
||||||
|
if (type.value === 'cycle') {
|
||||||
|
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化和监听
|
||||||
|
function syncFromBusinessObject() {
|
||||||
|
if (props.businessObject) {
|
||||||
|
const timerDef = (props.businessObject.eventDefinitions || [])[0]
|
||||||
|
if (timerDef) {
|
||||||
|
if (timerDef.timeDate) {
|
||||||
|
type.value = 'time'
|
||||||
|
condition.value = timerDef.timeDate.body
|
||||||
|
} else if (timerDef.timeDuration) {
|
||||||
|
type.value = 'duration'
|
||||||
|
condition.value = timerDef.timeDuration.body
|
||||||
|
} else if (timerDef.timeCycle) {
|
||||||
|
type.value = 'cycle'
|
||||||
|
condition.value = timerDef.timeCycle.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(syncFromBusinessObject)
|
||||||
|
|
||||||
|
|
||||||
|
// 切换类型
|
||||||
|
function setType(t) {
|
||||||
|
type.value = t
|
||||||
|
condition.value = ''
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入校验
|
||||||
|
watch([type, condition], () => {
|
||||||
|
valid.value = validate()
|
||||||
|
// updateNode() // 可以注释掉,避免频繁触发
|
||||||
|
})
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
if (type.value === 'time') {
|
||||||
|
return !!condition.value && !isNaN(Date.parse(condition.value))
|
||||||
|
}
|
||||||
|
if (type.value === 'duration') {
|
||||||
|
return /^P.*$/.test(condition.value)
|
||||||
|
}
|
||||||
|
if (type.value === 'cycle') {
|
||||||
|
return /^([0-9*\/?, ]+|R\d*\/P.*)$/.test(condition.value)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择时间
|
||||||
|
function onDateChange(val) {
|
||||||
|
dateValue.value = val
|
||||||
|
}
|
||||||
|
function onDateConfirm() {
|
||||||
|
if (dateValue.value) {
|
||||||
|
condition.value = new Date(dateValue.value).toISOString()
|
||||||
|
showDatePicker.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 持续时长
|
||||||
|
function onDurationChange(val) {
|
||||||
|
condition.value = val
|
||||||
|
}
|
||||||
|
function onDurationConfirm() {
|
||||||
|
showDurationDialog.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环
|
||||||
|
function onCycleChange(val) {
|
||||||
|
condition.value = val
|
||||||
|
}
|
||||||
|
function onCycleConfirm() {
|
||||||
|
showCycleDialog.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框聚焦时弹窗(可选)
|
||||||
|
function handleInputFocus() {
|
||||||
|
if (type.value === 'time') showDatePicker.value = true
|
||||||
|
if (type.value === 'duration') showDurationDialog.value = true
|
||||||
|
if (type.value === 'cycle') showCycleDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步到节点
|
||||||
|
function updateNode() {
|
||||||
|
const moddle = window.bpmnInstances?.moddle
|
||||||
|
const modeling = window.bpmnInstances?.modeling
|
||||||
|
const elementRegistry = window.bpmnInstances?.elementRegistry
|
||||||
|
if (!moddle || !modeling || !elementRegistry) return
|
||||||
|
|
||||||
|
// 获取元素
|
||||||
|
if (!props.businessObject || !props.businessObject.id) return
|
||||||
|
const element = elementRegistry.get(props.businessObject.id)
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
// 1. 复用原有 timerDef,或新建
|
||||||
|
let timerDef = (element.businessObject.eventDefinitions && element.businessObject.eventDefinitions[0])
|
||||||
|
if (!timerDef) {
|
||||||
|
timerDef =bpmnInstances().bpmnFactory.create('bpmn:TimerEventDefinition', {})
|
||||||
|
modeling.updateProperties(element, {
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 清空原有
|
||||||
|
delete timerDef.timeDate
|
||||||
|
delete timerDef.timeDuration
|
||||||
|
delete timerDef.timeCycle
|
||||||
|
|
||||||
|
// 3. 设置新的
|
||||||
|
if (type.value === 'time' && condition.value) {
|
||||||
|
timerDef.timeDate =bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'duration' && condition.value) {
|
||||||
|
timerDef.timeDuration =bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'cycle' && condition.value) {
|
||||||
|
timerDef.timeCycle = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
bpmnInstances().modeling.updateProperties(toRaw(element), {
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.businessObject,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
nextTick(() => {
|
||||||
|
|
||||||
|
syncFromBusinessObject()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 相关样式 */
|
||||||
|
</style>
|
@ -1,11 +1,12 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { store } from '../index'
|
|
||||||
import { humpToUnderline, setCssVar } from '@/utils'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
import { ElementPlusSize } from '@/types/elementPlus'
|
import { ElementPlusSize } from '@/types/elementPlus'
|
||||||
import { LayoutType } from '@/types/layout'
|
import { LayoutType } from '@/types/layout'
|
||||||
import { ThemeTypes } from '@/types/theme'
|
import { ThemeTypes } from '@/types/theme'
|
||||||
|
import { humpToUnderline, setCssVar } from '@/utils'
|
||||||
|
import { getCssColorVariable, hexToRGB, mix } from '@/utils/color'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { store } from '../index'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
@ -183,6 +184,40 @@ export const useAppStore = defineStore('app', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
setPrimaryLight() {
|
||||||
|
if (this.theme.elColorPrimary) {
|
||||||
|
const elColorPrimary = this.theme.elColorPrimary
|
||||||
|
const color = this.isDark ? '#000000' : '#ffffff'
|
||||||
|
const lightList = [3, 5, 7, 8, 9]
|
||||||
|
lightList.forEach((v) => {
|
||||||
|
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
|
||||||
|
})
|
||||||
|
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
|
||||||
|
|
||||||
|
this.setAllColorRgbVars()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理element自带的主题色和辅助色的-rgb切换主题变化,如:--el-color-primary-rgb
|
||||||
|
setAllColorRgbVars() {
|
||||||
|
// 需要处理的颜色类型列表
|
||||||
|
const colorTypes = ['primary', 'success', 'warning', 'danger', 'error', 'info']
|
||||||
|
|
||||||
|
colorTypes.forEach((type) => {
|
||||||
|
// 获取当前颜色值
|
||||||
|
const colorValue = getCssColorVariable(`--el-color-${type}`)
|
||||||
|
if (colorValue) {
|
||||||
|
// 转换为rgba并提取RGB部分
|
||||||
|
const rgbaString = hexToRGB(colorValue, 1)
|
||||||
|
const rgbValues = rgbaString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i)
|
||||||
|
if (rgbValues) {
|
||||||
|
const [, r, g, b] = rgbValues
|
||||||
|
// 设置对应的RGB变量
|
||||||
|
setCssVar(`--el-color-${type}-rgb`, `${r}, ${g}, ${b}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
setBreadcrumb(breadcrumb: boolean) {
|
setBreadcrumb(breadcrumb: boolean) {
|
||||||
this.breadcrumb = breadcrumb
|
this.breadcrumb = breadcrumb
|
||||||
},
|
},
|
||||||
@ -256,6 +291,7 @@ export const useAppStore = defineStore('app', {
|
|||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
}
|
}
|
||||||
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
|
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
|
||||||
|
this.setPrimaryLight()
|
||||||
},
|
},
|
||||||
setCurrentSize(currentSize: ElementPlusSize) {
|
setCurrentSize(currentSize: ElementPlusSize) {
|
||||||
this.currentSize = currentSize
|
this.currentSize = currentSize
|
||||||
@ -272,6 +308,7 @@ export const useAppStore = defineStore('app', {
|
|||||||
for (const key in this.theme) {
|
for (const key in this.theme) {
|
||||||
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
||||||
}
|
}
|
||||||
|
this.setPrimaryLight()
|
||||||
},
|
},
|
||||||
setFooter(footer: boolean) {
|
setFooter(footer: boolean) {
|
||||||
this.footer = footer
|
this.footer = footer
|
||||||
|
@ -172,3 +172,46 @@ export const PREDEFINE_COLORS = [
|
|||||||
'#1f73c3',
|
'#1f73c3',
|
||||||
'#711f57'
|
'#711f57'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixes two colors.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
* @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
* @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
|
||||||
|
* @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
*/
|
||||||
|
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
|
||||||
|
let color = '#'
|
||||||
|
for (let i = 0; i <= 2; i++) {
|
||||||
|
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
|
||||||
|
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
|
||||||
|
const c = Math.round(c1 * weight + c2 * (1 - weight))
|
||||||
|
color += c.toString(16).padStart(2, '0')
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getCssColorVariable
|
||||||
|
* @description 获取css变量的颜色值
|
||||||
|
* @param colorVariable css变量名
|
||||||
|
* @param opacity 透明度
|
||||||
|
* @returns {string} 颜色值
|
||||||
|
* @example getCssColorVariable('--el-color-primary', 0.5)
|
||||||
|
* @example getCssColorVariable('--el-color-primary')
|
||||||
|
* @example getCssColorVariable()
|
||||||
|
*/
|
||||||
|
export const getCssColorVariable = (
|
||||||
|
colorVariable: string = '--el-color-primary',
|
||||||
|
opacity?: number
|
||||||
|
) => {
|
||||||
|
const colorValue = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue(colorVariable)
|
||||||
|
.trim()
|
||||||
|
if (colorValue) {
|
||||||
|
return opacity ? hexToRGB(colorValue, opacity) : colorValue
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user