yxh
2026-10-18 01ee694a3bb8048372fc793a8de314d70b688b35
src/views/ecg/checktype/QueueSequenceForm.vue
@@ -7,7 +7,7 @@
    背景:该表原先每天自动生成,现已改为手工维护,此前没有维护界面。
  -->
  <Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
  <Dialog :title="dialogTitle" v-model="dialogVisible" width="65%" >
    <!-- 顶部说明:这些字段的含义与"每日重置"的关系 -->
    <el-alert type="info" :closable="false" class="mb-10px">
      <template #title>
@@ -19,7 +19,8 @@
      <div class="text-12px leading-5">
        · <b>起始序号</b>:每天自动重置时回到该值;<b>上限</b>:到达后该时段视为「已满」,患者签到会顺延到后续时段。<br />
        · <b>当前序号</b>:已发到几号,一般无需手工改(每日重置会自动回到"起始序号")。<br />
        · <b>序号上限必须大于起始序号</b>,否则该时段永远无法取号。<br />
        · <b>预定额度</b> = 上限 − 起始序号(只读)。<b>上限 = 起始序号 表示该时段没有名额</b>(允许保存,预定额度显示 0 并标红)。<br />
        · 上限 <b>不能小于</b>起始序号,否则该时段永远无法取号。<br />
        · 同一检查项目下<b>同一时段只能有一条规则</b>,重复添加会被拒绝。
      </div>
    </el-alert>
@@ -36,15 +37,26 @@
      </span>
    </div>
    <el-table v-loading="loading" :data="list" :stripe="true" border height="420px">
      <el-table-column label="时段" align="center" min-width="130px">
    <el-table
      v-loading="loading"
      :data="list"
      :stripe="true"
      border
      class="qs-table"
      height="calc(100vh - 300px)"
    >
      <!-- ============ ① 时段 ============ -->
      <el-table-column label="时段" align="center" width="96">
        <template #default="scope">
          <b>{{ formatTimeSlot(scope.row.timeSlot) }}</b>
          <div class="text-11px text-gray-400">{{ scope.row.timeSlot }}</div>
          <b class="text-12px" :title="String(scope.row.timeSlot ?? '')">
            {{ formatTimeSlot(scope.row.timeSlot) }}
          </b>
        </template>
      </el-table-column>
      <el-table-column label="普通-起始序号" align="center" min-width="120px">
      <!-- ============ ② 正常预约 ============ -->
      <el-table-column label="正常预约" align="center">
        <el-table-column label="起始序号" align="center" width="90">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueSet"
@@ -56,11 +68,11 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="普通-上限" align="center" min-width="110px">
        <el-table-column label="上限" align="center" width="80">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueFull"
            :min="1"
              :min="0"
            :controls="false"
            size="small"
            style="width: 100%"
@@ -68,7 +80,18 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="普通-当前序号" align="center" min-width="115px">
        <el-table-column label="预定额度" align="center" width="90">
          <template #default="scope">
            <span
              class="qs-quota"
              :class="{ 'qs-quota--zero': quotaOf(scope.row.queueSet, scope.row.queueFull) <= 0 }"
              :title="`上限 ${scope.row.queueFull ?? 0} − 起始 ${scope.row.queueSet ?? 0}`"
            >
              {{ quotaOf(scope.row.queueSet, scope.row.queueFull) }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="当前序号" align="center" width="90">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueNo"
@@ -80,8 +103,17 @@
          />
        </template>
      </el-table-column>
        <el-table-column label="状态" align="center" width="90">
          <template #default="scope">
            <el-tag v-if="isFull(scope.row)" type="danger" size="small">已满</el-tag>
            <el-tag v-else type="success" size="small">可取号</el-tag>
          </template>
        </el-table-column>
      </el-table-column>
      <el-table-column label="VIP-起始序号" align="center" min-width="115px">
      <!-- ============ ③ 预留 ============ -->
      <el-table-column label="预留" align="center">
        <el-table-column label="起始序号" align="center" width="90">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueVipSet"
@@ -93,11 +125,11 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="VIP-上限" align="center" min-width="105px">
        <el-table-column label="上限" align="center" width="80">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueVipFull"
            :min="1"
              :min="0"
            :controls="false"
            size="small"
            style="width: 100%"
@@ -105,7 +137,18 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="VIP-当前序号" align="center" min-width="110px">
        <el-table-column label="预定额度" align="center" width="90">
          <template #default="scope">
            <span
              class="qs-quota"
              :class="{ 'qs-quota--zero': quotaOf(scope.row.queueVipSet, scope.row.queueVipFull) <= 0 }"
              :title="`上限 ${scope.row.queueVipFull ?? 0} − 起始 ${scope.row.queueVipSet ?? 0}`"
            >
              {{ quotaOf(scope.row.queueVipSet, scope.row.queueVipFull) }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="当前序号" align="center" width="90">
        <template #default="scope">
          <el-input-number
            v-model="scope.row.queueVipNo"
@@ -117,26 +160,32 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="状态" align="center" width="90px">
        <el-table-column label="状态" align="center" width="90">
        <template #default="scope">
          <el-tag v-if="isFull(scope.row)" type="danger" size="small">已满</el-tag>
            <el-tag v-if="isFullVip(scope.row)" type="danger" size="small">已满</el-tag>
          <el-tag v-else type="success" size="small">可取号</el-tag>
        </template>
      </el-table-column>
      </el-table-column>
      <el-table-column label="操作" align="center" width="160px" fixed="right">
      <!-- ============ ④ 操作 ============ -->
      <el-table-column label="操作" align="center" width="160" fixed="right">
        <template #default="scope">
          <el-button
            link
            type="primary"
            :loading="savingId === scope.row.id"
            @click="handleRowSave(scope.row)"
          >
            保存
          </el-button>
          <el-button link type="primary" @click="openForm('update', scope.row)">编辑</el-button>
          <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          <el-button link type="primary" @click="openForm('update', scope.row)">
            编辑
          </el-button>
          <el-button link type="danger" @click="handleDelete(scope.row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
@@ -174,13 +223,15 @@
          <el-input-number v-model="formData.queueSet" :min="0" class="w-full" />
        </el-form-item>
        <el-form-item label="普通-上限" prop="queueFull">
          <el-input-number v-model="formData.queueFull" :min="1" class="w-full" />
          <!-- 【本次需求】上限允许等于起始序号(=该时段无名额),故 min 改为 0 -->
          <el-input-number v-model="formData.queueFull" :min="0" class="w-full" />
        </el-form-item>
        <el-form-item label="VIP-起始序号" prop="queueVipSet">
          <el-input-number v-model="formData.queueVipSet" :min="0" class="w-full" />
        </el-form-item>
        <el-form-item label="VIP-上限" prop="queueVipFull">
          <el-input-number v-model="formData.queueVipFull" :min="1" class="w-full" />
          <!-- 【本次需求】同普通上限:允许等于起始序号 -->
          <el-input-number v-model="formData.queueVipFull" :min="0" class="w-full" />
        </el-form-item>
        <el-form-item label="普通-当前序号">
          <el-input-number v-model="formData.queueNo" :min="0" class="w-full" />
@@ -256,8 +307,23 @@
    {
      validator: (_rule: any, value: any, callback: any) => {
        const set = formData.value.queueSet
        if (value !== undefined && value !== null && set !== undefined && set !== null && value <= set) {
          callback(new Error('上限必须大于起始序号,否则该时段无法取号'))
        // 【本次需求】上限 = 起始序号 是**合法**的:表示该时段没有名额(预定额度 0),允许保存。
        // 只有「上限 < 起始序号」才是配置错误(该时段永远无法取号)。
        if (value !== undefined && value !== null && set !== undefined && set !== null && value < set) {
          callback(new Error('上限不能小于起始序号(等于表示该时段没有名额)'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ],
  queueVipFull: [
    {
      validator: (_rule: any, value: any, callback: any) => {
        const set = formData.value.queueVipSet
        if (value !== undefined && value !== null && set !== undefined && set !== null && value < set) {
          callback(new Error('预留上限不能小于预留起始序号(等于表示预留没有名额)'))
        } else {
          callback()
        }
@@ -306,15 +372,47 @@
  }
}
/** 该时段是否已满(当前序号 >= 上限) */
const isFull = (row: QueueSequenceVO) =>
  (row.queueNo ?? 0) >= (row.queueFull ?? 0) && (row.queueFull ?? 0) > 0
/**
 * 预定额度 = 上限 − 起始序号(小于 0 按 0 显示)。
 *
 * 【本次需求】该列只读,值为 0 时单元格浅红底 —— 表示这个时段**没有名额**。
 * 「上限 = 起始序号」是合法配置(无名额时段),不再当作错误。
 */
const quotaOf = (set?: number | null, full?: number | null) =>
  Math.max(0, (full ?? 0) - (set ?? 0))
/**
 * 该时段是否已满(正常预约)。
 *
 * 满的两种情形:
 *  ① 预定额度 <= 0(上限 <= 起始序号)→ 本来就没有名额;
 *  ② 当前序号已达到上限。
 */
const isFull = (row: QueueSequenceVO) => {
  if (quotaOf(row.queueSet, row.queueFull) <= 0) return true
  return (row.queueNo ?? 0) >= (row.queueFull ?? 0)
}
/** 该时段是否已满(预留组),口径同上 */
const isFullVip = (row: QueueSequenceVO) => {
  if (quotaOf(row.queueVipSet, row.queueVipFull) <= 0) return true
  return (row.queueVipNo ?? 0) >= (row.queueVipFull ?? 0)
}
/** 行内保存(只改数值,不动时段) */
const handleRowSave = async (row: QueueSequenceVO) => {
  if (!row.id) return
  if ((row.queueFull ?? 0) <= (row.queueSet ?? 0)) {
    message.error(`时段 ${formatTimeSlot(row.timeSlot)}:上限必须大于起始序号`)
  // 【本次需求】只有「上限 < 起始序号」才是错误;相等表示该时段没有名额,允许保存。
  if ((row.queueFull ?? 0) < (row.queueSet ?? 0)) {
    message.error(
      `时段 ${formatTimeSlot(row.timeSlot)}:上限不能小于起始序号(等于表示该时段没有名额)`
    )
    return
  }
  if ((row.queueVipFull ?? 0) < (row.queueVipSet ?? 0)) {
    message.error(
      `时段 ${formatTimeSlot(row.timeSlot)}:预留上限不能小于预留起始序号(等于表示预留没有名额)`
    )
    return
  }
  savingId.value = row.id
@@ -388,3 +486,35 @@
  await getList()
}
</script>
<style scoped lang="scss">
/*
  【本次调整】字号恢复为系统默认,**允许横向滚动**:
    · 不再压缩字号与内边距 —— 表体恢复默认字号(14px)、输入框默认字号、状态标签用 small 默认样式;
    · 列宽由模板固定(时段 96 + 正常预约 5 列 + 预留 5 列 + 操作 160);
      总宽超过弹窗宽度时表格出现**横向滚动条**,操作列 fixed="right" 始终可见;
    · 表格高度 calc(100vh - 300px):竖向多显示几行,页面本身不出现滚动条。
*/
.qs-table {
  /* 行内数字输入占满单元格(不缩小字号,只控制宽度) */
  :deep(.el-input-number) {
    width: 100%;
  }
}
/* 预定额度(只读):值为 0 时浅红底,表示该时段没有名额 */
.qs-quota {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--el-text-color-primary);
}
.qs-quota--zero {
  color: var(--el-color-danger);
  background-color: var(--el-color-danger-light-9);
  box-shadow: inset 0 0 0 1px var(--el-color-danger-light-5);
}
</style>