Explorar o código

调整运营会议

Co-authored-by: Copilot <copilot@github.com>
Zimo hai 2 días
pai
achega
6eb7aef0b2

+ 5 - 4
src/components/ZmTable/ZmTableColumn.vue

@@ -3,7 +3,7 @@ import { type TableColumnCtx } from 'element-plus'
 import { computed, inject, nextTick, ref, useAttrs, watch } from 'vue'
 import { TableContextKey } from './token'
 import ZmTableColumnSettingTree from './ZmTableColumnSettingTree.vue'
-import type { ColumnSettingItem, SortChangePayload, SortOrder } from './token'
+import type { ColumnAlign, ColumnSettingItem, SortChangePayload, SortOrder } from './token'
 import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
 
 interface Props
@@ -39,6 +39,7 @@ const attrs = useAttrs()
 const tableContext = inject(TableContextKey, {
   data: ref([]),
   loading: ref(false),
+  columnAlign: ref<ColumnAlign>('center'),
   columnMaxWidth: ref(360),
   columnSettings: ref([]),
   updateColumnVisible: () => {},
@@ -53,8 +54,7 @@ const settingVisible = ref(false)
 
 const defaultOptions = ref<Partial<Props>>({
   align: 'center',
-  resizable: true,
-  showOverflowTooltip: true
+  resizable: true
 })
 
 const bindProps = computed(() => {
@@ -73,10 +73,11 @@ const bindProps = computed(() => {
     isParent,
     ...columnProps
   } = props
+  const columnAlign = props.align || (attrs.align as ColumnAlign | undefined)
   const resolvedAlign =
     action || zmSortable || zmFilterable
       ? 'left'
-      : attrs.align || props.align || defaultOptions.value.align
+      : columnAlign || tableContext.columnAlign.value || defaultOptions.value.align
 
   return {
     ...defaultOptions.value,

+ 1 - 0
src/components/ZmTable/ZmTableColumnSettingTree.vue

@@ -25,6 +25,7 @@ const emits = defineEmits<{
 const tableContext = inject(TableContextKey, {
   data: ref([]),
   loading: ref(false),
+  columnAlign: ref('center' as const),
   columnMaxWidth: ref(360),
   columnSettings: ref([]),
   updateColumnVisible: () => {},

+ 9 - 1
src/components/ZmTable/index.vue

@@ -13,7 +13,7 @@ import {
   useSlots
 } from 'vue'
 import { TableContextKey } from './token'
-import type { ColumnFixed, ColumnSettingItem } from './token'
+import type { ColumnAlign, ColumnFixed, ColumnSettingItem } from './token'
 import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
 import type { VNode } from 'vue'
 
@@ -34,6 +34,7 @@ interface Props
   loading: boolean
   customClass?: boolean
   showBorder?: boolean
+  align?: ColumnAlign
   columnMaxWidth?: number
 }
 
@@ -64,6 +65,7 @@ const bindProps = computed(() => {
     data,
     customClass: _customClass,
     showBorder: _showBorder,
+    align: _align,
     columnMaxWidth,
     ...otherProps
   } = props
@@ -78,6 +80,11 @@ const bindProps = computed(() => {
 
 const safeData = computed(() => props.data || [])
 const safeLoading = computed(() => props.loading)
+const safeColumnAlign = computed<ColumnAlign>(() => {
+  return props.align === 'left' || props.align === 'right' || props.align === 'center'
+    ? props.align
+    : 'center'
+})
 const safeColumnMaxWidth = computed(() => {
   const maxWidth = Number(props.columnMaxWidth)
   return Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 360
@@ -353,6 +360,7 @@ const TableDefaultSlot = () => renderDefaultSlot()
 provide(TableContextKey, {
   data: safeData,
   loading: safeLoading,
+  columnAlign: safeColumnAlign,
   columnMaxWidth: safeColumnMaxWidth,
   columnSettings,
   updateColumnVisible,

+ 2 - 0
src/components/ZmTable/token.ts

@@ -2,6 +2,7 @@ import type { InjectionKey, Ref } from 'vue'
 
 export type SortOrder = 'asc' | 'desc'
 export type ColumnFixed = false | 'left' | 'right'
+export type ColumnAlign = 'left' | 'center' | 'right'
 
 export interface SortChangePayload {
   prop: string
@@ -19,6 +20,7 @@ export interface ColumnSettingItem {
 export interface TableContext<T = any> {
   data: Ref<T[]>
   loading: Ref<boolean>
+  columnAlign: Ref<ColumnAlign>
   columnMaxWidth: Ref<number>
   columnSettings: Ref<ColumnSettingItem[]>
   updateColumnVisible: (key: string, visible: boolean) => void

+ 209 - 106
src/views/pms/operation-meeting/meeting-form.vue

@@ -1,5 +1,6 @@
 <script lang="ts" setup>
 import type { FormInstance, FormRules } from 'element-plus'
+import type { CSSProperties } from 'vue'
 import type { DeptOption, DetailItem, OperationMeeting } from './types'
 import { OperationMeetingApi } from '@/api/pms/meeting'
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
@@ -12,15 +13,6 @@ interface Props {
   deptOptions?: DeptOption[]
 }
 
-interface SummaryColumn {
-  property?: string
-}
-
-interface DetailSummaryMethodProps {
-  columns: SummaryColumn[]
-  data: DetailItem[]
-}
-
 interface OperationMeetingForm
   extends Omit<Partial<OperationMeeting>, 'meetingDate' | 'meetingSeries'> {
   meetingDate?: number | string | Date
@@ -52,7 +44,7 @@ const drawerTitle = computed(() =>
 )
 
 const isMobile = computed(() => width.value <= 768)
-const drawerSize = computed(() => (isMobile.value ? '100%' : '92.2%'))
+const drawerSize = computed(() => (isMobile.value ? '100%' : '100%'))
 const detailDrawerSize = computed(() => (isMobile.value ? '100%' : '50%'))
 
 const companyDisplayName = computed(() => {
@@ -211,6 +203,13 @@ const detailCardGroups: { title: string; fields: DetailCardField[] }[] = [
   }
 ]
 
+const meetingTableBlueColumns = new Set<keyof DetailItem>([
+  'projectName',
+  'actualCompletion',
+  'keyWorkCompletion',
+  'problemsAnalysis'
+])
+
 const requiredTextRule = (message: string) => [
   {
     required: true,
@@ -355,28 +354,14 @@ const formatDetailCardValue = (item: DetailItem, field: DetailCardField) => {
   return `${formatSummaryNumber(numericValue)}${field.unit || ''}`
 }
 
-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 as DetailSummaryField)) {
-      sums[index] = ''
-      return
-    }
+const getMeetingTableCellStyle: any = ({ column }): CSSProperties | undefined => {
+  const property = column.property as keyof DetailItem | undefined
 
-    const total = data.reduce(
-      (sum, item) => sum + Number(item[column.property as keyof DetailItem] || 0),
-      0
-    )
-    sums[index] = formatSummaryNumber(total)
-  })
+  if (property && meetingTableBlueColumns.has(property)) {
+    return { color: '#1b71f6' }
+  }
 
-  return sums
+  return undefined
 }
 
 const handleAddDetailItem = () => {
@@ -660,8 +645,10 @@ onMounted(() => {
       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>
+      <section
+        class="meeting-info-section bg-white border-solid border-1 border-gray-200/90 rounded-xl mb-4"
+      >
+        <h3 class="meeting-info-section__title">会议信息</h3>
 
         <div class="meeting-section__grid">
           <el-form-item label="所属公司" class="meeting-form-item mb-0! min-w-0">
@@ -699,18 +686,22 @@ onMounted(() => {
             />
           </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>
+          <section class="meeting-summary-strip meeting-section__grid-full">
+            <div class="meeting-summary-strip__title">
+              <span>公司整体</span>
+              <small>经营数据汇总</small>
+            </div>
+            <div class="meeting-summary-strip__grid">
+              <div
+                v-for="item in detailSummaryCards"
+                :key="item.label"
+                class="meeting-summary-strip__item"
+              >
+                <span>{{ item.label }}</span>
+                <strong>{{ item.value }}<em>万元</em></strong>
+              </div>
+            </div>
+          </section>
         </div>
       </section>
       <section class="p-6 bg-white border-solid border-1 border-gray-200/90 rounded-xl">
@@ -723,29 +714,20 @@ onMounted(() => {
         </div>
         <div class="meeting-detail-table-view">
           <ZmTable
+            class="meeting-table"
             :data="detailItems"
             :loading="loading"
-            show-summary
-            :summary-method="getDetailSummaries"
+            :max-height="660"
+            align="left"
+            :show-overflow-tooltip="false"
+            :cell-style="getMeetingTableCellStyle"
           >
-            <zm-table-column width="120" 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 width="156" align="center" label="项目名称" prop="projectName" />
             <zm-table-column label="本期生产运行情况">
-              <zm-table-column width="100" label="计划工作量" prop="plannedWorkload" />
-              <zm-table-column width="100" label="实际完成" prop="actualCompletion" />
+              <zm-table-column width="120" label="计划工作量" prop="plannedWorkload" />
+              <zm-table-column width="120" label="实际完成" prop="actualCompletion" />
               <zm-table-column
-                width="88"
+                width="120"
                 label="设备利用率"
                 prop="equipmentUtilizationRate"
                 :formatter="(row) => `${row.equipmentUtilizationRate}%`"
@@ -753,17 +735,17 @@ onMounted(() => {
             </zm-table-column>
             <zm-table-column label="生产管理情况及重点工作	">
               <zm-table-column label="重点工作及完成情况" prop="keyWorkCompletion" />
-              <zm-table-column label="存在问题及分析" prop="problemsAnalysis" />
+              <zm-table-column width="300" label="存在问题及分析" prop="problemsAnalysis" />
             </zm-table-column>
             <zm-table-column label="下期工作计划		">
-              <zm-table-column width="100" label="计划工作量" prop="nextPlannedWorkload" />
+              <zm-table-column width="120" label="计划工作量" prop="nextPlannedWorkload" />
               <zm-table-column label="重点工作事项" prop="priorityTasks" />
             </zm-table-column>
-            <zm-table-column label="操作" width="120" fixed="right">
+            <zm-table-column label="操作" width="140" fixed="right">
               <template #default="{ row, $index }">
                 <el-button
                   link
-                  size="small"
+                  size="default"
                   type="primary"
                   @click="handleEditDetailItem(row, $index)"
                 >
@@ -772,7 +754,7 @@ onMounted(() => {
                 <el-button
                   v-if="type !== 'view'"
                   link
-                  size="small"
+                  size="default"
                   type="danger"
                   @click="handleDeleteDetailItem($index)"
                 >
@@ -832,23 +814,24 @@ onMounted(() => {
                 </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 class="meeting-support-panel">
+          <el-form-item
+            label="需集团协调支持的事项"
+            prop="support"
+            class="meeting-form-item mb-0! min-w-0"
+          >
+            <el-input
+              v-model="operationMeeting.support"
+              type="textarea"
+              :rows="4"
+              placeholder="请输入需集团协调支持的事项"
+            />
+          </el-form-item>
+        </section>
       </section>
     </el-form>
 
@@ -1053,13 +1036,136 @@ onMounted(() => {
 .meeting-section__grid {
   display: grid;
   grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 32px;
+  gap: 10px 20px;
+}
+
+.meeting-info-section {
+  padding: 10px 16px;
+}
+
+.meeting-info-section__title {
+  margin: 0 0 8px;
+  font-size: 15px;
+  font-weight: 800;
+  line-height: 20px;
+  color: #1f2937;
 }
 
 .meeting-section__grid-full {
   grid-column: 1 / -1;
 }
 
+.meeting-summary-strip {
+  display: grid;
+  grid-template-columns: 112px minmax(0, 1fr);
+  gap: 8px;
+  align-items: stretch;
+  padding: 6px 8px;
+  background: linear-gradient(180deg, #f8fbff 0%, #fff 100%);
+  border: 1px solid #dbeafe;
+  border-radius: 8px;
+}
+
+.meeting-summary-strip__title {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  padding-right: 8px;
+  border-right: 1px solid #dbeafe;
+
+  span {
+    font-size: 14px;
+    font-weight: 800;
+    line-height: 20px;
+    color: #1f2937;
+  }
+
+  small {
+    margin-top: 0;
+    font-size: 12px;
+    line-height: 16px;
+    color: #64748b;
+  }
+}
+
+.meeting-summary-strip__grid {
+  display: grid;
+  grid-template-columns: repeat(6, minmax(0, 1fr));
+  gap: 4px;
+}
+
+.meeting-summary-strip__item {
+  display: flex;
+  min-width: 0;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  padding: 4px 6px;
+  background: rgb(255 255 255 / 72%);
+  border: 1px solid rgb(219 234 254 / 70%);
+  border-radius: 7px;
+
+  span {
+    display: block;
+    flex: 1;
+    min-width: 0;
+    overflow: hidden;
+    font-size: 12px;
+    line-height: 16px;
+    color: #64748b;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  strong {
+    display: flex;
+    min-width: 0;
+    flex-shrink: 0;
+    align-items: baseline;
+    gap: 3px;
+    margin-top: 0;
+    overflow: hidden;
+    font-size: 15px;
+    font-weight: 800;
+    line-height: 18px;
+    color: #1b71f6;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  em {
+    font-size: 12px;
+    font-style: normal;
+    font-weight: 600;
+    color: #64748b;
+  }
+}
+
+.meeting-table {
+  --zm-table-font-size: 18px;
+  --zm-table-header-font-size: 18px;
+  --zm-table-header-font-weight: 800;
+  --zm-table-row-font-weight: 800;
+  --zm-table-header-text-color: #333;
+  --zm-table-border-color: #cbd5e1;
+  --zm-table-header-border-color: #c2ccda;
+  --zm-table-row-border-color: #d4dce8;
+
+  :deep(.header-wrapper) {
+    height: 20px;
+
+    .truncate {
+      height: 20px;
+    }
+  }
+}
+
+.meeting-support-panel {
+  padding-top: 18px;
+  margin-top: 18px;
+  border-top: 1px solid var(--el-border-color-lighter);
+}
+
 .meeting-form-tip {
   margin-top: 6px;
   font-size: 12px;
@@ -1132,8 +1238,7 @@ onMounted(() => {
 }
 
 .meeting-detail-card__title span,
-.meeting-detail-card__field span,
-.meeting-detail-summary-card__item span {
+.meeting-detail-card__field span {
   font-size: 12px;
   line-height: 1.4;
   color: var(--el-text-color-secondary);
@@ -1160,23 +1265,20 @@ onMounted(() => {
   color: var(--el-text-color-primary);
 }
 
-.meeting-detail-card__fields,
-.meeting-detail-summary-card__grid {
+.meeting-detail-card__fields {
   display: grid;
   grid-template-columns: repeat(2, minmax(0, 1fr));
   gap: 10px 12px;
 }
 
-.meeting-detail-card__field,
-.meeting-detail-summary-card__item {
+.meeting-detail-card__field {
   display: flex;
   min-width: 0;
   flex-direction: column;
   gap: 4px;
 }
 
-.meeting-detail-card__field strong,
-.meeting-detail-summary-card__item strong {
+.meeting-detail-card__field strong {
   min-width: 0;
   font-size: 13px;
   font-weight: 600;
@@ -1192,25 +1294,26 @@ onMounted(() => {
   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;
   }
 
+  .meeting-summary-strip {
+    grid-template-columns: 1fr;
+  }
+
+  .meeting-summary-strip__title {
+    padding-right: 0;
+    padding-bottom: 12px;
+    border-right: 0;
+    border-bottom: 1px solid #dbeafe;
+  }
+
+  .meeting-summary-strip__grid {
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+  }
+
   .detail-section__grid {
     grid-template-columns: 1fr;
   }
@@ -1232,8 +1335,8 @@ onMounted(() => {
     gap: 12px;
   }
 
-  .meeting-detail-card__fields,
-  .meeting-detail-summary-card__grid {
+  .meeting-summary-strip__grid,
+  .meeting-detail-card__fields {
     grid-template-columns: 1fr;
   }
 }