WXL
2 天以前 c8e9849cb5f24848df0174c13bfbbff37bb08a5a
src/components/MaintainComponents/LiverKidneyPanel.vue
@@ -2,15 +2,26 @@
  <div class="medical-panel">
    <div class="panel-header">
      <h3>肝功能肾功能记录表</h3>
      <el-button
        v-if="isEditing"
        type="primary"
        size="small"
        @click="addColumn"
        icon="el-icon-plus"
      >
        新增记录列
      </el-button>
      <div class="panel-actions">
        <el-button
          v-if="isEditing"
          type="primary"
          size="small"
          @click="addColumn"
          icon="el-icon-plus"
        >
          新增记录列
        </el-button>
        <el-tooltip content="刷新表格布局" placement="top">
          <el-button
            size="small"
            @click="forceTableLayout"
            icon="el-icon-refresh"
          >
            刷新布局
          </el-button>
        </el-tooltip>
      </div>
    </div>
    <el-table
@@ -19,7 +30,6 @@
      style="width: 100%"
      class="medical-table"
      :key="tableKey"
      @header-dragend="handleHeaderDragEnd"
    >
      <el-table-column
@@ -30,19 +40,61 @@
        class-name="leave-alone"
      >
        <template #default="scope">
          <span :class="{ 'required-item': scope.row.required }">
            {{ scope.row.itemName }}
          </span>
          <div class="item-name-cell">
            <span :class="{ 'required-item': scope.row.required }">
              {{ scope.row.itemName }}
            </span>
            <el-tooltip
              v-if="scope.row.reference"
              :content="`参考范围: ${scope.row.reference}`"
              placement="top"
            >
              <i class="el-icon-info reference-icon"></i>
            </el-tooltip>
          </div>
        </template>
      </el-table-column>
      <el-table-column
        v-for="(col, index) in dynamicColumns"
        :key="col.key"
        :label="col.label"
        :min-width="120"
        :min-width="140"
        :resizable="isEditing"
        header-align="center"
        align="center"
      >
        <!-- 自定义列头 -->
        <template #header>
          <div class="custom-column-header">
            <div class="header-content" @click.stop="handleEditColumn(index)">
              <span class="header-label">{{ col.label.replace('\n', ' ') }}</span>
              <el-tooltip v-if="col.remark" :content="col.remark" placement="top">
                <i class="el-icon-info header-remark-icon"></i>
              </el-tooltip>
            </div>
            <div class="header-actions" v-if="isEditing">
              <el-tooltip content="编辑时间" placement="top">
                <el-button
                  size="mini"
                  type="text"
                  icon="el-icon-edit"
                  class="header-btn"
                  @click.stop="handleEditColumn(index)"
                />
              </el-tooltip>
              <el-tooltip content="删除此列" placement="top">
                <el-button
                  size="mini"
                  type="text"
                  icon="el-icon-delete"
                  class="header-btn delete-btn"
                  @click.stop="handleDeleteColumnByIndex(index)"
                />
              </el-tooltip>
            </div>
          </div>
        </template>
        <template #default="scope">
          <div class="cell-content">
            <el-input
@@ -157,34 +209,28 @@
    <!-- 列编辑对话框 -->
    <el-dialog
      title="编辑时间点"
      :title="`${editingColumnIndex !== null ? '编辑' : '新增'}时间点`"
      :visible.sync="columnDialogVisible"
      width="400px"
      width="450px"
      @closed="handleDialogClosed"
    >
      <el-form :model="columnForm" label-width="80px" ref="columnForm">
        <el-form-item
          label="日期"
          prop="date"
          :rules="[
            { required: true, message: '请选择日期', trigger: 'change' }
          ]"
        >
      <el-form
        :model="columnForm"
        label-width="80px"
        ref="columnForm"
        :rules="columnRules"
      >
        <el-form-item label="日期" prop="date">
          <el-date-picker
            v-model="columnForm.date"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
            style="width: 100%"
            :disabled-date="disableFutureDates"
          />
        </el-form-item>
        <el-form-item
          label="时间"
          prop="time"
          :rules="[
            { required: true, message: '请选择时间', trigger: 'change' }
          ]"
        >
        <el-form-item label="时间" prop="time">
          <el-time-picker
            v-model="columnForm.time"
            placeholder="选择时间"
@@ -192,10 +238,33 @@
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input
            v-model="columnForm.remark"
            type="textarea"
            :rows="2"
            placeholder="可选填写时间点备注信息"
            maxlength="100"
            show-word-limit
          />
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="columnDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="confirmAddColumn">确定</el-button>
        <el-button
          v-if="editingColumnIndex !== null"
          type="danger"
          @click="handleDeleteColumn"
        >
          删除
        </el-button>
        <el-button
          type="primary"
          @click="confirmAddColumn"
          :loading="saveLoading"
        >
          确定
        </el-button>
      </span>
    </el-dialog>
@@ -238,10 +307,18 @@
      columnDialogVisible: false,
      columnForm: {
        date: "",
        time: ""
        time: "",
        remark: ""
      },
      editingColumnIndex: null,
      tableKey: 0,
      tableLoading: false,
      saveLoading: false,
      internalData: {},
      columnRules: {
        date: [{ required: true, message: "请选择日期", trigger: "change" }],
        time: [{ required: true, message: "请选择时间", trigger: "change" }]
      },
      // 预览相关
      previewVisible: false,
@@ -279,7 +356,8 @@
          this.forceTableLayout();
        });
      },
      deep: true
      deep: true,
      immediate: true
    },
    // 监听附件数据变化
    attachments: {
@@ -353,7 +431,8 @@
            label: `${new Date().toISOString().split("T")[0]}\n08:00`,
            key: "time1",
            date: new Date().toISOString().split("T")[0],
            time: "08:00"
            time: "08:00",
            remark: "晨起检测"
          }
        ];
      }
@@ -376,11 +455,31 @@
    },
    addColumn() {
      this.editingColumnIndex = null;
      this.columnForm = {
        date: new Date().toISOString().split("T")[0],
        time: "08:00"
        time: "08:00",
        remark: ""
      };
      this.columnDialogVisible = true;
      this.$nextTick(() => {
        this.$refs.columnForm && this.$refs.columnForm.clearValidate();
      });
    },
    handleEditColumn(index) {
      if (!this.isEditing) return;
      this.editingColumnIndex = index;
      const column = this.dynamicColumns[index];
      this.columnForm = {
        date: column.date || '',
        time: column.time || '',
        remark: column.remark || ''
      };
      this.columnDialogVisible = true;
      this.$nextTick(() => {
        this.$refs.columnForm && this.$refs.columnForm.clearValidate();
      });
    },
    confirmAddColumn() {
@@ -390,34 +489,114 @@
          return;
        }
        const newIndex = this.dynamicColumns.length + 1;
        const newColumn = {
          label: `${this.columnForm.date}\n${this.columnForm.time}`,
          key: `time${newIndex}`,
          date: this.columnForm.date,
          time: this.columnForm.time
        };
        this.saveLoading = true;
        this.dynamicColumns.push(newColumn);
        if (this.editingColumnIndex !== null) {
          // 编辑现有列
          const column = this.dynamicColumns[this.editingColumnIndex];
          column.label = `${this.columnForm.date}\n${this.columnForm.time}`;
          column.date = this.columnForm.date;
          column.time = this.columnForm.time;
          column.remark = this.columnForm.remark;
          this.$message.success("时间点修改成功");
        } else {
          // 新增列
          const newIndex = this.dynamicColumns.length + 1;
          const newColumn = {
            label: `${this.columnForm.date}\n${this.columnForm.time}`,
            key: `time${newIndex}_${Date.now()}`,
            date: this.columnForm.date,
            time: this.columnForm.time,
            remark: this.columnForm.remark
          };
        // 为所有行新增一个空值
        this.tableData.forEach(row => {
          if (!row.values) {
            row.values = [];
          }
          row.values.push("");
        });
          this.dynamicColumns.push(newColumn);
          // 为所有行新增一个空值
          this.tableData.forEach(row => {
            if (!row.values) {
              row.values = [];
            }
            row.values.push("");
          });
          this.$message.success("时间点添加成功");
        }
        this.columnDialogVisible = false;
        this.$message.success("时间点添加成功");
        this.saveLoading = false;
        this.tableKey += 1;
        this.saveData();
      });
    },
    handleDeleteColumn() {
      if (this.editingColumnIndex !== null) {
        this.$confirm(
          `确定要删除「${this.dynamicColumns[this.editingColumnIndex].label.replace('\n', ' ')}」这个时间点的所有数据吗?`,
          '删除确认',
          {
            confirmButtonText: '确定删除',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).then(() => {
          // 删除列
          this.dynamicColumns.splice(this.editingColumnIndex, 1);
          // 同步删除每行对应的数据
          this.tableData.forEach(row => {
            if (row.values && row.values.length > this.editingColumnIndex) {
              row.values.splice(this.editingColumnIndex, 1);
            }
          });
          this.columnDialogVisible = false;
          this.tableKey += 1;
          this.saveData();
          this.$message.success("时间点删除成功");
        }).catch(() => {
          // 取消删除,不做任何操作
        });
      }
    },
    handleDeleteColumnByIndex(index) {
      this.$confirm(
        `确定要删除「${this.dynamicColumns[index].label.replace('\n', ' ')}」这个时间点的所有数据吗?`,
        '删除确认',
        {
          confirmButtonText: '确定删除',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).then(() => {
        // 删除列
        this.dynamicColumns.splice(index, 1);
        // 同步删除每行对应的数据
        this.tableData.forEach(row => {
          if (row.values && row.values.length > index) {
            row.values.splice(index, 1);
          }
        });
        this.tableKey += 1;
        this.saveData();
        this.$message.success("时间点删除成功");
      }).catch(() => {
        // 取消删除,不做任何操作
      });
    },
    handleDialogClosed() {
      this.columnForm = { date: "", time: "" };
      this.editingColumnIndex = null;
      this.columnForm = { date: "", time: "", remark: "" };
      this.$refs.columnForm && this.$refs.columnForm.clearValidate();
    },
    disableFutureDates(time) {
      return time.getTime() > Date.now();
    },
    handleValueChange(row, columnIndex) {
@@ -662,6 +841,12 @@
  font-weight: 600;
}
.panel-actions {
  display: flex;
  gap: 12px;
  align-items: center;
}
.medical-table {
  margin-bottom: 30px;
  min-width: 100%;
@@ -671,34 +856,116 @@
  overflow-x: auto;
}
/* 自定义列头样式 */
.custom-column-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 4px 0;
}
.header-content {
  display: flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  transition: color 0.2s;
  white-space: nowrap;
}
.header-content:hover {
  color: #409eff;
}
.header-label {
  font-size: 13px;
  line-height: 1.4;
}
.header-remark-icon {
  color: #909399;
  font-size: 12px;
  cursor: help;
}
.header-actions {
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.2s;
}
.custom-column-header:hover .header-actions {
  opacity: 1;
}
.header-btn {
  padding: 2px 4px !important;
  font-size: 14px !important;
}
.header-btn.delete-btn {
  color: #f56c6c !important;
}
.header-btn.delete-btn:hover {
  color: #c0392b !important;
}
/* 编辑模式下始终显示操作按钮 */
.is-editing .header-actions {
  opacity: 1;
}
.item-name-cell {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.reference-icon {
  color: #909399;
  font-size: 14px;
  cursor: help;
  margin-left: 8px;
}
.cell-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 32px;
  min-height: 38px;
  gap: 8px;
}
.value-input {
  flex: 1;
  margin-right: 8px;
  min-width: 60px;
}
.value-display {
  font-weight: 500;
  color: #303133;
  max-width: 70px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.unit {
  color: #909399;
  font-size: 12px;
  margin-left: 4px;
  font-size: 11px;
  margin-left: 2px;
  flex-shrink: 0;
}
.reference-range {
  color: #67c23a;
  font-size: 12px;
  font-size: 11px;
  font-style: italic;
  margin-left: 8px;
  margin-left: 4px;
  flex-shrink: 0;
}
.required-item::before {
@@ -761,24 +1028,39 @@
  .panel-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .panel-header h3 {
    margin-bottom: 10px;
    margin-bottom: 0;
  }
  .panel-actions {
    width: 100%;
    justify-content: flex-end;
  }
  .cell-content {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }
  .reference-range {
    margin-left: 0;
    margin-top: 4px;
    margin-top: 2px;
  }
  .attachment-section {
    padding: 10px;
  }
  .custom-column-header {
    padding: 2px 0;
  }
  .header-actions {
    opacity: 1;
  }
}
</style>