|
|
@@ -0,0 +1,805 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import type { FormInstance, FormRules } from 'element-plus'
|
|
|
+import type { DeptOption, DetailItem, OperationMeeting } from './types'
|
|
|
+import { OperationMeetingApi } from '@/api/pms/meeting'
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
+
|
|
|
+interface Props {
|
|
|
+ visible: boolean
|
|
|
+ id?: number
|
|
|
+ type: 'create' | 'edit' | 'view'
|
|
|
+ deptOptions?: DeptOption[]
|
|
|
+}
|
|
|
+
|
|
|
+interface SummaryColumn {
|
|
|
+ property?: string
|
|
|
+}
|
|
|
+
|
|
|
+interface DetailSummaryMethodProps {
|
|
|
+ columns: SummaryColumn[]
|
|
|
+ data: DetailItem[]
|
|
|
+}
|
|
|
+
|
|
|
+interface OperationMeetingForm
|
|
|
+ extends Omit<Partial<OperationMeeting>, 'meetingDate' | 'meetingSeries'> {
|
|
|
+ meetingDate?: number | string | Date
|
|
|
+ meetingSeries?: number
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ type: 'create',
|
|
|
+ deptOptions: () => []
|
|
|
+})
|
|
|
+
|
|
|
+const emits = defineEmits(['update:visible', 'success'])
|
|
|
+const message = useMessage()
|
|
|
+
|
|
|
+const operationMeeting = ref<OperationMeetingForm>({})
|
|
|
+const operationMeetingRef = ref<FormInstance>()
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+const detailItems = ref<DetailItem[]>([])
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<DetailItem>()
|
|
|
+const detailDrawerVisible = ref(false)
|
|
|
+const detailFormRef = ref<FormInstance>()
|
|
|
+const detailEditingIndex = ref(-1)
|
|
|
+const detailFormType = ref<'create' | 'edit'>('create')
|
|
|
+
|
|
|
+const drawerTitle = computed(() =>
|
|
|
+ props.type === 'create' ? '创建会议' : props.type === 'edit' ? '编辑会议' : '查看会议'
|
|
|
+)
|
|
|
+
|
|
|
+const companyDisplayName = computed(() => {
|
|
|
+ if (operationMeeting.value.companyName) {
|
|
|
+ return operationMeeting.value.companyName
|
|
|
+ }
|
|
|
+
|
|
|
+ if (operationMeeting.value.deptId) {
|
|
|
+ return props.deptOptions.find((item) => item.value === operationMeeting.value.deptId)?.label || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ return ''
|
|
|
+})
|
|
|
+
|
|
|
+const detailDrawerTitle = computed(() => {
|
|
|
+ if (props.type === 'view') {
|
|
|
+ return '查看会议明细'
|
|
|
+ }
|
|
|
+
|
|
|
+ return detailFormType.value === 'create' ? '新增会议明细' : '编辑会议明细'
|
|
|
+})
|
|
|
+
|
|
|
+const createDetailItem = (): DetailItem => ({
|
|
|
+ projectName: '',
|
|
|
+ currentRevenue: undefined,
|
|
|
+ cumulativeRevenue: undefined,
|
|
|
+ currentOnAccount: undefined,
|
|
|
+ cumulativeOnAccount: undefined,
|
|
|
+ currentPayment: undefined,
|
|
|
+ cumulativePayment: undefined,
|
|
|
+ plannedWorkload: '',
|
|
|
+ actualCompletion: '',
|
|
|
+ equipmentUtilizationRate: undefined,
|
|
|
+ keyWorkCompletion: '',
|
|
|
+ problemsAnalysis: '',
|
|
|
+ nextPlannedWorkload: '',
|
|
|
+ priorityTasks: ''
|
|
|
+})
|
|
|
+
|
|
|
+const cloneDetailItem = (data?: Partial<DetailItem>): DetailItem => ({
|
|
|
+ projectName: data?.projectName || '',
|
|
|
+ currentRevenue: data?.currentRevenue,
|
|
|
+ cumulativeRevenue: data?.cumulativeRevenue,
|
|
|
+ currentOnAccount: data?.currentOnAccount,
|
|
|
+ cumulativeOnAccount: data?.cumulativeOnAccount,
|
|
|
+ currentPayment: data?.currentPayment,
|
|
|
+ cumulativePayment: data?.cumulativePayment,
|
|
|
+ plannedWorkload: data?.plannedWorkload || '',
|
|
|
+ actualCompletion: data?.actualCompletion || '',
|
|
|
+ equipmentUtilizationRate: data?.equipmentUtilizationRate,
|
|
|
+ keyWorkCompletion: data?.keyWorkCompletion || '',
|
|
|
+ problemsAnalysis: data?.problemsAnalysis || '',
|
|
|
+ nextPlannedWorkload: data?.nextPlannedWorkload || '',
|
|
|
+ priorityTasks: data?.priorityTasks || ''
|
|
|
+})
|
|
|
+
|
|
|
+const parseNumberValue = (value: unknown) => {
|
|
|
+ if (value === undefined || value === null || value === '') return undefined
|
|
|
+ const parsed = Number(String(value).replace('%', ''))
|
|
|
+
|
|
|
+ return Number.isNaN(parsed) ? undefined : parsed
|
|
|
+}
|
|
|
+
|
|
|
+const parseMeetingSeries = (value: unknown) => {
|
|
|
+ if (value === undefined || value === null || value === '') return undefined
|
|
|
+ const matched = String(value).match(/\d+/)
|
|
|
+
|
|
|
+ return matched ? Number(matched[0]) : undefined
|
|
|
+}
|
|
|
+
|
|
|
+const normalizeDetailItem = (data?: Record<string, unknown>): DetailItem => ({
|
|
|
+ projectName: String(data?.projectName || ''),
|
|
|
+ currentRevenue: parseNumberValue(data?.currentRevenue),
|
|
|
+ cumulativeRevenue: parseNumberValue(data?.cumulativeRevenue),
|
|
|
+ currentOnAccount: parseNumberValue(data?.currentOnAccount),
|
|
|
+ cumulativeOnAccount: parseNumberValue(data?.cumulativeOnAccount),
|
|
|
+ currentPayment: parseNumberValue(data?.currentPayment),
|
|
|
+ cumulativePayment: parseNumberValue(data?.cumulativePayment),
|
|
|
+ plannedWorkload: String(data?.plannedWorkload || ''),
|
|
|
+ actualCompletion: String(data?.actualCompletion || ''),
|
|
|
+ equipmentUtilizationRate: parseNumberValue(data?.equipmentUtilizationRate),
|
|
|
+ keyWorkCompletion: String(data?.keyWorkCompletion || ''),
|
|
|
+ problemsAnalysis: String(data?.problemsAnalysis || ''),
|
|
|
+ nextPlannedWorkload: String(data?.nextPlannedWorkload || ''),
|
|
|
+ priorityTasks: String(data?.priorityTasks || '')
|
|
|
+})
|
|
|
+
|
|
|
+const detailForm = ref<DetailItem>(createDetailItem())
|
|
|
+
|
|
|
+const detailSummaryFields = [
|
|
|
+ 'currentRevenue',
|
|
|
+ 'cumulativeRevenue',
|
|
|
+ 'currentOnAccount',
|
|
|
+ 'cumulativeOnAccount',
|
|
|
+ 'currentPayment',
|
|
|
+ 'cumulativePayment'
|
|
|
+]
|
|
|
+
|
|
|
+const requiredTextRule = (message: string) => [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ whitespace: true,
|
|
|
+ message,
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const requiredNumberRule = (message: string) => [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ type: 'number' as const,
|
|
|
+ message,
|
|
|
+ trigger: ['blur', 'change']
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const operationMeetingRules = reactive<FormRules>({
|
|
|
+ meetingDate: [{ required: true, message: '请选择会议日期', trigger: 'change' }],
|
|
|
+ meetingSeries: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ type: 'number',
|
|
|
+ message: '请输入会议期次',
|
|
|
+ trigger: ['blur', 'change']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'number',
|
|
|
+ min: 1,
|
|
|
+ message: '会议期次最小为1',
|
|
|
+ trigger: ['blur', 'change']
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ support: requiredTextRule('请输入需集团协调支持的事项')
|
|
|
+})
|
|
|
+
|
|
|
+const detailRules = reactive<FormRules>({
|
|
|
+ projectName: requiredTextRule('请输入项目部'),
|
|
|
+ currentRevenue: requiredNumberRule('请输入收入-本期'),
|
|
|
+ cumulativeRevenue: requiredNumberRule('请输入收入-累计'),
|
|
|
+ currentOnAccount: requiredNumberRule('请输入挂帐-本期'),
|
|
|
+ cumulativeOnAccount: requiredNumberRule('请输入挂帐-累计'),
|
|
|
+ currentPayment: requiredNumberRule('请输入回款-本期'),
|
|
|
+ cumulativePayment: requiredNumberRule('请输入回款-累计'),
|
|
|
+ plannedWorkload: requiredTextRule('请输入计划工作量'),
|
|
|
+ actualCompletion: requiredTextRule('请输入实际完成'),
|
|
|
+ equipmentUtilizationRate: requiredNumberRule('请输入设备利用率'),
|
|
|
+ keyWorkCompletion: requiredTextRule('请输入重点工作及完成情况'),
|
|
|
+ problemsAnalysis: requiredTextRule('请输入存在问题及分析'),
|
|
|
+ nextPlannedWorkload: requiredTextRule('请输入下期计划工作量'),
|
|
|
+ priorityTasks: requiredTextRule('请输入重点工作事项')
|
|
|
+})
|
|
|
+
|
|
|
+const formatSummaryNumber = (value: number) =>
|
|
|
+ value.toLocaleString('zh-CN', {
|
|
|
+ maximumFractionDigits: 2,
|
|
|
+ minimumFractionDigits: Number.isInteger(value) ? 0 : 2
|
|
|
+ })
|
|
|
+
|
|
|
+const getDetailSummaries = ({ columns, data }: DetailSummaryMethodProps) => {
|
|
|
+ const sums: string[] = []
|
|
|
+
|
|
|
+ columns.forEach((column, index) => {
|
|
|
+ if (index === 0) {
|
|
|
+ sums[index] = '公司整体'
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!column.property || !detailSummaryFields.includes(column.property)) {
|
|
|
+ sums[index] = ''
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const total = data.reduce(
|
|
|
+ (sum, item) => sum + Number(item[column.property as keyof DetailItem] || 0),
|
|
|
+ 0
|
|
|
+ )
|
|
|
+ sums[index] = formatSummaryNumber(total)
|
|
|
+ })
|
|
|
+
|
|
|
+ return sums
|
|
|
+}
|
|
|
+
|
|
|
+const handleAddDetailItem = () => {
|
|
|
+ detailFormType.value = 'create'
|
|
|
+ detailEditingIndex.value = -1
|
|
|
+ detailForm.value = createDetailItem()
|
|
|
+ detailDrawerVisible.value = true
|
|
|
+ nextTick(() => detailFormRef.value?.clearValidate())
|
|
|
+}
|
|
|
+
|
|
|
+const handleEditDetailItem = (row: DetailItem, index: number) => {
|
|
|
+ detailFormType.value = 'edit'
|
|
|
+ detailEditingIndex.value = index
|
|
|
+ detailForm.value = cloneDetailItem(row)
|
|
|
+ detailDrawerVisible.value = true
|
|
|
+ nextTick(() => detailFormRef.value?.clearValidate())
|
|
|
+}
|
|
|
+
|
|
|
+const handleDeleteDetailItem = (index: number) => {
|
|
|
+ detailItems.value.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const handleDetailDrawerChange = (visible: boolean) => {
|
|
|
+ detailDrawerVisible.value = visible
|
|
|
+
|
|
|
+ if (!visible) {
|
|
|
+ detailForm.value = createDetailItem()
|
|
|
+ detailFormType.value = 'create'
|
|
|
+ detailEditingIndex.value = -1
|
|
|
+ nextTick(() => detailFormRef.value?.clearValidate())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ operationMeeting.value = {}
|
|
|
+ detailItems.value = []
|
|
|
+ loading.value = false
|
|
|
+ handleDetailDrawerChange(false)
|
|
|
+ nextTick(() => operationMeetingRef.value?.clearValidate())
|
|
|
+}
|
|
|
+
|
|
|
+const loadOperationMeetingDetail = async (id: number) => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const data = await OperationMeetingApi.getOperationMeeting(id)
|
|
|
+
|
|
|
+ if (!props.visible) return
|
|
|
+
|
|
|
+ operationMeeting.value = {
|
|
|
+ id: data?.id,
|
|
|
+ deptId: data?.deptId,
|
|
|
+ companyName: data?.companyName || '',
|
|
|
+ meetingDate: data?.meetingDate,
|
|
|
+ support: data?.support || '',
|
|
|
+ meetingSeries: parseMeetingSeries(data?.meetingSeries)
|
|
|
+ }
|
|
|
+
|
|
|
+ const details = Array.isArray(data?.details) ? data.details : []
|
|
|
+ detailItems.value = details.map((item) => normalizeDetailItem(item as Record<string, unknown>))
|
|
|
+ nextTick(() => operationMeetingRef.value?.clearValidate())
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleVisibleChange = (visible: boolean) => {
|
|
|
+ emits('update:visible', visible)
|
|
|
+
|
|
|
+ if (!visible) {
|
|
|
+ resetForm()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [props.visible, props.id, props.type] as const,
|
|
|
+ ([visible, id, type]) => {
|
|
|
+ if (!visible) return
|
|
|
+
|
|
|
+ if (type === 'create') {
|
|
|
+ resetForm()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (id) {
|
|
|
+ loadOperationMeetingDetail(id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+const formatMeetingSeries = (value: number) => `第${value}期`
|
|
|
+
|
|
|
+const getMeetingDateTimestamp = (value: OperationMeetingForm['meetingDate']) => {
|
|
|
+ if (value instanceof Date) return value.getTime()
|
|
|
+ if (typeof value === 'number') return value
|
|
|
+ if (typeof value === 'string') {
|
|
|
+ const timestamp = new Date(value).getTime()
|
|
|
+
|
|
|
+ return Number.isNaN(timestamp) ? undefined : timestamp
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined
|
|
|
+}
|
|
|
+
|
|
|
+const buildOperationMeetingPayload = (): Partial<OperationMeeting> => {
|
|
|
+ const payload: Partial<OperationMeeting> = {
|
|
|
+ meetingDate: getMeetingDateTimestamp(operationMeeting.value.meetingDate),
|
|
|
+ meetingSeries: formatMeetingSeries(operationMeeting.value.meetingSeries as number),
|
|
|
+ support: operationMeeting.value.support || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ if (operationMeeting.value.id) {
|
|
|
+ payload.id = operationMeeting.value.id
|
|
|
+ }
|
|
|
+
|
|
|
+ return payload
|
|
|
+}
|
|
|
+
|
|
|
+const buildDetailPayload = (item: DetailItem): DetailItem => cloneDetailItem(item)
|
|
|
+
|
|
|
+const submitForm = async () => {
|
|
|
+ if (props.type === 'view' || !operationMeetingRef.value) return
|
|
|
+
|
|
|
+ const valid = await operationMeetingRef.value.validate().catch(() => false)
|
|
|
+
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ if (detailItems.value.length === 0) {
|
|
|
+ message.warning('请先新增至少一条会议明细')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ await OperationMeetingApi.saveBatch({
|
|
|
+ operationMeeting: buildOperationMeetingPayload(),
|
|
|
+ details: detailItems.value.map((item) => buildDetailPayload(item))
|
|
|
+ })
|
|
|
+
|
|
|
+ message.success('保存成功')
|
|
|
+ handleVisibleChange(false)
|
|
|
+ emits('success')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const saveDetailItem = async () => {
|
|
|
+ if (props.type === 'view' || !detailFormRef.value) return
|
|
|
+
|
|
|
+ const valid = await detailFormRef.value.validate().catch(() => false)
|
|
|
+
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ const nextItem = cloneDetailItem(detailForm.value)
|
|
|
+
|
|
|
+ if (detailEditingIndex.value > -1) {
|
|
|
+ detailItems.value.splice(detailEditingIndex.value, 1, nextItem)
|
|
|
+ } else {
|
|
|
+ detailItems.value.push(nextItem)
|
|
|
+ }
|
|
|
+
|
|
|
+ handleDetailDrawerChange(false)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <el-drawer
|
|
|
+ :model-value="visible"
|
|
|
+ @update:model-value="handleVisibleChange"
|
|
|
+ header-class="mb-0! p-4!"
|
|
|
+ body-class="bg-gray-100"
|
|
|
+ footer-class="p-4!"
|
|
|
+ size="92.2%"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <span class="font-bold text-xl">{{ drawerTitle }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-form
|
|
|
+ ref="operationMeetingRef"
|
|
|
+ label-position="top"
|
|
|
+ size="default"
|
|
|
+ :model="operationMeeting"
|
|
|
+ :rules="operationMeetingRules"
|
|
|
+ v-loading="loading"
|
|
|
+ scroll-to-error
|
|
|
+ require-asterisk-position="right"
|
|
|
+ :disabled="type === 'view'"
|
|
|
+ >
|
|
|
+ <section class="p-6 bg-white border-solid border-1 border-gray-200/90 rounded-xl mb-6">
|
|
|
+ <h3 class="text-lg font-bold mb-4">会议信息</h3>
|
|
|
+
|
|
|
+ <div class="meeting-section__grid">
|
|
|
+ <el-form-item
|
|
|
+ label="所属公司/项目部"
|
|
|
+ class="meeting-form-item mb-0! min-w-0"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ :model-value="companyDisplayName"
|
|
|
+ class="w-full!"
|
|
|
+ placeholder="新建保存后系统自动填充"
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ <div class="meeting-form-tip">新建保存后由系统自动填充。</div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="会议日期"
|
|
|
+ prop="meetingDate"
|
|
|
+ class="meeting-form-item mb-0! min-w-0"
|
|
|
+ >
|
|
|
+ <el-date-picker
|
|
|
+ v-model="operationMeeting.meetingDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择会议日期"
|
|
|
+ class="w-full!"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="会议期次"
|
|
|
+ prop="meetingSeries"
|
|
|
+ class="meeting-form-item mb-0! min-w-0"
|
|
|
+ >
|
|
|
+ <el-input-number
|
|
|
+ v-model="operationMeeting.meetingSeries"
|
|
|
+ class="w-full!"
|
|
|
+ placeholder="请输入会议期次"
|
|
|
+ :controls="false"
|
|
|
+ :min="1"
|
|
|
+ :step="1"
|
|
|
+ :precision="0"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="需集团协调支持的事项"
|
|
|
+ prop="support"
|
|
|
+ class="meeting-form-item meeting-section__grid-full mb-0! min-w-0"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="operationMeeting.support"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入需集团协调支持的事项"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <section class="p-6 bg-white border-solid border-1 border-gray-200/90 rounded-xl">
|
|
|
+ <div class="flex items-center justify-between gap-4 mb-4">
|
|
|
+ <h3 class="text-lg font-bold m-0">会议明细</h3>
|
|
|
+ <el-button v-if="type !== 'view'" type="primary" @click="handleAddDetailItem">
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" />
|
|
|
+ 新增一行
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <ZmTable
|
|
|
+ :data="detailItems"
|
|
|
+ :loading="loading"
|
|
|
+ show-summary
|
|
|
+ :summary-method="getDetailSummaries"
|
|
|
+ >
|
|
|
+ <zm-table-column label="项目部" prop="projectName" />
|
|
|
+ <zm-table-column label="收入(万元)">
|
|
|
+ <zm-table-column label="本期" prop="currentRevenue" />
|
|
|
+ <zm-table-column label="累计" prop="cumulativeRevenue" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="挂帐(万元)">
|
|
|
+ <zm-table-column label="本期" prop="currentOnAccount" />
|
|
|
+ <zm-table-column label="累计" prop="cumulativeOnAccount" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="回款(万元)">
|
|
|
+ <zm-table-column label="本期" prop="currentPayment" />
|
|
|
+ <zm-table-column label="累计" prop="cumulativePayment" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="本期生产运行情况">
|
|
|
+ <zm-table-column label="计划工作量" prop="plannedWorkload" />
|
|
|
+ <zm-table-column label="实际完成" prop="actualCompletion" />
|
|
|
+ <zm-table-column label="设备利用率" prop="equipmentUtilizationRate" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="生产管理情况及重点工作 ">
|
|
|
+ <zm-table-column label="重点工作及完成情况" prop="keyWorkCompletion" />
|
|
|
+ <zm-table-column label="存在问题及分析" prop="problemsAnalysis" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="下期工作计划 ">
|
|
|
+ <zm-table-column label="计划工作量" prop="nextPlannedWorkload" />
|
|
|
+ <zm-table-column label="重点工作事项" prop="priorityTasks" />
|
|
|
+ </zm-table-column>
|
|
|
+ <zm-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ @click="handleEditDetailItem(row, $index)"
|
|
|
+ >
|
|
|
+ {{ type === 'view' ? '查看' : '编辑' }}
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="type !== 'view'"
|
|
|
+ link
|
|
|
+ size="small"
|
|
|
+ type="danger"
|
|
|
+ @click="handleDeleteDetailItem($index)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </zm-table-column>
|
|
|
+ </ZmTable>
|
|
|
+ </section>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button size="default" @click="handleVisibleChange(false)">取消</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="type !== 'view'"
|
|
|
+ size="default"
|
|
|
+ type="primary"
|
|
|
+ :loading="loading"
|
|
|
+ @click="submitForm"
|
|
|
+ >
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-drawer
|
|
|
+ :model-value="detailDrawerVisible"
|
|
|
+ @update:model-value="handleDetailDrawerChange"
|
|
|
+ :append-to-body="true"
|
|
|
+ size="50%"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :show-close="false"
|
|
|
+ header-class="mb-0! p-4!"
|
|
|
+ body-class="bg-gray-100"
|
|
|
+ footer-class="p-4!"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <span class="font-bold text-xl">{{ detailDrawerTitle }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-form
|
|
|
+ ref="detailFormRef"
|
|
|
+ label-position="top"
|
|
|
+ size="default"
|
|
|
+ :model="detailForm"
|
|
|
+ :rules="detailRules"
|
|
|
+ :disabled="type === 'view'"
|
|
|
+ scroll-to-error
|
|
|
+ require-asterisk-position="right"
|
|
|
+ >
|
|
|
+ <section class="detail-section">
|
|
|
+ <h4 class="detail-section__title">项目基础</h4>
|
|
|
+ <div class="detail-section__grid detail-section__grid--single">
|
|
|
+ <el-form-item label="项目部" prop="projectName">
|
|
|
+ <el-input v-model="detailForm.projectName" placeholder="请输入项目部" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="detail-section">
|
|
|
+ <h4 class="detail-section__title">经营情况(万元)</h4>
|
|
|
+ <div class="detail-section__grid">
|
|
|
+ <el-form-item label="收入-本期" prop="currentRevenue">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.currentRevenue"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="收入-累计" prop="cumulativeRevenue">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.cumulativeRevenue"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="挂帐-本期" prop="currentOnAccount">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.currentOnAccount"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="挂帐-累计" prop="cumulativeOnAccount">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.cumulativeOnAccount"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="回款-本期" prop="currentPayment">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.currentPayment"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="回款-累计" prop="cumulativePayment">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.cumulativePayment"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="detail-section">
|
|
|
+ <h4 class="detail-section__title">本期生产运行情况</h4>
|
|
|
+ <div class="detail-section__grid">
|
|
|
+ <el-form-item label="计划工作量" prop="plannedWorkload">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.plannedWorkload"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入计划工作量"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="实际完成" prop="actualCompletion">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.actualCompletion"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入实际完成"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="设备利用率(%)" prop="equipmentUtilizationRate">
|
|
|
+ <el-input-number
|
|
|
+ v-model="detailForm.equipmentUtilizationRate"
|
|
|
+ class="w-full!"
|
|
|
+ :controls="false"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ :precision="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="detail-section">
|
|
|
+ <h4 class="detail-section__title">生产管理情况及重点工作</h4>
|
|
|
+ <div class="detail-section__grid">
|
|
|
+ <el-form-item label="重点工作及完成情况" prop="keyWorkCompletion">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.keyWorkCompletion"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入重点工作及完成情况"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="存在问题及分析" prop="problemsAnalysis">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.problemsAnalysis"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入存在问题及分析"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="detail-section">
|
|
|
+ <h4 class="detail-section__title">下期工作计划</h4>
|
|
|
+ <div class="detail-section__grid">
|
|
|
+ <el-form-item label="计划工作量" prop="nextPlannedWorkload">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.nextPlannedWorkload"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入下期计划工作量"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="重点工作事项" prop="priorityTasks">
|
|
|
+ <el-input
|
|
|
+ v-model="detailForm.priorityTasks"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入重点工作事项"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button size="default" @click="handleDetailDrawerChange(false)">取消</el-button>
|
|
|
+ <el-button size="default" v-if="type !== 'view'" type="primary" @click="saveDetailItem">
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+ </el-drawer>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.meeting-section__grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ gap: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-section__grid-full {
|
|
|
+ grid-column: 1 / -1;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-form-tip {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 18px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section {
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid rgb(229 231 235 / 90%);
|
|
|
+ border-radius: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section__title {
|
|
|
+ margin: 0 0 16px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section__grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 18px 24px;
|
|
|
+
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section__grid--single {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+}
|
|
|
+
|
|
|
+@media (width <= 960px) {
|
|
|
+ .meeting-section__grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-section__grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (width <= 768px) {
|
|
|
+ .meeting-section {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|