月计划填报管理页面优化

This commit is contained in:
BianLzhaoMin 2025-12-19 17:30:43 +08:00
parent 3c394a9c2e
commit 6e60d53b8d
9 changed files with 1569 additions and 568 deletions

View File

@ -0,0 +1,37 @@
import request from '@/utils/request'
// 计划管理- 查询列表
export function listPlanAPI(query) {
return request({
url: '/personnel/getPersonnelList',
method: 'GET',
params: query,
})
}
// 计划管理- 新增
export function addPlanAPI(data) {
return request({
url: '/personnel/addPersonnel',
method: 'POST',
data,
})
}
// 计划管理- 修改
export function updatePlanAPI(data) {
return request({
url: '/personnel/updatePersonnel',
method: 'POST',
data,
})
}
// 计划管理- 删除
export function delPlanAPI(data) {
return request({
url: `/personnel/delPersonnel`,
method: 'POST',
data,
})
}

View File

@ -0,0 +1,37 @@
import request from '@/utils/request'
// 计划管理- 查询列表
export function listPlanAPI(query) {
return request({
url: '/personnel/getPersonnelList',
method: 'GET',
params: query,
})
}
// 计划管理- 新增
export function addPlanAPI(data) {
return request({
url: '/personnel/addPersonnel',
method: 'POST',
data,
})
}
// 计划管理- 修改
export function updatePlanAPI(data) {
return request({
url: '/personnel/updatePersonnel',
method: 'POST',
data,
})
}
// 计划管理- 删除
export function delPlanAPI(data) {
return request({
url: `/personnel/delPersonnel`,
method: 'POST',
data,
})
}

View File

@ -320,7 +320,7 @@ const props = defineProps({
},
actionWidth: {
type: [String, Number],
default: 240, // 3
default: 200, // 3
},
actionFixed: {
type: [String, Boolean],

View File

@ -31,7 +31,15 @@
>
<!-- 工具栏插槽 -->
<template #toolbar>
<slot name="toolbar" />
<slot
name="toolbar"
:formData="
Object.assign({}, searchFormRef?.getFormData() || {}, {
pageNum: pagination.page,
pageSize: pagination.pageSize,
}) || {}
"
/>
</template>
<!-- 自定义列插槽 -->
@ -125,7 +133,7 @@ const loading = ref(false)
const tableData = ref([])
const pagination = reactive({
page: 1,
limit: 10,
pageSize: 10,
total: 0,
})
@ -179,7 +187,7 @@ const fetchData = async (formData = {}) => {
...formData,
...props.defaultQueryParams,
pageNum: pagination.page, // pageNum
pageSize: pagination.limit, // pageSize
pageSize: pagination.pageSize, // pageSize
}
const response = await props.loadData(params)

View File

@ -1,296 +1,383 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="任务名称" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="请输入任务名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="任务名称" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="请输入任务名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="任务组名" prop="jobGroup">
<el-select
v-model="queryParams.jobGroup"
placeholder="请选择任务组名"
clearable
style="width: 200px"
>
<el-option
v-for="dict in sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择任务状态"
clearable
style="width: 200px"
>
<el-option
v-for="dict in sys_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['monitor:job:add']"
>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['monitor:job:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>导出</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Operation"
@click="handleJobLog"
v-hasPermi="['monitor:job:query']"
>日志</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务编号" width="100" align="center" prop="jobId" />
<el-table-column
label="任务名称"
align="center"
prop="jobName"
:show-overflow-tooltip="true"
/>
</el-form-item>
<el-form-item label="任务组名" prop="jobGroup">
<el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable style="width: 200px">
<el-option
v-for="dict in sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable style="width: 200px">
<el-option
v-for="dict in sys_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table-column label="任务组名" align="center" prop="jobGroup">
<template #default="scope">
<dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
</template>
</el-table-column>
<el-table-column
label="调用目标字符串"
align="center"
prop="invokeTarget"
:show-overflow-tooltip="true"
/>
<el-table-column
label="cron执行表达式"
align="center"
prop="cronExpression"
:show-overflow-tooltip="true"
/>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
width="200"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['monitor:job:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['monitor:job:remove']"
></el-button>
</el-tooltip>
<el-tooltip content="执行一次" placement="top">
<el-button
link
type="primary"
icon="CaretRight"
@click="handleRun(scope.row)"
v-hasPermi="['monitor:job:changeStatus']"
></el-button>
</el-tooltip>
<el-tooltip content="任务详细" placement="top">
<el-button
link
type="primary"
icon="View"
@click="handleView(scope.row)"
v-hasPermi="['monitor:job:query']"
></el-button>
</el-tooltip>
<el-tooltip content="调度日志" placement="top">
<el-button
link
type="primary"
icon="Operation"
@click="handleJobLog(scope.row)"
v-hasPermi="['monitor:job:query']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['monitor:job:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['monitor:job:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Operation"
@click="handleJobLog"
v-hasPermi="['monitor:job:query']"
>日志</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务编号" width="100" align="center" prop="jobId" />
<el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
<el-table-column label="任务组名" align="center" prop="jobGroup">
<template #default="scope">
<dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
<!-- 添加或修改定时任务对话框 -->
<el-dialog :title="title" v-model="open" width="820px" append-to-body>
<el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" placeholder="请输入任务名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组" prop="jobGroup">
<el-select v-model="form.jobGroup" placeholder="请选择">
<el-option
v-for="dict in sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="invokeTarget">
<template #label>
<span>
调用方法
<el-tooltip placement="top">
<template #content>
<div>
Bean调用示例ryTask.ryParams('ry')
<br />Class类调用示例com.ruoyi.quartz.task.RyTask.ryParams('ry')
<br />参数说明支持字符串布尔类型长整型浮点型整型
</div>
</template>
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<el-input
v-model="form.invokeTarget"
placeholder="请输入调用目标字符串"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="cron表达式" prop="cronExpression">
<el-input
v-model="form.cronExpression"
placeholder="请输入cron执行表达式"
>
<template #append>
<el-button type="primary" @click="handleShowCron">
生成表达式
<i class="el-icon-time el-icon--right"></i>
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" v-if="form.jobId !== undefined">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_job_status"
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略" prop="misfirePolicy">
<el-radio-group v-model="form.misfirePolicy">
<el-radio-button value="1">立即执行</el-radio-button>
<el-radio-button value="2">执行一次</el-radio-button>
<el-radio-button value="3">放弃执行</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发" prop="concurrent">
<el-radio-group v-model="form.concurrent">
<el-radio-button value="0">允许</el-radio-button>
<el-radio-button value="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-table-column>
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
<el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</el-dialog>
<el-dialog title="Cron表达式生成器" v-model="openCron" append-to-body destroy-on-close>
<crontab
ref="crontabRef"
@hide="openCron = false"
@fill="crontabFill"
:expression="expression"
></crontab>
</el-dialog>
<!-- 任务日志详细 -->
<el-dialog title="任务详细" v-model="openView" width="700px" append-to-body>
<el-form :model="form" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
<el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="cron表达式">{{ form.cronExpression }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次执行时间:">{{
parseTime(form.nextValidTime)
}}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务状态:">
<div v-if="form.status == 0">正常</div>
<div v-else-if="form.status == 1">暂停</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发:">
<div v-if="form.concurrent == 0">允许</div>
<div v-else-if="form.concurrent == 1">禁止</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略:">
<div v-if="form.misfirePolicy == 0">默认策略</div>
<div v-else-if="form.misfirePolicy == 1">立即执行</div>
<div v-else-if="form.misfirePolicy == 2">执行一次</div>
<div v-else-if="form.misfirePolicy == 3">放弃执行</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="openView = false"> </el-button>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['monitor:job:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['monitor:job:remove']"></el-button>
</el-tooltip>
<el-tooltip content="执行一次" placement="top">
<el-button link type="primary" icon="CaretRight" @click="handleRun(scope.row)" v-hasPermi="['monitor:job:changeStatus']"></el-button>
</el-tooltip>
<el-tooltip content="任务详细" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
</el-tooltip>
<el-tooltip content="调度日志" placement="top">
<el-button link type="primary" icon="Operation" @click="handleJobLog(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改定时任务对话框 -->
<el-dialog :title="title" v-model="open" width="820px" append-to-body>
<el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" placeholder="请输入任务名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组" prop="jobGroup">
<el-select v-model="form.jobGroup" placeholder="请选择">
<el-option
v-for="dict in sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="invokeTarget">
<template #label>
<span>
调用方法
<el-tooltip placement="top">
<template #content>
<div>
Bean调用示例ryTask.ryParams('ry')
<br />Class类调用示例com.ruoyi.quartz.task.RyTask.ryParams('ry')
<br />参数说明支持字符串布尔类型长整型浮点型整型
</div>
</template>
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="cron表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
<template #append>
<el-button type="primary" @click="handleShowCron">
生成表达式
<i class="el-icon-time el-icon--right"></i>
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" v-if="form.jobId !== undefined">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_job_status"
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略" prop="misfirePolicy">
<el-radio-group v-model="form.misfirePolicy">
<el-radio-button value="1">立即执行</el-radio-button>
<el-radio-button value="2">执行一次</el-radio-button>
<el-radio-button value="3">放弃执行</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发" prop="concurrent">
<el-radio-group v-model="form.concurrent">
<el-radio-button value="0">允许</el-radio-button>
<el-radio-button value="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog title="Cron表达式生成器" v-model="openCron" append-to-body destroy-on-close>
<crontab ref="crontabRef" @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
</el-dialog>
<!-- 任务日志详细 -->
<el-dialog title="任务详细" v-model="openView" width="700px" append-to-body>
<el-form :model="form" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
<el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="cron表达式">{{ form.cronExpression }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务状态:">
<div v-if="form.status == 0">正常</div>
<div v-else-if="form.status == 1">暂停</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发:">
<div v-if="form.concurrent == 0">允许</div>
<div v-else-if="form.concurrent == 1">禁止</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略:">
<div v-if="form.misfirePolicy == 0">默认策略</div>
<div v-else-if="form.misfirePolicy == 1">立即执行</div>
<div v-else-if="form.misfirePolicy == 2">执行一次</div>
<div v-else-if="form.misfirePolicy == 3">放弃执行</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="openView = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</el-dialog>
</div>
</template>
<script setup name="Job">
import Crontab from '@/components/Crontab'
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"
import {
listJob,
getJob,
delJob,
addJob,
updateJob,
runJob,
changeJobStatus,
} from '@/api/monitor/job'
const router = useRouter()
const { proxy } = getCurrentInstance()
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status")
const { sys_job_group, sys_job_status } = proxy.useDict('sys_job_group', 'sys_job_status')
const jobList = ref([])
const open = ref(false)
@ -300,202 +387,218 @@ const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const title = ref('')
const openView = ref(false)
const openCron = ref(false)
const expression = ref("")
const expression = ref('')
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined
},
rules: {
jobName: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }],
cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "change" }]
}
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined,
},
rules: {
jobName: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
invokeTarget: [{ required: true, message: '调用目标字符串不能为空', trigger: 'blur' }],
cronExpression: [{ required: true, message: 'cron执行表达式不能为空', trigger: 'change' }],
},
})
const { queryParams, form, rules } = toRefs(data)
/** 查询定时任务列表 */
function getList() {
loading.value = true
listJob(queryParams.value).then(response => {
jobList.value = response.rows
total.value = response.total
loading.value = false
})
loading.value = true
listJob(queryParams.value).then((response) => {
jobList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 任务组名字典翻译 */
function jobGroupFormat(row, column) {
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup)
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup)
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
jobId: undefined,
jobName: undefined,
jobGroup: undefined,
invokeTarget: undefined,
cronExpression: undefined,
misfirePolicy: 1,
concurrent: 1,
status: "0"
}
proxy.resetForm("jobRef")
form.value = {
jobId: undefined,
jobName: undefined,
jobGroup: undefined,
invokeTarget: undefined,
cronExpression: undefined,
misfirePolicy: 1,
concurrent: 1,
status: '0',
}
proxy.resetForm('jobRef')
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
handleQuery()
proxy.resetForm('queryRef')
handleQuery()
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.jobId)
single.value = selection.length != 1
multiple.value = !selection.length
ids.value = selection.map((item) => item.jobId)
single.value = selection.length != 1
multiple.value = !selection.length
}
//
function handleCommand(command, row) {
switch (command) {
case "handleRun":
handleRun(row)
break
case "handleView":
handleView(row)
break
case "handleJobLog":
handleJobLog(row)
break
default:
break
}
switch (command) {
case 'handleRun':
handleRun(row)
break
case 'handleView':
handleView(row)
break
case 'handleJobLog':
handleJobLog(row)
break
default:
break
}
}
//
function handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用"
proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
return changeJobStatus(row.jobId, row.status)
}).then(() => {
proxy.$modal.msgSuccess(text + "成功")
}).catch(function () {
row.status = row.status === "0" ? "1" : "0"
})
let text = row.status === '0' ? '启用' : '停用'
proxy.$modal
.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?')
.then(function () {
return changeJobStatus(row.jobId, row.status)
})
.then(() => {
proxy.$modal.msgSuccess(text + '成功')
})
.catch(function () {
row.status = row.status === '0' ? '1' : '0'
})
}
/* 立即执行一次 */
function handleRun(row) {
proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
return runJob(row.jobId, row.jobGroup)
}).then(() => {
proxy.$modal.msgSuccess("执行成功")})
.catch(() => {})
proxy.$modal
.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?')
.then(function () {
return runJob(row.jobId, row.jobGroup)
})
.then(() => {
proxy.$modal.msgSuccess('执行成功')
})
.catch(() => {})
}
/** 任务详细信息 */
function handleView(row) {
getJob(row.jobId).then(response => {
form.value = response.data
openView.value = true
})
getJob(row.jobId).then((response) => {
form.value = response.data
openView.value = true
})
}
/** cron表达式按钮操作 */
function handleShowCron() {
expression.value = form.value.cronExpression
openCron.value = true
expression.value = form.value.cronExpression
openCron.value = true
}
/** 确定后回传值 */
function crontabFill(value) {
form.value.cronExpression = value
form.value.cronExpression = value
}
/** 任务日志列表查询 */
function handleJobLog(row) {
const jobId = row.jobId || 0
router.push('/monitor/job-log/index/' + jobId)
const jobId = row.jobId || 0
router.push('/monitor/job-log/index/' + jobId)
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = "添加任务"
reset()
open.value = true
title.value = '添加任务'
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const jobId = row.jobId || ids.value
getJob(jobId).then(response => {
form.value = response.data
open.value = true
title.value = "修改任务"
})
reset()
const jobId = row.jobId || ids.value
getJob(jobId).then((response) => {
form.value = response.data
open.value = true
title.value = '修改任务'
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["jobRef"].validate(valid => {
if (valid) {
if (form.value.jobId != undefined) {
updateJob(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addJob(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
proxy.$refs['jobRef'].validate((valid) => {
if (valid) {
if (form.value.jobId != undefined) {
updateJob(form.value).then((response) => {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
})
} else {
addJob(form.value).then((response) => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const jobIds = row.jobId || ids.value
proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
return delJob(jobIds)
}).then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
const jobIds = row.jobId || ids.value
proxy.$modal
.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?')
.then(function () {
return delJob(jobIds)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("monitor/job/export", {
...queryParams.value,
}, `job_${new Date().getTime()}.xlsx`)
proxy.download(
'monitor/job/export',
{
...queryParams.value,
},
`job_${new Date().getTime()}.xlsx`,
)
}
getList()

View File

@ -1,59 +1,75 @@
<template>
<div class="monthly-add-form">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="auto"
size="large"
class="month-form"
>
<el-row :gutter="12" align="middle">
<el-col :span="8">
<el-form-item label="计划执行月份" prop="month">
<el-date-picker
v-model="formData.month"
type="month"
value-format="YYYY-MM"
placeholder="请选择月份"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form-item prop="keyword" class="keyword-item">
<el-input
placeholder="输入关键字"
clearable
v-model.trim="formData.keyword"
@keyup.enter="onSearch"
style="width: 240px"
<!-- 表单区域 -->
<div class="form-section">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="140px"
size="default"
class="month-form"
>
<template #suffix>
<el-icon @click="onSearch" style="cursor: pointer">
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="月计划执行月份:" prop="month" class="month-item">
<el-date-picker
v-model="formData.month"
type="month"
value-format="YYYY-MM"
placeholder="xxxx-xx"
style="width: 200px"
clearable
/>
</el-form-item>
</el-form>
<!-- 搜索区域 -->
<div class="search-section">
<el-input
v-model.trim="formData.keyword"
placeholder="输入关键字"
clearable
@keyup.enter="onSearch"
class="search-input"
>
<template #prefix>
<el-icon class="search-icon" @click="onSearch">
<Search />
</el-icon>
</template>
</el-input>
</div>
</div>
<!-- 任务列表 -->
<el-table
border
:data="tableData"
v-loading="loading"
@selection-change="onSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" prop="projectName" label="项目名称" min-width="260" />
<el-table-column align="center" prop="workTask" label="工作任务" min-width="360" />
</el-table>
<div class="table-section">
<el-table
:data="tableData"
v-loading="loading"
@selection-change="onSelectionChange"
class="task-table"
stripe
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 600 }"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column
prop="name"
label="项目名称"
min-width="260"
show-overflow-tooltip
/>
<el-table-column
prop="workTask"
label="工作任务"
min-width="360"
show-overflow-tooltip
/>
</el-table>
</div>
</div>
</template>
<script setup name="MonthlyPlanAddForm">
import { ref, watch, getCurrentInstance } from 'vue'
import { ref, watch, getCurrentInstance, reactive } from 'vue'
import { Search } from '@element-plus/icons-vue'
const props = defineProps({
@ -73,7 +89,10 @@ const { proxy } = getCurrentInstance()
const formRef = ref(null)
const loading = ref(false)
const tableData = ref([])
const tableData = ref([
{ name: '项目1', workTask: '工作任务1' },
{ name: '项目2', workTask: '工作任务2' },
])
const selectedRows = ref([])
const formData = ref({
@ -126,18 +145,116 @@ watch(
defineExpose({
getSelectedTasks: () => selectedRows.value,
search: fetchData,
validate: () => formRef.value.validate(),
validate: async () => {
await formRef.value.validate()
if (selectedRows.value.length === 0) {
proxy.$modal.msgError('请选择项目')
return false
}
return true
},
})
</script>
<style scoped lang="scss">
.monthly-add-form {
.month-form {
margin-bottom: 12px;
padding: 0;
.form-section {
margin-bottom: 20px;
.month-form {
margin-bottom: 16px;
.month-item {
margin-bottom: 0;
:deep(.el-form-item__label) {
font-weight: 500;
color: #303133;
&::before {
content: '*';
color: #f56c6c;
margin-right: 4px;
}
}
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
}
}
.search-section {
display: flex;
justify-content: flex-start;
margin-top: 16px;
.search-input {
width: 300px;
:deep(.el-input__wrapper) {
border-radius: 4px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
transition: all 0.3s;
&:hover {
box-shadow: 0 0 0 1px #c0c4cc inset;
}
&.is-focus {
box-shadow: 0 0 0 1px #409eff inset;
}
}
.search-icon {
color: #909399;
cursor: pointer;
font-size: 16px;
transition: color 0.3s;
&:hover {
color: #409eff;
}
}
}
}
}
.keyword-item :deep(.el-form-item__content) {
justify-content: flex-end;
.table-section {
.task-table {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
:deep(.el-table__header) {
th {
padding: 12px 0;
font-size: 14px;
}
}
:deep(.el-table__body) {
td {
padding: 12px 0;
font-size: 14px;
}
tr {
&:hover {
background-color: #f5f7fa;
}
}
}
:deep(.el-checkbox) {
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #409eff;
border-color: #409eff;
}
}
}
}
}
</style>

View File

@ -12,29 +12,18 @@
ref="formRef"
:model="formData"
:rules="rules"
label-width="auto"
label-width="180"
size="large"
:disabled="isDetail"
>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="运检站" prop="stationId">
<el-select
v-model="formData.stationId"
placeholder="请选择运检站"
clearable
filterable
>
<el-option
v-for="item in stationOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="运检站">
<el-input v-model="formData.stationName" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="12">
<el-form-item label="专业" prop="majorId">
<el-select
v-model="formData.majorId"
@ -42,16 +31,18 @@
clearable
>
<el-option
v-for="item in majorOptions"
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in majorOptions"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-col :span="8">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="业务类型" prop="businessTypeId">
<el-select
v-model="formData.businessTypeId"
@ -67,28 +58,38 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="项目名称" prop="projectName">
<el-col :span="12">
<el-form-item label="项目名称">
<el-input
v-model.trim="formData.projectName"
placeholder="请输入项目名称"
maxlength="50"
show-word-limit
clearable
disabled
placeholder="请输入项目名称"
v-model.trim="formData.projectName"
/>
</el-form-item>
</el-col>
</el-row>
<el-col :span="8">
<el-form-item label="风险等级" prop="riskLevel">
<el-select
v-model="formData.riskLevel"
placeholder="请选择风险等级"
clearable
>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="工作任务">
<el-input
type="textarea"
v-model.trim="formData.workContent"
placeholder="请输入作业内容"
maxlength="500"
show-word-limit
disabled
:autosize="{ minRows: 4, maxRows: 12 }"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="类别" prop="typeId">
<el-select v-model="formData.typeId" placeholder="请选择类别" clearable>
<el-option
v-for="item in riskLevelOptions"
:key="item.value"
@ -98,34 +99,70 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-col :span="8">
<el-form-item label="计划执行月份" prop="month">
<el-date-picker
v-model="formData.month"
type="month"
value-format="YYYY-MM"
placeholder="请选择月份"
style="width: 100%"
<el-row :gutter="24">
<template v-for="(item, index) in formData.workLoadList" :key="index">
<el-col :span="8">
<el-form-item :label="index == 0 ? '工作量' : ''" prop="workType">
<el-select
clearable
v-model="item.workType"
placeholder="请选择工作量类别"
>
<el-option
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in riskLevelOptions"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label-width="0" prop="count">
<el-input
v-model="item.count"
style="width: 240px"
placeholder="输入数量"
>
<template #suffix>
<el-button
type="success"
icon="Plus"
size="small"
v-if="index === 0 && !isDetail"
@click="onAddWorkLoad"
/>
<el-button
type="danger"
icon="Delete"
size="small"
v-if="index !== 0 && !isDetail"
@click="onDeleteWorkLoad(index)"
/>
</template>
</el-input>
</el-form-item>
</el-col>
</template>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="塔基数" prop="towerCount">
<el-input
clearable
placeholder="请输入塔基数量"
v-model.trim="formData.towerCount"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="作业任务" prop="workContent">
<el-input
type="textarea"
v-model.trim="formData.workContent"
placeholder="请输入作业内容"
maxlength="500"
show-word-limit
:autosize="{ minRows: 3, maxRows: 6 }"
/>
</el-form-item>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="计划开始时间" prop="planStartDate">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="计划开始时间">
<el-date-picker
v-model="formData.planStartDate"
type="date"
@ -135,8 +172,8 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="计划结束时间" prop="planEndDate">
<el-col :span="12">
<el-form-item label="计划结束时间">
<el-date-picker
v-model="formData.planEndDate"
type="date"
@ -148,42 +185,293 @@
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input
type="textarea"
v-model.trim="formData.remark"
placeholder="请输入备注"
maxlength="200"
show-word-limit
:autosize="{ minRows: 3, maxRows: 6 }"
/>
</el-form-item>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="计划投入管理人员" prop="planManagers">
<el-input
v-model="selectedManagerNames"
readonly
placeholder="点击选择管理人员"
@click="onOpenPersonPicker"
>
<template #suffix>
<el-icon
class="clickable-suffix"
@click.stop="onOpenPersonPicker"
>
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划投入车辆数量" prop="planVehicleCount">
<el-input
clearable
placeholder="请输入车辆数量"
v-model.trim="formData.planVehicleCount"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="7">
<el-form-item label="计划投入熟练工人数" prop="skilledStaffCount">
<el-input
clearable
placeholder="请输入熟练工人数"
v-model.trim="formData.skilledStaffCount"
/>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item
label="计划投入工日"
prop="skilledWorkDays"
label-width="auto"
>
<el-input
clearable
placeholder="请输入熟练工工日"
v-model.trim="formData.skilledWorkDays"
/>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="计划投入辅助工人数" prop="assistantStaffCount">
<el-input
clearable
placeholder="请输入辅助工人数"
v-model.trim="formData.assistantStaffCount"
/>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item
label="计划投入工日"
prop="assistantWorkDays"
label-width="auto"
>
<el-input
clearable
placeholder="请输入辅助工工日"
v-model.trim="formData.assistantWorkDays"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="计划投入分包车辆" prop="subcontractVehicleCount">
<el-input
clearable
placeholder="请输入分包车辆数量"
v-model.trim="formData.subcontractVehicleCount"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际工作天数">
<el-input
clearable
placeholder="请输入实际工作天数"
v-model.trim="formData.actualWorkDays"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 人员排班区域先留出结构后续可按业务补充 -->
<!-- 人员排班区域 -->
<el-card shadow="never" class="card-section">
<template #header>
<div class="card-header">人员排班</div>
</template>
<div class="calendar-placeholder">
<span>这里展示月份日历和每日人员排班待与后端字段对接后完善</span>
<div class="schedule-wrapper">
<div class="schedule-tip">
<span class="schedule-tip-extra">
仅在计划开始时间 ~
计划结束时间范围内可排班表单中的计划投入管理人员只是模板不会被日历上的增删影响
</span>
</div>
<div class="schedule-grid">
<div
v-for="day in calendarDays"
:key="day.date"
class="schedule-cell"
:class="{ 'is-disabled': !day.isInRange }"
>
<div class="cell-date">{{ day.label }}</div>
<div class="cell-persons">
<template v-if="day.managers && day.managers.length">
<el-popover
v-if="day.managers.length > maxShowInCell"
placement="top"
trigger="hover"
>
<template #reference>
<div class="person-tags">
<el-tag
size="small"
v-for="person in day.managers.slice(
0,
maxShowInCell,
)"
:key="person.id"
closable
@close.stop="
onRemovePersonFromDay(day.date, person)
"
>
{{ person.name }}
</el-tag>
<span class="more-text">
+{{ day.managers.length - maxShowInCell }}
</span>
</div>
</template>
<div class="popover-list">
<el-tag
v-for="person in day.managers"
:key="person.id"
size="small"
closable
@close.stop="onRemovePersonFromDay(day.date, person)"
>
{{ person.name }}
</el-tag>
</div>
</el-popover>
<template v-else>
<div class="person-tags">
<el-tag
size="small"
v-for="person in day.managers"
:key="person.id"
closable
@close.stop="onRemovePersonFromDay(day.date, person)"
>
{{ person.name }}
</el-tag>
</div>
</template>
</template>
<span
v-else
class="empty-tip-small"
:class="{ 'is-disabled-text': !day.isInRange }"
>
{{ day.isInRange ? '未安排人员' : '不可排班' }}
</span>
</div>
<div class="cell-actions" v-if="!isDetail">
<el-button
type="primary"
size="small"
plain
:disabled="!day.isInRange || !formData.planManagers.length"
@click="onFillManagersForDay(day.date)"
>
安排人员
</el-button>
<el-button
type="danger"
size="small"
plain
:disabled="!day.isInRange || !day.managers.length"
@click="onClearManagersForDay(day.date)"
>
清除
</el-button>
</div>
</div>
</div>
</div>
</el-card>
<!-- 底部操作按钮详情模式下隐藏 -->
<div v-if="!isDetail" class="page-footer">
<ComButton plain type="info" @click="onBack">取消</ComButton>
<ComButton type="primary" @click="onSubmit">保存</ComButton>
<div class="page-footer">
<ComButton plain type="info" @click="onBack">
{{ isDetail ? '返回' : '取消' }}
</ComButton>
<ComButton v-if="!isDetail" type="primary" @click="onSubmit">保存</ComButton>
</div>
<!-- 人员选择弹窗使用封装的 ComDialog -->
<ComDialog :dialog-config="managerDialogConfig" @closeDialogOuter="onCloseDialogOuter">
<template #outerContent>
<el-row :gutter="20">
<el-col :span="16">
<div class="person-search-bar">
<el-input
v-model.trim="managerDialog.keyword"
placeholder="输入姓名搜索"
clearable
prefix-icon="Search"
/>
</div>
<el-table
ref="personTableRef"
:data="filteredPersons"
height="400px"
@selection-change="onManagerSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="org" label="人员所属单位" width="140" />
<el-table-column prop="name" label="姓名" width="120" />
<el-table-column prop="gender" label="性别" width="80" />
<el-table-column prop="station" label="岗位" width="140" />
<el-table-column prop="position" label="岗位性质" width="140" />
<el-table-column prop="type" label="人员分类" />
</el-table>
</el-col>
<el-col :span="8">
<div class="selected-panel">
<div class="panel-header">
<span>已选人员</span>
<el-button type="text" @click="onClearManagers">清空</el-button>
</div>
<div class="selected-list">
<div>
<el-tag
closable
:key="item.id"
class="selected-tag"
@close="onRemoveManager(item)"
v-for="item in managerDialog.selected"
>
{{ item.name }}
</el-tag>
</div>
<div v-if="!managerDialog.selected.length" class="empty-tip">
暂未选择人员
</div>
</div>
</div>
</el-col>
</el-row>
<el-row class="common-btn-row" justify="end" style="margin-top: 16px">
<ComButton plain type="info" @click="managerDialog.visible = false">
取消
</ComButton>
<ComButton type="primary" @click="onConfirmManager">确定</ComButton>
</el-row>
</template>
</ComDialog>
</div>
</template>
<script setup name="MonthlyPlanEdit">
import { ref, computed, getCurrentInstance } from 'vue'
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { addPlanAPI, updatePlanAPI } from '@/api/planMange/plan.js'
import ComButton from '@/components/ComButton/index.vue'
import ComDialog from '@/components/ComDialog/index.vue'
import { Search } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
@ -202,16 +490,34 @@ const formRef = ref(null)
const getInitFormData = () => ({
planId: null,
stationId: null,
stationName: '测试运检站1',
majorId: null,
businessTypeId: null,
projectName: '',
workContent: '',
projectName: '测试项目123',
workContent:
'工作内容非常多工作内容非常多···工作内容非常多···工作内容非常多···工作内容非常多···工作内容非常多···工作内容非常多······--',
riskLevel: null,
month: '',
planStartDate: '',
planEndDate: '',
remark: '',
// 2025 12
month: '2025-12',
planStartDate: '2025-12-06',
planEndDate: '2025-12-30',
typeId: null,
towerCount: '',
planManagers: [],
planVehicleCount: '',
skilledStaffCount: '',
skilledWorkDays: '',
assistantStaffCount: '',
assistantWorkDays: '',
subcontractVehicleCount: '',
actualWorkDays: '',
workLoadList: [
{
workType: '',
count: '',
},
],
})
const formData = ref(getInitFormData())
@ -239,15 +545,21 @@ const riskLevelOptions = [
]
const rules = {
stationId: [{ required: true, message: '请选择运检站', trigger: 'change' }],
majorId: [{ required: true, message: '请选择专业', trigger: 'change' }],
businessTypeId: [{ required: true, message: '请选择业务类型', trigger: 'change' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
workContent: [{ required: true, message: '请输入作业内容', trigger: 'blur' }],
riskLevel: [{ required: true, message: '请选择风险等级', trigger: 'change' }],
month: [{ required: true, message: '请选择计划执行月份', trigger: 'change' }],
planStartDate: [{ required: true, message: '请选择计划开始时间', trigger: 'change' }],
planEndDate: [{ required: true, message: '请选择计划结束时间', trigger: 'change' }],
typeId: [{ required: true, message: '请输选择类别', trigger: 'change' }],
workType: [{ required: true, message: '请选择计工作量类别', trigger: 'change' }],
count: [{ required: true, message: '请输入数量', trigger: 'blur' }],
towerCount: [{ required: true, message: '请输入塔基数量', trigger: 'blur' }],
planManagers: [{ required: true, message: '请选择计划投入管理人员', trigger: 'blur' }],
planVehicleCount: [{ required: true, message: '请输入计划投入车数量', trigger: 'blur' }],
skilledStaffCount: [{ required: true, message: '请输入计划投入熟练工人数', trigger: 'blur' }],
skilledWorkDays: [{ required: true, message: '请输入计划投入工日', trigger: 'blur' }],
assistantStaffCount: [{ required: true, message: '请输入计划投入辅助工人数', trigger: 'blur' }],
subcontractVehicleCount: [
{ required: true, message: '请输入计划投入分包车辆数量', trigger: 'blur' },
],
assistantWorkDays: [{ required: true, message: '请输入辅助工工日', trigger: 'blur' }],
}
const onBack = () => {
@ -255,21 +567,238 @@ const onBack = () => {
}
const onSubmit = () => {
formRef.value.validate(async (valid) => {
if (!valid) return
const API = mode.value === 'edit' ? updatePlanAPI : addPlanAPI
const payload = { ...formData.value }
try {
const res = await API(payload)
if (res.code === 200) {
proxy.$modal.msgSuccess(mode.value === 'edit' ? '编辑成功' : '新增成功')
onBack()
formRef.value.validate(async (valid, fields) => {
if (!valid) {
//
if (fields && Object.keys(fields).length) {
const firstProp = Object.keys(fields)[0]
formRef.value.scrollToField(firstProp)
}
} catch (error) {
console.error('保存月计划失败:', error)
return
}
//
// const API = mode.value === 'edit' ? updatePlanAPI : addPlanAPI
// const payload = { ...formData.value }
// try {
// const res = await API(payload)
// if (res.code === 200) {
// proxy.$modal.msgSuccess(mode.value === 'edit' ? '' : '')
// onBack()
// }
// } catch (error) {
// console.error(':', error)
// }
})
}
const onAddWorkLoad = () => {
formData.value.workLoadList.push({
workType: '',
count: '',
})
}
const onDeleteWorkLoad = (index) => {
formData.value.workLoadList.splice(index, 1)
}
//
const personTableRef = ref(null)
const managerDialogConfig = reactive({
outerVisible: false,
outerTitle: '选择人员',
outerWidth: '70%',
minHeight: '60vh',
maxHeight: '80vh',
})
const managerDialog = reactive({
visible: false,
keyword: '',
selected: [],
data: [
{
id: 1,
org: '昆明运检站',
name: '张三',
gender: '男',
station: '站长',
position: '全职',
type: '运检人员',
},
{
id: 2,
org: '昆明运检站',
name: '李四钱七',
gender: '男',
station: '副站长',
position: '全职',
type: '运检人员',
},
{
id: 3,
org: '昆明运检站',
name: '王五',
gender: '男',
station: '安全员',
position: '全职',
type: '运检人员',
},
{
id: 4,
org: '大理运检站',
name: '赵六钱七',
gender: '女',
station: '文员',
position: '派遣',
type: '后勤人员',
},
{
id: 5,
org: '大理运检站',
name: '钱七',
gender: '男',
station: '驾驶员',
position: '派遣',
type: '运检人员',
},
{
id: 6,
org: '大理运检站',
name: '钱七钱七',
gender: '男',
station: '驾驶员',
position: '派遣',
type: '运检人员',
},
{
id: 7,
org: '大理运检站',
name: '钱七',
gender: '男',
station: '驾驶员',
position: '派遣',
type: '运检人员',
},
],
})
const filteredPersons = computed(() => {
if (!managerDialog.keyword) return managerDialog.data
const keyword = managerDialog.keyword.toLowerCase()
return managerDialog.data.filter(
(item) =>
item.name.toLowerCase().includes(keyword) ||
item.org.toLowerCase().includes(keyword) ||
item.station.toLowerCase().includes(keyword),
)
})
const selectedManagerNames = computed(() =>
formData.value.planManagers.map((item) => item.name).join('、'),
)
const onOpenPersonPicker = async () => {
managerDialog.visible = true
managerDialogConfig.outerVisible = true
await nextTick()
if (personTableRef.value) {
personTableRef.value.clearSelection()
managerDialog.data.forEach((row) => {
const exists = formData.value.planManagers.find((item) => item.id === row.id)
if (exists) {
personTableRef.value.toggleRowSelection(row, true)
}
})
}
managerDialog.selected = [...formData.value.planManagers]
}
const onManagerSelectionChange = (rows) => {
managerDialog.selected = [...rows]
}
const onRemoveManager = (item) => {
managerDialog.selected = managerDialog.selected.filter((row) => row.id !== item.id)
if (personTableRef.value) {
const target = managerDialog.data.find((row) => row.id === item.id)
if (target) personTableRef.value.toggleRowSelection(target, false)
}
}
const onClearManagers = () => {
managerDialog.selected = []
if (personTableRef.value) personTableRef.value.clearSelection()
}
const onConfirmManager = () => {
formData.value.planManagers = [...managerDialog.selected]
managerDialog.visible = false
managerDialogConfig.outerVisible = false
}
//
const onCloseDialogOuter = (visible) => {
managerDialogConfig.outerVisible = visible
}
//
const dayAssignments = ref({}) // { '2025-01-01': [person, ...] }
const maxShowInCell = 5
const calendarDays = computed(() => {
const monthStr = formData.value.month
if (!monthStr) return []
const [yearStr, monthStrNum] = monthStr.split('-')
const year = Number(yearStr)
const month = Number(monthStrNum)
if (!year || !month) return []
const daysInMonth = new Date(year, month, 0).getDate()
const start = formData.value.planStartDate
const end = formData.value.planEndDate
const pad = (n) => (n < 10 ? `0${n}` : `${n}`)
const list = []
for (let d = 1; d <= daysInMonth; d++) {
const date = `${year}-${pad(month)}-${pad(d)}`
const label = `${month}${pad(d)}`
let isInRange = false
if (start && end) {
isInRange = date >= start && date <= end
}
list.push({
date,
label,
isInRange,
managers: dayAssignments.value[date] || [],
})
}
return list
})
//
const onFillManagersForDay = (date) => {
if (!formData.value.planManagers || !formData.value.planManagers.length) return
dayAssignments.value[date] = formData.value.planManagers.map((p) => ({ ...p }))
}
//
const onClearManagersForDay = (date) => {
if (dayAssignments.value[date]) {
dayAssignments.value[date] = []
}
}
//
const onRemovePersonFromDay = (date, person) => {
const list = dayAssignments.value[date] || []
dayAssignments.value[date] = list.filter((item) => item.id !== person.id)
}
</script>
<style scoped lang="scss">
@ -287,17 +816,6 @@ const onSubmit = () => {
font-size: 14px;
}
.calendar-placeholder {
min-height: 240px;
display: flex;
align-items: center;
justify-content: center;
color: #9ca3af;
border: 1px dashed #e5e7eb;
border-radius: 8px;
background-color: #f9fafb;
}
.page-footer {
margin-top: 12px;
display: flex;
@ -305,4 +823,138 @@ const onSubmit = () => {
gap: 12px;
}
}
.person-search-bar {
margin-bottom: 8px;
}
.selected-panel {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 12px;
height: 100%;
display: flex;
flex-direction: column;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.selected-list {
flex: 1;
overflow: auto;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.selected-tag {
margin-left: 6px;
margin-bottom: 10px;
}
.empty-tip {
color: #9ca3af;
}
.clickable-suffix {
cursor: pointer;
}
.schedule-wrapper {
.schedule-tip {
display: flex;
flex-direction: column;
font-size: 12px;
color: #6b7280;
margin-bottom: 8px;
.schedule-tip-extra {
margin-top: 2px;
}
}
.schedule-grid {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr)); // 7
gap: 8px;
}
.schedule-cell {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 6px 8px;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #ffffff;
&.is-disabled {
background-color: #f9fafb;
color: #9ca3af;
}
}
.cell-date {
font-size: 12px;
font-weight: 500;
margin-bottom: 4px;
}
.cell-persons {
flex: 1;
// min-height: 52px;
// max-height: 52px;
overflow: hidden;
font-size: 12px;
}
.person-tags {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 4px;
}
.more-text {
font-size: 12px;
color: #2563eb;
margin-left: 2px;
}
.empty-tip-small {
font-size: 12px;
color: #9ca3af;
&.is-disabled-text {
color: #d1d5db;
}
}
.cell-actions {
margin-top: 4px;
display: flex;
justify-content: space-between;
.el-button + .el-button {
margin-left: 4px;
}
}
.popover-list {
.popover-item {
display: flex;
font-size: 12px;
// padding: 2px 6px;
background-color: #f3f4f6;
border-radius: 2px;
gap: 4px;
}
}
}
</style>

View File

@ -10,8 +10,32 @@
:show-action="true"
:action-columns="actionColumns"
>
<template #toolbar>
<template #toolbar="{ formData }">
<ComButton type="primary" icon="Plus" @click="onHandleAdd">新增月计划</ComButton>
<ComButton
type="info"
plain
icon="UploadFilled"
@click="onExportPersonArrange(formData)"
>
导出人员安排表
</ComButton>
<ComButton
type="info"
plain
icon="UploadFilled"
@click="onExportOverallSummary(formData)"
>
导出整体汇总表
</ComButton>
<ComButton
type="info"
plain
icon="UploadFilled"
@click="onExportWorkloadSummary(formData)"
>
导出工作量汇总表
</ComButton>
</template>
</ComTable>
@ -68,7 +92,7 @@ const actionColumns = [
handler: (row) => {
router.push({
path: '/plan/monthlyPlanEdit/index',
query: { id: row.planId, mode: 'detail' },
query: { id: row.planId, mode: 'edit' },
})
},
},
@ -107,6 +131,28 @@ const onHandleSave = () => {
const onCloseDialogOuter = (visible) => {
dialogConfig.outerVisible = visible
}
//
const onExportPersonArrange = (queryParams) => {
console.log('queryParams', queryParams)
// proxy.download(
// 'xxx/xxx',
// {
// ...queryParams.value,
// },
// `job_${new Date().getTime()}.xlsx`,
// )
}
//
const onExportOverallSummary = (queryParams) => {
console.log('queryParams', queryParams)
}
//
const onExportWorkloadSummary = (queryParams) => {
console.log('queryParams', queryParams)
}
</script>
<style scoped></style>

View File

@ -2,7 +2,8 @@ import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
const baseUrl = 'http://192.168.0.133:58080' // 后端接口地址 超子
// const baseUrl = 'http://192.168.0.133:58080' // 后端接口地址 超子
const baseUrl = 'http://localhost:58080' // 后端接口地址 超子
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {