Files
yunzerwebsiteallinone/backend/src/views/apps/oa/schedule/components/edit.vue
T

527 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-drawer v-model="visible" :title="isEdit ? '编辑日程' : '新建日程'" size="460px">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item label="日程标题" prop="title">
<el-input
v-model="form.title"
maxlength="200"
type="textarea"
:rows="4"
show-word-limit
placeholder="请输入日程标题"
/>
</el-form-item>
<el-form-item label="日程日期" prop="schedule_date">
<el-date-picker
v-model="form.schedule_date"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="全天">
<el-switch
v-model="form.all_day"
active-text="全天"
inactive-text="按时段"
/>
</el-form-item>
<el-form-item v-if="!form.all_day" label="时间段">
<div class="time-range">
<el-time-select
v-model="form.start_time"
start="00:00"
step="00:15"
end="23:45"
placeholder="开始时间"
class="time-input"
/>
<span class="time-sep">至</span>
<el-time-select
v-model="form.end_time"
start="00:00"
step="00:15"
end="23:45"
placeholder="结束(可选)"
class="time-input"
/>
</div>
</el-form-item>
<el-form-item label="优先级">
<el-radio-group v-model="form.priority">
<el-radio-button :value="0">普通</el-radio-button>
<el-radio-button :value="1">重要</el-radio-button>
<el-radio-button :value="2">紧急</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="标签颜色">
<div class="color-options">
<span
v-for="c in colorOptions"
:key="c"
class="color-dot"
:class="{ active: form.color === c }"
:style="{ background: c }"
@click="form.color = c"
/>
</div>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.content"
type="textarea"
:rows="4"
maxlength="2000"
show-word-limit
placeholder="补充说明(可选)"
/>
</el-form-item>
<el-divider>提醒设置</el-divider>
<el-form-item label="开启提醒">
<el-switch v-model="form.reminder_enabled" @change="onReminderToggle" />
<span class="form-tip">开启后将在「提醒工具」生成一条提醒任务,到点时按渠道通知你</span>
</el-form-item>
<template v-if="form.reminder_enabled">
<el-form-item label="提醒时间" prop="reminder_time">
<el-date-picker
v-model="form.reminder_time"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择提醒时间"
style="width: 100%"
/>
<span class="form-tip"
>默认取日程发生时间,可修改;提醒将在"提前提醒分钟"之前开始送达</span
>
</el-form-item>
<el-form-item label="提醒渠道" prop="remind_channels">
<el-checkbox-group v-model="form.remind_channels">
<el-checkbox value="SMS">短信</el-checkbox>
<el-checkbox value="EMAIL">邮件</el-checkbox>
<el-checkbox value="BARK">Bark</el-checkbox>
<el-checkbox value="SITE_MSG">站内信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="提前提醒(分钟)" prop="advance_minutes">
<el-input-number
v-model="form.advance_minutes"
:min="0"
:max="1440"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-alert
v-if="missingChannels.length"
type="warning"
:closable="false"
show-icon
class="reminder-alert"
>
<template #title>
未配置「{{ missingChannels.join("、") }}」的接收方式
</template>
<div class="alert-body">
<span>请先在通知设置中配置对应账号,否则该渠道提醒无法送达。</span>
<el-button
type="primary"
size="small"
link
@click="saveDraftAndGotoNotify"
>去设置并暂存当前日程</el-button
>
</div>
</el-alert>
</template>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSave"
>保存</el-button
>
</template>
</el-drawer>
</template>
<script setup>
import { computed, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { createSchedule, updateSchedule } from "@/api/oaSchedule";
import { getNotifyConfig } from "@/api/notifyConfig";
import { getReminderDetail } from "@/api/reminder";
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
// 编辑时传入的日程对象;null 表示新建
schedule: {
type: Object,
default: null
},
// 新建时预填的日期(YYYY-MM-DD)
defaultDate: {
type: String,
default: ""
},
// 从通知设置页返回时恢复的草稿(含提醒设置)
draft: {
type: Object,
default: null
}
});
const emit = defineEmits(["update:modelValue", "saved", "draft-applied"]);
const router = useRouter();
const visible = computed({
get: () => props.modelValue,
set: value => emit("update:modelValue", value)
});
const colorOptions = [
"#409EFF",
"#0AD18E",
"#F5A623",
"#F56C6C",
"#9B6DF3",
"#909399"
];
const formRef = ref(null);
const saving = ref(false);
const editingId = ref(0);
const form = reactive({
title: "",
schedule_date: "",
all_day: true,
start_time: "",
end_time: "",
priority: 0,
color: colorOptions[0],
content: "",
// 提醒绑定
reminder_enabled: false,
reminder_time: "",
remind_channels: [],
advance_minutes: 0
});
// 用户通知配置:用于判断所选渠道是否已配置接收账号
const notifyConfig = ref({});
const notifyLoading = ref(false);
const rules = {
title: [{ required: true, message: "请输入日程标题", trigger: "blur" }],
schedule_date: [
{ required: true, message: "请选择日程日期", trigger: "change" }
]
};
const isEdit = computed(() => !!props.schedule);
// 打开抽屉时根据编辑对象/默认日期/草稿重建表单。
// 用 watch 替代 el-drawer 的 @open + destroy-on-close:
// open 事件早于抽屉内容挂载,时序不稳定会导致表单控件状态错乱、点击无效。
watch(
() => props.modelValue,
open => {
if (open) {
initForm();
}
}
);
function resetReminderForm() {
form.reminder_enabled = false;
form.reminder_time = "";
form.remind_channels = [];
form.advance_minutes = 0;
}
// 由日程日期/时间推算默认提醒时间:全天取 09:00,按时段取开始时间。
function defaultReminderTime() {
if (!form.schedule_date) {
return "";
}
if (form.all_day) {
return `${form.schedule_date} 09:00:00`;
}
const t = form.start_time || "09:00";
return `${form.schedule_date} ${t}:00`;
}
// 日程时间变化时,若提醒已开启且用户未手动填写提醒时间,则同步默认值
watch(
() => [form.schedule_date, form.all_day, form.start_time],
() => {
if (form.reminder_enabled && !form.reminder_time) {
form.reminder_time = defaultReminderTime();
}
}
);
function onReminderToggle(val) {
if (val && !form.reminder_time) {
form.reminder_time = defaultReminderTime();
}
}
async function loadNotifyConfig() {
notifyLoading.value = true;
try {
const res = await getNotifyConfig();
if (res?.code === 200) {
notifyConfig.value = res.data || {};
}
} catch {
notifyConfig.value = {};
} finally {
notifyLoading.value = false;
}
}
// 当前选中渠道中,缺少对应接收账号配置的渠道
const missingChannels = computed(() => {
if (!form.reminder_enabled) {
return [];
}
const cfg = notifyConfig.value || {};
const need = [];
for (const ch of form.remind_channels) {
if (ch === "EMAIL" && !cfg.email) {
need.push("邮件");
} else if (ch === "SMS" && !cfg.phone) {
need.push("短信");
} else if (ch === "BARK" && !cfg.bark_device_key) {
need.push("Bark");
}
// SITE_MSG(站内信)无需额外配置
}
return need;
});
// 暂存当前填写内容并跳转到通知设置页
function saveDraftAndGotoNotify() {
const draft = { ...form, schedule_id: undefined };
sessionStorage.setItem("oa_schedule_draft", JSON.stringify(draft));
ElMessage.info("已暂存当前日程,去设置通知后返回即可继续");
router.push("/user/notifyConfig");
}
// 编辑已绑定提醒的日程时,回填提醒设置
async function loadReminderForEdit(reminderId) {
try {
const res = await getReminderDetail(reminderId);
if (res?.code === 200 && res.data) {
const d = res.data;
form.reminder_time = d.schedule_time || defaultReminderTime();
form.remind_channels = d.remind_channels || [];
form.advance_minutes = d.advance_minutes ?? 0;
}
} catch {
// 读取失败时不阻断编辑,退化为基础默认值
form.reminder_time = defaultReminderTime();
}
}
function initForm() {
// 优先应用从通知设置页返回的草稿
if (props.draft) {
editingId.value = 0;
Object.assign(form, props.draft);
if (!form.reminder_time) {
form.reminder_time = defaultReminderTime();
}
emit("draft-applied");
loadNotifyConfig();
return;
}
if (props.schedule) {
editingId.value = props.schedule.id;
form.title = props.schedule.title;
form.schedule_date = props.schedule.schedule_date;
form.all_day = props.schedule.all_day === 1;
form.start_time = props.schedule.start_time || "";
form.end_time = props.schedule.end_time || "";
form.priority = props.schedule.priority || 0;
form.color = props.schedule.color || colorOptions[0];
form.content = props.schedule.content || "";
// 回显已绑定的提醒任务
if (props.schedule.reminder_id > 0) {
form.reminder_enabled = true;
loadReminderForEdit(props.schedule.reminder_id);
} else {
resetReminderForm();
}
} else {
editingId.value = 0;
form.title = "";
form.schedule_date = props.defaultDate || "";
form.all_day = true;
form.start_time = "";
form.end_time = "";
form.priority = 0;
form.color = colorOptions[0];
form.content = "";
resetReminderForm();
}
loadNotifyConfig();
}
async function handleSave() {
try {
await formRef.value.validate();
} catch {
return;
}
if (!form.all_day) {
if (!form.start_time) {
ElMessage.warning("请选择开始时间");
return;
}
if (form.end_time && form.end_time < form.start_time) {
ElMessage.warning("结束时间不能早于开始时间");
return;
}
}
// 开启提醒但所选渠道缺少接收配置:阻断保存,引导先去通知设置
if (form.reminder_enabled && missingChannels.value.length) {
ElMessage.warning(
`请先配置${missingChannels.value.join("、")}的接收方式,或取消该渠道`
);
return;
}
if (form.reminder_enabled && !form.reminder_time) {
ElMessage.warning("请选择提醒时间");
return;
}
if (form.reminder_enabled && form.remind_channels.length === 0) {
ElMessage.warning("请至少选择一个提醒渠道");
return;
}
saving.value = true;
try {
const payload = {
title: form.title.trim(),
content: form.content,
schedule_date: form.schedule_date,
start_time: form.all_day ? "" : form.start_time,
end_time: form.all_day ? "" : form.end_time || "",
all_day: form.all_day ? 1 : 0,
color: form.color,
priority: form.priority,
reminder_enabled: !!form.reminder_enabled,
reminder_time: form.reminder_enabled ? form.reminder_time : "",
remind_channels: form.reminder_enabled ? form.remind_channels : [],
advance_minutes: form.reminder_enabled
? Number(form.advance_minutes || 0)
: 0
};
const res =
isEdit.value
? await updateSchedule(editingId.value, payload)
: await createSchedule(payload);
if (res?.code === 200) {
ElMessage.success(isEdit.value ? "日程已更新" : "日程已创建");
emit("saved");
visible.value = false;
} else {
ElMessage.error(res?.msg || "保存失败");
}
} finally {
saving.value = false;
}
}
</script>
<style lang="less" scoped>
.time-range {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.time-input {
flex: 1;
min-width: 0;
}
.time-sep {
color: #909399;
}
.color-options {
display: flex;
align-items: center;
gap: 12px;
}
.color-dot {
width: 22px;
height: 22px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
transition: transform 0.15s;
}
.color-dot:hover {
transform: scale(1.12);
}
.color-dot.active {
border-color: #fff;
outline: 2px solid #409eff;
}
.form-tip {
display: block;
font-size: 12px;
color: #909399;
line-height: 1.5;
margin-top: 4px;
}
.reminder-alert {
margin-bottom: 8px;
}
.reminder-alert .alert-body {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.reminder-alert .alert-body span {
font-size: 13px;
}
// ===== 暗色主题适配 =====
html.dark & {
.time-sep {
color: var(--el-text-color-secondary);
}
.color-dot.active {
border-color: var(--el-bg-color);
outline-color: var(--el-color-primary);
}
}
</style>