This commit is contained in:
Teo
2025-05-27 09:16:44 +08:00
parent cbb62f2bf0
commit 933fe4d74a
9 changed files with 1723 additions and 58 deletions

View File

@ -0,0 +1,266 @@
<template>
<div class="daily_paper">
<el-dialog v-model="isShowDialog" @close="onCancel" width="1000px" :close-on-click-modal="false" :destroy-on-close="true">
<template #header>
<div v-drag="['.daily_paper .el-dialog', '.daily_paper .el-dialog__header']" style="font-size: 18px">{{ infoDetail.name }} 日报填写</div>
</template>
<div class="box">
<div class="box-left">
<el-table
v-loading="loading"
:data="tableData"
height="64vh"
:cell-style="{ textAlign: 'center' }"
:header-cell-style="{ textAlign: 'center' }"
border
:row-key="tableKey"
:expand-row-keys="expandRowKeys"
@expand-change="clickOpen"
>
<el-table-column type="expand">
<template #default="props">
<div style="margin-left: 45px" m="4">
<el-table
:border="true"
:data="props.row.detail"
:header-cell-style="{ 'text-align': 'center' }"
:cell-style="{ 'text-align': 'center' }"
highlight-current-row
>
<el-table-column label="序号" type="index" width="50px" />
<el-table-column label="计划日期" prop="date"> </el-table-column>
<el-table-column label="数量" prop="planNum" width="60" />
<el-table-column label="完成量" prop="finishedNum" width="60" />
<el-table-column label="操作" class-name="small-padding" width="80px" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleDayAdd(scope.row, props.row)"
><el-icon><ele-Plus /></el-icon>日报</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="序号" type="index" :index="indexMethod" width="50px" />
<el-table-column label="计划数量" prop="planNum" min-width="100px" />
<el-table-column label="开始时间" min-width="100px">
<template #default="scope">
<span>{{ scope.row.startAt.split(' ')[0] }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getWorkList"
/>
</div>
<div class="box_right">
<div class="box_form" v-if="formDetail.submitTime">
<span>{{ dayDetail.date }}</span>
<el-form size="large" ref="formRef" :model="formDetail" :rules="rules" label-width="110px">
<el-form-item label="实际完成数量" prop="finishedNum">
<el-row>
<el-col :span="18">
<el-input type="number" :min="0" v-model="formDetail.finishedNum" placeholder="请输入实际完成数量"> </el-input
></el-col>
</el-row> </el-form-item
></el-form>
<div class="footer_box">
<el-button @click="onAddSubmit" type="primary" size="large"> </el-button>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="onCancel" size="large"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { workScheduleList, workScheduleDel, workScheduleSubmit } from '@/api/progress/plan';
const { proxy } = getCurrentInstance() as any;
const menuRef = ref();
const formRef = ref();
const expandRowKeys = ref([]);
const loading = ref(false);
const isShowDialog = ref(false);
const tableData = ref([]);
const total = ref(0);
const oldLength = ref(0);
const queryParams = reactive({
pageSize: 10,
pageNum: 1,
workId: ''
});
const infoDetail = reactive({
name: ''
});
const formDetail = reactive({
workId: '',
id: '',
submitTime: '',
finishedNum: 0
});
const dayDetail = reactive({
date: '',
finishedNum: 0
});
const rules = {
planNum: [{ required: true, message: '实际完成数量不能为空', trigger: 'blur' }]
};
const openDialog = (row: any) => {
queryParams.workId = row.work_id;
formDetail.workId = row.work_id;
infoDetail.name = row.name;
isShowDialog.value = true;
getWorkList(true);
};
const getWorkList = (bool = false) => {
loading.value = true;
workScheduleList(queryParams).then((res: any) => {
if (res.code === 0) {
tableData.value = res.data.list.map((item: any, index: number) => ({ ...item, index: index + 1 }));
if (bool) oldLength.value = res.data.list.length;
total.value = res.data.total;
}
loading.value = false;
});
};
const closeDialog = () => {
emit('getProgressList');
isShowDialog.value = false;
};
const onCancel = () => {
closeDialog();
};
const handleDelete = (row: any) => {
ElMessageBox.confirm('你确定要删除所选计划?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
workScheduleDel({ ids: [row.id] }).then((res: any) => {
if (res.code === 0) {
ElMessage.success('删除成功');
getWorkList();
} else {
ElMessage.error(res.message);
}
});
})
.catch(() => {});
};
const indexMethod = (index: number) => {
const currentPage = queryParams.pageNum - 1;
return index + 1 + (currentPage > 0 ? currentPage * queryParams.pageSize : 0);
};
const handleDayAdd = (row: any, obj: any) => {
formDetail.id = obj.id;
formDetail.submitTime = row.date;
formDetail.finishedNum = row.finishedNum;
Object.assign(dayDetail, row);
};
const onAddSubmit = () => {
dayDetail.finishedNum = Number(dayDetail.finishedNum);
workScheduleSubmit(formDetail).then((res: any) => {
if (res.code === 0) {
ElMessage.success('添加成功');
dayDetail.finishedNum = formDetail.finishedNum;
formDetail.submitTime = '';
formDetail.finishedNum = 0;
} else {
ElMessage.error(res.message);
}
});
};
const tableKey = (row: any) => row.id;
const clickOpen = (row: any) => {
const idx = expandRowKeys.value.indexOf(row.id);
if (idx !== -1) expandRowKeys.value.splice(idx, 1);
else expandRowKeys.value.push(row.id);
expandRowKeys.value = [...new Set(expandRowKeys.value)];
};
const emit = defineEmits(['getProgressList']);
</script>
<style lang="scss" scoped>
.daily_paper {
width: 100%;
.box {
display: flex;
width: 100%;
justify-content: space-between;
padding: 0 10px;
.box-left {
width: 46%;
float: left;
}
.box_right {
width: 48%;
float: left;
border: 1px solid #ebeef5;
height: 64vh;
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
.box_form {
width: 80%;
background-color: aliceblue;
padding: 10px;
> span {
display: block;
width: 100%;
font-size: 20px;
display: grid;
place-items: center;
margin-bottom: 20px;
}
.footer_box {
margin: 20px auto 0;
width: 100%;
display: grid;
place-items: center;
}
}
}
}
.pagination-container {
padding: 10px 10px !important;
}
.el-drawer__title {
font-size: 18px;
}
}
</style>

View File

@ -0,0 +1,292 @@
<template>
<div class="new-bar-chart-add">
<el-dialog v-model="isShowDialog" width="800px" :close-on-click-modal="false" :destroy-on-close="true">
<template #header>
<div v-drag="['.new-bar-chart-add .el-dialog', '.new-bar-chart-add .el-dialog__header']" style="font-size: 18px">
{{ infoDetail.name }} 计划创建
</div>
</template>
<el-row>
<el-col :span="14">
<el-form size="large" ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="开始时间" prop="start_at">
<el-date-picker
v-model="formData.start_at"
placeholder="选择开始时间"
type="date"
clearable
value-format="YYYY-MM-DD"
size="large"
:disabled-date="pickerOptionsStart"
:disabled="remainingNumAt.remainingNum === 0"
/>
</el-form-item>
<el-form-item label="结束时间" prop="end_at">
<el-date-picker
v-model="formData.end_at"
placeholder="选择结束时间"
type="date"
clearable
value-format="YYYY-MM-DD"
size="large"
:disabled-date="pickerOptionsEnd"
:disabled="remainingNumAt.remainingNum === 0"
/>
</el-form-item>
<el-form-item label="计划数量" prop="planNum">
<el-row>
<el-col :span="18">
<el-input
v-model="formData.planNum"
type="number"
:min="0"
:max="remainingNumAt.remainingNum"
:disabled="remainingNumAt.remainingNum === 0"
placeholder="请输入计划数量"
>
<template #append>
<span style="font-size: 12px">最大值{{ remainingNumAt.remainingNum }}</span>
</template>
</el-input>
</el-col>
<el-button type="primary" @click="onAverage" :disabled="!formData.planNum || tableData.length === 0" style="margin-left: 10px"
>均值</el-button
>
</el-row>
</el-form-item>
</el-form>
</el-col>
<el-col :span="10">
<el-table
style="margin-left: 10px; margin-bottom: 10px"
height="250"
:data="paginatedData"
border
empty-text="暂无计划"
:cell-style="{ textAlign: 'center' }"
:header-cell-style="{ textAlign: 'center' }"
>
<el-table-column type="index" :index="indexMethod" label="序号" width="60" />
<el-table-column prop="date" label="日期" width="130" />
<el-table-column prop="planNum" label="数量">
<template #default="scope">
<el-input-number v-model="scope.row.planNum" :value-on-clear="0" :min="0" :max="scope.row.max" @change="onChangeSum" size="small" />
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin-left: 10px"
background
layout="total, prev, pager, next"
:total="tableData.length"
:page-size="pageSize"
:current-page="currentPage"
@current-change="(val) => (currentPage = val)"
/>
</el-col>
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" size="large" @click="onSubmit">确 定</el-button>
<el-button size="large" @click="closeDialog">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, computed } from 'vue';
import { ElMessage, FormInstance } from 'element-plus';
import { workScheduleAddPlan, lastTime } from '@/api/progress/plan';
// Form refs
const formRef = ref<FormInstance | null>(null);
// State
const isShowDialog = ref(false);
const currentPage = ref(1);
const pageSize = 10;
const formData = reactive({
start_at: '',
end_at: '',
planNum: 0
});
const rules = {
start_at: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
end_at: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
planNum: [{ required: true, message: '计划数量不能为空', trigger: 'blur' }]
};
const infoDetail = reactive({
name: '',
work_id: '',
is_delay: 0
});
const remainingNumAt = reactive({
endAt: '',
remainingNum: 100
});
const tableData = ref<{ date: string; planNum: number; max: number }[]>([]);
// Computed paginated data
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize;
return tableData.value.slice(start, start + pageSize);
});
// Watch date changes
watch(
() => [formData.start_at, formData.end_at],
([start, end]) => {
if (start && end) {
const dates = generateDateRange(start, end);
tableData.value = dates.map((date) => ({
date,
planNum: 0,
max: remainingNumAt.remainingNum
}));
} else {
tableData.value = [];
}
}
);
// Date helpers
const generateDateRange = (start: string, end: string): string[] => {
const result: string[] = [];
const startDate = new Date(start);
const endDate = new Date(end);
while (startDate <= endDate) {
result.push(startDate.toISOString().split('T')[0]);
startDate.setDate(startDate.getDate() + 1);
}
return result;
};
// Picker rules
const pickerOptionsStart = (date: Date) => {
if (formData.end_at) return date.getTime() > new Date(formData.end_at).getTime();
if (remainingNumAt.endAt) return date.getTime() < new Date(remainingNumAt.endAt).getTime();
return false;
};
const pickerOptionsEnd = (date: Date) => {
if (formData.start_at) return date.getTime() < new Date(formData.start_at).getTime();
if (remainingNumAt.endAt) return date.getTime() < new Date(remainingNumAt.endAt).getTime();
return false;
};
// Average calculation
const onAverage = () => {
const total = formData.planNum;
const len = tableData.value.length;
const avg = Math.floor(total / len);
let remainder = total % len;
tableData.value.forEach((row) => {
row.planNum = avg + (remainder > 0 ? 1 : 0);
remainder--;
});
};
// Total sum validation
const onChangeSum = () => {
let total = tableData.value.reduce((sum, item) => sum + item.planNum, 0);
if (total > remainingNumAt.remainingNum) {
tableData.value.forEach((item) => {
item.max = item.planNum;
});
}
formData.planNum = total;
};
// Index method
const indexMethod = (index: number) => {
return index + 1 + (currentPage.value - 1) * pageSize;
};
// Submit handler
const onSubmit = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (!valid) return;
const payload = {
workId: infoDetail.work_id,
planNum: formData.planNum,
scheduledTime: tableData.value,
is_delay: infoDetail.is_delay === 1 ? 1 : undefined
};
workScheduleAddPlan(payload).then((res: any) => {
if (res.code === 0) {
ElMessage.success('添加成功');
closeDialog();
} else {
ElMessage.error(res.message);
}
});
});
};
// Dialog controls
const openDialog = (row: typeof infoDetail) => {
Object.assign(infoDetail, row);
isShowDialog.value = true;
resetForm();
fetchLastTime(row);
};
const closeDialog = () => {
isShowDialog.value = false;
};
const resetForm = () => {
formData.start_at = '';
formData.end_at = '';
formData.planNum = 0;
tableData.value = [];
};
const fetchLastTime = (row: typeof infoDetail) => {
lastTime({ workId: row.work_id, is_delay: row.is_delay }).then((res: any) => {
if (res.code === 0) {
Object.assign(remainingNumAt, res.data);
}
});
};
// Export function if needed externally
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.new-bar-chart-add {
.el-form-item--large .el-form-item__content {
line-height: 32px !important;
}
.el-input-number--small {
width: 90px;
}
.el-input-group__append {
padding: 0 10px;
}
.el-form {
background: aliceblue;
padding: 60px 10px;
}
.el-row {
align-items: center;
width: 100%;
}
}
</style>