|
|
@@ -3,6 +3,7 @@ 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'
|
|
|
+import { useWindowSize } from '@vueuse/core'
|
|
|
|
|
|
interface Props {
|
|
|
visible: boolean
|
|
|
@@ -37,6 +38,7 @@ const message = useMessage()
|
|
|
const operationMeeting = ref<OperationMeetingForm>({})
|
|
|
const operationMeetingRef = ref<FormInstance>()
|
|
|
const loading = ref(false)
|
|
|
+const { width } = useWindowSize()
|
|
|
|
|
|
const detailItems = ref<DetailItem[]>([])
|
|
|
const { ZmTable, ZmTableColumn } = useTableComponents<DetailItem>()
|
|
|
@@ -49,13 +51,19 @@ const drawerTitle = computed(() =>
|
|
|
props.type === 'create' ? '创建会议' : props.type === 'edit' ? '编辑会议' : '查看会议'
|
|
|
)
|
|
|
|
|
|
+const isMobile = computed(() => width.value <= 768)
|
|
|
+const drawerSize = computed(() => (isMobile.value ? '100%' : '92.2%'))
|
|
|
+const detailDrawerSize = computed(() => (isMobile.value ? '100%' : '50%'))
|
|
|
+
|
|
|
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 (
|
|
|
+ props.deptOptions.find((item) => item.value === operationMeeting.value.deptId)?.label || ''
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
return ''
|
|
|
@@ -143,6 +151,60 @@ const detailSummaryFields = [
|
|
|
'cumulativeOnAccount',
|
|
|
'currentPayment',
|
|
|
'cumulativePayment'
|
|
|
+] as const
|
|
|
+
|
|
|
+type DetailSummaryField = (typeof detailSummaryFields)[number]
|
|
|
+
|
|
|
+interface DetailCardField {
|
|
|
+ label: string
|
|
|
+ prop: keyof DetailItem
|
|
|
+ unit?: string
|
|
|
+ numeric?: boolean
|
|
|
+}
|
|
|
+
|
|
|
+const detailSummaryLabelMap: Record<DetailSummaryField, string> = {
|
|
|
+ currentRevenue: '收入-本期',
|
|
|
+ cumulativeRevenue: '收入-累计',
|
|
|
+ currentOnAccount: '挂帐-本期',
|
|
|
+ cumulativeOnAccount: '挂帐-累计',
|
|
|
+ currentPayment: '回款-本期',
|
|
|
+ cumulativePayment: '回款-累计'
|
|
|
+}
|
|
|
+
|
|
|
+const detailCardGroups: { title: string; fields: DetailCardField[] }[] = [
|
|
|
+ {
|
|
|
+ title: '经营情况',
|
|
|
+ fields: [
|
|
|
+ { label: '收入-本期', prop: 'currentRevenue', unit: '万元', numeric: true },
|
|
|
+ { label: '收入-累计', prop: 'cumulativeRevenue', unit: '万元', numeric: true },
|
|
|
+ { label: '挂帐-本期', prop: 'currentOnAccount', unit: '万元', numeric: true },
|
|
|
+ { label: '挂帐-累计', prop: 'cumulativeOnAccount', unit: '万元', numeric: true },
|
|
|
+ { label: '回款-本期', prop: 'currentPayment', unit: '万元', numeric: true },
|
|
|
+ { label: '回款-累计', prop: 'cumulativePayment', unit: '万元', numeric: true }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '本期生产运行情况',
|
|
|
+ fields: [
|
|
|
+ { label: '计划工作量', prop: 'plannedWorkload' },
|
|
|
+ { label: '实际完成', prop: 'actualCompletion' },
|
|
|
+ { label: '设备利用率', prop: 'equipmentUtilizationRate', unit: '%', numeric: true }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '生产管理情况及重点工作',
|
|
|
+ fields: [
|
|
|
+ { label: '重点工作及完成情况', prop: 'keyWorkCompletion' },
|
|
|
+ { label: '存在问题及分析', prop: 'problemsAnalysis' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '下期工作计划',
|
|
|
+ fields: [
|
|
|
+ { label: '计划工作量', prop: 'nextPlannedWorkload' },
|
|
|
+ { label: '重点工作事项', prop: 'priorityTasks' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
]
|
|
|
|
|
|
const requiredTextRule = (message: string) => [
|
|
|
@@ -205,6 +267,36 @@ const formatSummaryNumber = (value: number) =>
|
|
|
minimumFractionDigits: Number.isInteger(value) ? 0 : 2
|
|
|
})
|
|
|
|
|
|
+const getDetailSummaryTotal = (field: DetailSummaryField) =>
|
|
|
+ detailItems.value.reduce((sum, item) => sum + Number(item[field] || 0), 0)
|
|
|
+
|
|
|
+const detailSummaryCards = computed(() =>
|
|
|
+ detailSummaryFields.map((field) => ({
|
|
|
+ label: detailSummaryLabelMap[field],
|
|
|
+ value: formatSummaryNumber(getDetailSummaryTotal(field))
|
|
|
+ }))
|
|
|
+)
|
|
|
+
|
|
|
+const formatDetailCardValue = (item: DetailItem, field: DetailCardField) => {
|
|
|
+ const value = item[field.prop]
|
|
|
+
|
|
|
+ if (value === undefined || value === null || value === '') {
|
|
|
+ return '-'
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!field.numeric) {
|
|
|
+ return String(value)
|
|
|
+ }
|
|
|
+
|
|
|
+ const numericValue = Number(value)
|
|
|
+
|
|
|
+ if (Number.isNaN(numericValue)) {
|
|
|
+ return '-'
|
|
|
+ }
|
|
|
+
|
|
|
+ return `${formatSummaryNumber(numericValue)}${field.unit || ''}`
|
|
|
+}
|
|
|
+
|
|
|
const getDetailSummaries = ({ columns, data }: DetailSummaryMethodProps) => {
|
|
|
const sums: string[] = []
|
|
|
|
|
|
@@ -214,7 +306,7 @@ const getDetailSummaries = ({ columns, data }: DetailSummaryMethodProps) => {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if (!column.property || !detailSummaryFields.includes(column.property)) {
|
|
|
+ if (!column.property || !detailSummaryFields.includes(column.property as DetailSummaryField)) {
|
|
|
sums[index] = ''
|
|
|
return
|
|
|
}
|
|
|
@@ -399,7 +491,7 @@ const saveDetailItem = async () => {
|
|
|
header-class="mb-0! p-4!"
|
|
|
body-class="bg-gray-100"
|
|
|
footer-class="p-4!"
|
|
|
- size="92.2%"
|
|
|
+ :size="drawerSize"
|
|
|
>
|
|
|
<template #header>
|
|
|
<div class="flex items-center">
|
|
|
@@ -422,10 +514,7 @@ const saveDetailItem = async () => {
|
|
|
<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-form-item label="所属公司" class="meeting-form-item mb-0! min-w-0">
|
|
|
<el-input
|
|
|
:model-value="companyDisplayName"
|
|
|
class="w-full!"
|
|
|
@@ -435,11 +524,7 @@ const saveDetailItem = async () => {
|
|
|
<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-form-item label="会议日期" prop="meetingDate" class="meeting-form-item mb-0! min-w-0">
|
|
|
<el-date-picker
|
|
|
v-model="operationMeeting.meetingDate"
|
|
|
type="date"
|
|
|
@@ -486,60 +571,129 @@ const saveDetailItem = async () => {
|
|
|
新增一行
|
|
|
</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)"
|
|
|
+ <div class="meeting-detail-table-view">
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-loading="loading" class="meeting-detail-card-view">
|
|
|
+ <template v-if="detailItems.length">
|
|
|
+ <article v-for="(item, index) in detailItems" :key="index" class="meeting-detail-card">
|
|
|
+ <div class="meeting-detail-card__header">
|
|
|
+ <div class="meeting-detail-card__title">
|
|
|
+ <span>项目部</span>
|
|
|
+ <strong>{{ item.projectName || '-' }}</strong>
|
|
|
+ </div>
|
|
|
+ <el-tag size="small" effect="plain">第 {{ index + 1 }} 项</el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section
|
|
|
+ v-for="group in detailCardGroups"
|
|
|
+ :key="group.title"
|
|
|
+ class="meeting-detail-card__group"
|
|
|
>
|
|
|
- 删除
|
|
|
- </el-button>
|
|
|
- </template>
|
|
|
- </zm-table-column>
|
|
|
- </ZmTable>
|
|
|
+ <h4>{{ group.title }}</h4>
|
|
|
+ <div class="meeting-detail-card__fields">
|
|
|
+ <div
|
|
|
+ v-for="field in group.fields"
|
|
|
+ :key="field.prop"
|
|
|
+ class="meeting-detail-card__field"
|
|
|
+ >
|
|
|
+ <span>{{ field.label }}</span>
|
|
|
+ <strong>{{ formatDetailCardValue(item, field) }}</strong>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <div class="meeting-detail-card__actions">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ @click="handleEditDetailItem(item, index)"
|
|
|
+ >
|
|
|
+ {{ type === 'view' ? '查看' : '编辑' }}
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="type !== 'view'"
|
|
|
+ link
|
|
|
+ size="small"
|
|
|
+ type="danger"
|
|
|
+ @click="handleDeleteDetailItem(index)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <section class="meeting-detail-summary-card">
|
|
|
+ <div class="meeting-detail-summary-card__title">公司整体</div>
|
|
|
+ <div class="meeting-detail-summary-card__grid">
|
|
|
+ <div
|
|
|
+ v-for="item in detailSummaryCards"
|
|
|
+ :key="item.label"
|
|
|
+ class="meeting-detail-summary-card__item"
|
|
|
+ >
|
|
|
+ <span>{{ item.label }}</span>
|
|
|
+ <strong>{{ item.value }}万元</strong>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </template>
|
|
|
+ <el-empty v-else description="暂无会议明细" :image-size="80" />
|
|
|
+ </div>
|
|
|
</section>
|
|
|
</el-form>
|
|
|
|
|
|
@@ -560,7 +714,7 @@ const saveDetailItem = async () => {
|
|
|
:model-value="detailDrawerVisible"
|
|
|
@update:model-value="handleDetailDrawerChange"
|
|
|
:append-to-body="true"
|
|
|
- size="50%"
|
|
|
+ :size="detailDrawerSize"
|
|
|
:close-on-click-modal="false"
|
|
|
:close-on-press-escape="false"
|
|
|
:show-close="false"
|
|
|
@@ -787,6 +941,116 @@ const saveDetailItem = async () => {
|
|
|
grid-template-columns: 1fr;
|
|
|
}
|
|
|
|
|
|
+.meeting-detail-table-view {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card-view {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 14px;
|
|
|
+ padding: 12px;
|
|
|
+ padding-bottom: 4px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 8px rgb(15 23 42 / 6%);
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__header,
|
|
|
+.meeting-detail-card__actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__title {
|
|
|
+ display: flex;
|
|
|
+ min-width: 0;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__title span,
|
|
|
+.meeting-detail-card__field span,
|
|
|
+.meeting-detail-summary-card__item span {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.4;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__title strong {
|
|
|
+ overflow: hidden;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1.35;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__group {
|
|
|
+ padding-top: 12px;
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__group h4 {
|
|
|
+ margin: 0 0 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__fields,
|
|
|
+.meeting-detail-summary-card__grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 10px 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__field,
|
|
|
+.meeting-detail-summary-card__item {
|
|
|
+ display: flex;
|
|
|
+ min-width: 0;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__field strong,
|
|
|
+.meeting-detail-summary-card__item strong {
|
|
|
+ min-width: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ overflow-wrap: anywhere;
|
|
|
+ white-space: pre-wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-card__actions {
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 2px;
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-summary-card {
|
|
|
+ padding: 14px;
|
|
|
+ background: var(--el-fill-color-lighter);
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.meeting-detail-summary-card__title {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+
|
|
|
@media (width <= 960px) {
|
|
|
.meeting-section__grid {
|
|
|
grid-template-columns: 1fr;
|
|
|
@@ -801,5 +1065,21 @@ const saveDetailItem = async () => {
|
|
|
.meeting-section {
|
|
|
padding: 16px;
|
|
|
}
|
|
|
+
|
|
|
+ .meeting-detail-table-view {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meeting-detail-card-view {
|
|
|
+ display: flex;
|
|
|
+ min-height: 160px;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meeting-detail-card__fields,
|
|
|
+ .meeting-detail-summary-card__grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|