<template>
|
<div>
|
<!-- 申请类型选择 -->
|
<el-card class="type-card">
|
<div class="type-selector">
|
<div v-for="type in applicationTypes"
|
:key="type.value"
|
class="type-item"
|
:class="{ active: currentType === type.value }"
|
@click="changeType(type.value)">
|
<div class="type-icon">
|
<el-icon :size="24">
|
<component :is="type.icon" />
|
</el-icon>
|
</div>
|
<div class="type-info">
|
<div class="type-name">{{ type.name }}</div>
|
<div class="type-desc">{{ type.desc }}</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
<!-- 会议申请表单 -->
|
<el-card>
|
<div class="form-header">
|
<h3>{{ getCurrentTypeName() }}申请</h3>
|
</div>
|
<el-form ref="meetingFormRef"
|
:model="meetingForm"
|
:rules="rules"
|
label-width="100px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="会议主题"
|
prop="title">
|
<el-input v-model="meetingForm.title"
|
placeholder="请输入会议主题" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="会议室"
|
prop="roomId">
|
<el-select v-model="meetingForm.roomId"
|
placeholder="请选择会议室"
|
style="width: 100%">
|
<el-option v-for="room in meetingRooms"
|
:key="room.id"
|
:label="`${room.name} (${room.location})`"
|
:value="room.id" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="主持人"
|
prop="host">
|
<el-input v-model="meetingForm.host"
|
placeholder="请输入主持人姓名" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="会议日期"
|
prop="meetingDate">
|
<el-date-picker v-model="meetingForm.meetingDate"
|
type="date"
|
placeholder="请选择会议日期"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
:disabled-date="disabledDate"
|
style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<!-- 空列,保持布局 -->
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="开始时间"
|
prop="startTime">
|
<el-select v-model="meetingForm.startTime"
|
placeholder="请选择开始时间"
|
style="width: 100%">
|
<el-option v-for="time in startTimeOptions"
|
:key="time.value"
|
:label="time.label"
|
:value="time.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="结束时间"
|
prop="endTime">
|
<el-select v-model="meetingForm.endTime"
|
placeholder="请选择结束时间"
|
style="width: 100%">
|
<el-option v-for="time in endTimeOptions"
|
:key="time.value"
|
:label="time.label"
|
:value="time.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="参会人员"
|
prop="participants">
|
<el-select v-model="meetingForm.participants"
|
multiple
|
filterable
|
placeholder="请选择参会人员"
|
style="width: 100%">
|
<el-option v-for="user in users"
|
:key="user.userId"
|
:label="user.deptNames ? `${user.nickName} (${user.deptNames})` : user.nickName"
|
:value="user.userId" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="会议说明"
|
prop="description">
|
<el-input v-model="meetingForm.description"
|
type="textarea"
|
:rows="4"
|
placeholder="请输入会议说明" />
|
</el-form-item>
|
</el-form>
|
<div class="form-footer">
|
<el-button @click="resetForm">重置</el-button>
|
<el-button type="primary"
|
@click="submitForm">提交</el-button>
|
</div>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed, watch } from "vue";
|
import { ElMessage } from "element-plus";
|
import { Plus, Document, Promotion, Bell } from "@element-plus/icons-vue";
|
import {
|
getRoomEnum,
|
saveMeetingApplication,
|
} from "@/api/collaborativeApproval/meeting.js";
|
import { userListNoPageByTenantId } from "@/api/system/user.js";
|
|
// 当前申请类型
|
const currentType = ref("department"); // approval: 审批流程, department: 部门级, notification: 通知发布
|
|
// 申请类型选项
|
const applicationTypes = ref([
|
{
|
value: "approval",
|
name: "审批流程会议",
|
desc: "需要经过多级审批的会议申请",
|
icon: Document,
|
},
|
{
|
value: "department",
|
name: "部门级会议",
|
desc: "部门内部会议申请流程",
|
icon: Promotion,
|
},
|
{
|
value: "notification",
|
name: "会议通知",
|
desc: "无需审批直接发布的会议通知",
|
icon: Bell,
|
},
|
]);
|
|
// 表单数据
|
const meetingForm = reactive({
|
title: "",
|
type: "",
|
roomId: "",
|
host: "",
|
meetingDate: "",
|
startTime: "",
|
endTime: "",
|
participants: [],
|
description: "",
|
});
|
|
// 表单引用
|
const meetingFormRef = ref(null);
|
|
// 会议室列表
|
const meetingRooms = ref([]);
|
|
// 用户列表(系统管理-用户管理)
|
const users = ref([]);
|
|
// 时间选项(以半小时为间隔)
|
const timeOptions = ref([]);
|
|
const getTimeInMinutes = time => {
|
if (!time) return -1;
|
const [hour, minute] = time.split(":").map(Number);
|
return hour * 60 + minute;
|
};
|
|
const isToday = dateText => {
|
if (!dateText) return false;
|
const [year, month, day] = dateText.split("-").map(Number);
|
const now = new Date();
|
return (
|
year === now.getFullYear() &&
|
month === now.getMonth() + 1 &&
|
day === now.getDate()
|
);
|
};
|
|
const validateStartTime = (_rule, value, callback) => {
|
if (!value) {
|
callback();
|
return;
|
}
|
|
if (isToday(meetingForm.meetingDate)) {
|
const now = new Date();
|
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
if (getTimeInMinutes(value) > currentMinutes) {
|
callback(new Error("当天开始时间不能晚于当前时间"));
|
return;
|
}
|
}
|
|
callback();
|
};
|
|
const validateEndTime = (_rule, value, callback) => {
|
if (!value || !meetingForm.startTime) {
|
callback();
|
return;
|
}
|
|
if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
|
callback(new Error("结束时间必须大于开始时间"));
|
return;
|
}
|
|
callback();
|
};
|
|
// 表单校验规则
|
const rules = {
|
title: [{ required: true, message: "请输入会议主题", trigger: "blur" }],
|
roomId: [{ required: true, message: "请选择会议室", trigger: "change" }],
|
host: [{ required: true, message: "请输入主持人", trigger: "blur" }],
|
meetingDate: [
|
{ required: true, message: "请选择会议日期", trigger: "change" },
|
],
|
startTime: [
|
{ required: true, message: "请选择开始时间", trigger: "change" },
|
{ validator: validateStartTime, trigger: "change" },
|
],
|
endTime: [
|
{ required: true, message: "请选择结束时间", trigger: "change" },
|
{ validator: validateEndTime, trigger: "change" },
|
],
|
participants: [
|
{ required: true, message: "请选择参会人员", trigger: "change" },
|
],
|
};
|
|
const startTimeOptions = computed(() => {
|
if (!isToday(meetingForm.meetingDate)) {
|
return timeOptions.value;
|
}
|
const now = new Date();
|
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
return timeOptions.value.filter(
|
item => getTimeInMinutes(item.value) <= currentMinutes
|
);
|
});
|
|
const endTimeOptions = computed(() => {
|
if (!meetingForm.startTime) {
|
return timeOptions.value;
|
}
|
const startMinutes = getTimeInMinutes(meetingForm.startTime);
|
return timeOptions.value.filter(
|
item => getTimeInMinutes(item.value) > startMinutes
|
);
|
});
|
|
// 初始化时间选项
|
const initTimeOptions = () => {
|
const options = [];
|
const now = new Date();
|
const currentHour = now.getHours();
|
const currentMinute = now.getMinutes();
|
// meetingDate 是 "yyyy-MM-dd"
|
const meetingDate = new Date(meetingForm.meetingDate);
|
|
const isSameDay =
|
now.getFullYear() === meetingDate.getFullYear() &&
|
now.getMonth() === meetingDate.getMonth() &&
|
now.getDate() === meetingDate.getDate();
|
|
console.log("是否同一天:", isSameDay);
|
for (let hour = 8; hour <= 18; hour++) {
|
// 开始时间必须晚于当前时间
|
if (hour < currentHour && isSameDay) {
|
continue;
|
}
|
if (hour === currentHour && currentMinute > 30 && isSameDay) {
|
continue;
|
}
|
// 每个小时添加两个选项:整点和半点
|
options.push({
|
value: `${hour.toString().padStart(2, "0")}:00`,
|
label: `${hour.toString().padStart(2, "0")}:00`,
|
});
|
|
if (hour < 18) {
|
// 18:00之后没有半点选项
|
options.push({
|
value: `${hour.toString().padStart(2, "0")}:30`,
|
label: `${hour.toString().padStart(2, "0")}:30`,
|
});
|
}
|
}
|
timeOptions.value = options;
|
};
|
|
watch(
|
() => meetingForm.meetingDate,
|
() => {
|
if (
|
meetingForm.startTime &&
|
!startTimeOptions.value.some(item => item.value === meetingForm.startTime)
|
) {
|
meetingForm.startTime = "";
|
}
|
if (
|
meetingForm.endTime &&
|
!endTimeOptions.value.some(item => item.value === meetingForm.endTime)
|
) {
|
meetingForm.endTime = "";
|
}
|
if (meetingForm.startTime) {
|
meetingFormRef.value?.validateField("startTime");
|
}
|
if (meetingForm.endTime) {
|
meetingFormRef.value?.validateField("endTime");
|
}
|
initTimeOptions();
|
}
|
);
|
|
watch(
|
() => meetingForm.startTime,
|
() => {
|
if (
|
meetingForm.endTime &&
|
getTimeInMinutes(meetingForm.endTime) <=
|
getTimeInMinutes(meetingForm.startTime)
|
) {
|
meetingForm.endTime = "";
|
}
|
if (meetingForm.endTime) {
|
meetingFormRef.value?.validateField("endTime");
|
}
|
}
|
);
|
|
// 禁用日期(禁用今天之前的日期)
|
const disabledDate = time => {
|
// 禁用今天之前的日期
|
return time.getTime() < Date.now() - 86400000;
|
};
|
|
// 切换申请类型
|
const changeType = type => {
|
currentType.value = type;
|
};
|
|
// 获取当前类型名称
|
const getCurrentTypeName = () => {
|
const type = applicationTypes.value.find(t => t.value === currentType.value);
|
return type ? type.name : "";
|
};
|
|
// 重置表单
|
const resetForm = () => {
|
meetingFormRef.value?.resetFields();
|
};
|
|
// 提交表单
|
const submitForm = () => {
|
meetingFormRef.value?.validate(valid => {
|
if (valid) {
|
let formData = { ...meetingForm };
|
formData.applicationType = currentType.value;
|
formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`;
|
formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`;
|
formData.participants = JSON.stringify(formData.participants);
|
console.log(formData);
|
saveMeetingApplication(formData).then(() => {
|
// 模拟提交操作
|
ElMessage.success(`${getCurrentTypeName()}提交成功`);
|
|
// 根据不同类型执行不同操作
|
switch (currentType.value) {
|
case "approval":
|
ElMessage.info("会议已提交审批流程");
|
break;
|
case "department":
|
ElMessage.info("部门级会议申请已提交");
|
break;
|
case "notification":
|
ElMessage.info("会议通知已发布");
|
break;
|
}
|
resetForm();
|
});
|
}
|
});
|
};
|
|
// 页面加载时初始化
|
onMounted(() => {
|
initTimeOptions();
|
getRoomEnum().then(res => {
|
meetingRooms.value = res.data;
|
});
|
userListNoPageByTenantId().then(res => {
|
const list = Array.isArray(res?.data) ? res.data : [];
|
users.value = list
|
.map(item => ({
|
userId: item?.userId,
|
nickName:
|
item?.nickName || item?.userName || String(item?.userId ?? ""),
|
deptNames: item?.deptNames || "",
|
}))
|
.filter(
|
item =>
|
item.userId !== null && item.userId !== undefined && item.nickName
|
)
|
.sort((a, b) => String(a.nickName).localeCompare(String(b.nickName)));
|
});
|
});
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.type-card {
|
margin-bottom: 20px;
|
}
|
|
.type-selector {
|
display: flex;
|
gap: 20px;
|
}
|
|
.type-item {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
padding: 20px;
|
border: 1px solid #ebeef5;
|
border-radius: 8px;
|
cursor: pointer;
|
transition: all 0.3s;
|
}
|
|
.type-item:hover {
|
border-color: #409eff;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
|
.type-item.active {
|
border-color: #409eff;
|
background-color: #ecf5ff;
|
}
|
|
.type-icon {
|
margin-right: 15px;
|
color: #409eff;
|
}
|
|
.type-name {
|
font-size: 16px;
|
font-weight: 500;
|
color: #303133;
|
margin-bottom: 5px;
|
}
|
|
.type-desc {
|
font-size: 14px;
|
color: #909399;
|
}
|
|
.form-header {
|
margin-bottom: 20px;
|
padding-bottom: 15px;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.form-header h3 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.form-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
margin-top: 30px;
|
padding-top: 20px;
|
border-top: 1px solid #ebeef5;
|
}
|
</style>
|