From 3471290659516cf21db3211a9053daff5f283e03 Mon Sep 17 00:00:00 2001
From: zhuguifei <312353457@qq.com>
Date: 星期五, 20 三月 2026 15:50:18 +0800
Subject: [PATCH] feat: 基础数据仪器管理、判定依据、判定依据明细

---
 ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue |  408 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 408 insertions(+), 0 deletions(-)

diff --git a/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue b/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue
new file mode 100755
index 0000000..8aad960
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/qm/std/modules/std-sub-table.vue
@@ -0,0 +1,408 @@
+<script setup lang="tsx">
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import type { DataTableColumns, DataTableRowKey } from 'naive-ui';
+import { NDivider } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDeleteCheckitem, fetchGetCheckitemTree } from '@/service/api/qm/checkitem';
+import { useAuth } from '@/hooks/business/auth';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import CheckitemOperateDrawer from '@/views/qm/checkitem/modules/checkitem-operate-drawer.vue';
+
+defineOptions({
+  name: 'StdSubTable'
+});
+
+interface Props {
+  stdId?: CommonType.IdType | null;
+  stdCode?: string | '';
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  stdId: null,
+  stdCode: ''
+});
+
+interface StdSubRow extends Api.Qm.Checkitem {
+  children?: StdSubRow[];
+}
+
+const { loading, startLoading, endLoading } = useLoading();
+const { hasAuth } = useAuth();
+const rows = ref<StdSubRow[]>([]);
+const checkedRowKeys = ref<CommonType.IdType[]>([]);
+const drawerVisible = ref(false);
+const operateType = ref<NaiveUI.TableOperateType>('add');
+const editingData = ref<Api.Qm.Checkitem | null>(null);
+const showFullscreen = ref(false);
+const fullscreenStyle = ref<Record<string, string>>({});
+const onFullscreenKeydown = (event: KeyboardEvent) => {
+  if (event.key === 'Escape') {
+    showFullscreen.value = false;
+  }
+};
+
+const cardTitle = computed(() => `瑙勭▼鏄庣粏${props.stdCode || ''}`);
+
+async function getSubList() {
+  if (!props.stdId && !props.stdCode) {
+    rows.value = [];
+    return;
+  }
+
+  startLoading();
+  try {
+    const { data, error } = await fetchGetCheckitemTree({
+      pageNum: 1,
+      pageSize: 9999,
+      stdCode: props.stdId != null ? String(props.stdId) : String(props.stdCode)
+    });
+    if (error) {
+      rows.value = [];
+      return;
+    }
+
+    rows.value = data.rows;
+  } finally {
+    endLoading();
+  }
+}
+
+watch(
+  () => [props.stdId, props.stdCode],
+  async () => {
+    checkedRowKeys.value = [];
+    await getSubList();
+  },
+  { immediate: true }
+);
+
+watch(
+  showFullscreen,
+  visible => {
+    if (visible) {
+      updateFullscreenStyle();
+      window.addEventListener('keydown', onFullscreenKeydown);
+      window.addEventListener('resize', updateFullscreenStyle);
+      window.addEventListener('scroll', updateFullscreenStyle, true);
+      return;
+    }
+    window.removeEventListener('keydown', onFullscreenKeydown);
+    window.removeEventListener('resize', updateFullscreenStyle);
+    window.removeEventListener('scroll', updateFullscreenStyle, true);
+  },
+  { immediate: true }
+);
+
+onBeforeUnmount(() => {
+  window.removeEventListener('keydown', onFullscreenKeydown);
+  window.removeEventListener('resize', updateFullscreenStyle);
+  window.removeEventListener('scroll', updateFullscreenStyle, true);
+});
+
+onMounted(() => {
+  updateFullscreenStyle();
+});
+
+function updateFullscreenStyle() {
+  const container = document.querySelector<HTMLElement>('.std-content-area');
+  if (!container) {
+    fullscreenStyle.value = {
+      position: 'fixed',
+      inset: '0',
+      zIndex: '20'
+    };
+    return;
+  }
+  const rect = container.getBoundingClientRect();
+  fullscreenStyle.value = {
+    position: 'fixed',
+    left: `${rect.left}px`,
+    top: `${rect.top}px`,
+    width: `${rect.width}px`,
+    height: `${rect.height}px`,
+    zIndex: '20'
+  };
+}
+
+function handleAdd() {
+  if (!props.stdId && !props.stdCode) {
+    window.$message?.warning('璇峰厛閫夋嫨瑙勭▼');
+    return;
+  }
+  operateType.value = 'add';
+  editingData.value = null;
+  drawerVisible.value = true;
+}
+
+function handleEdit(row: StdSubRow) {
+  operateType.value = 'edit';
+  editingData.value = jsonClone(row);
+  drawerVisible.value = true;
+}
+
+async function handleBatchDelete() {
+  if (checkedRowKeys.value.length === 0) return;
+  const { error } = await fetchBatchDeleteCheckitem(checkedRowKeys.value);
+  if (error) return;
+  window.$message?.success($t('common.deleteSuccess'));
+  checkedRowKeys.value = [];
+  await getSubList();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+  const { error } = await fetchBatchDeleteCheckitem([id]);
+  if (error) return;
+  window.$message?.success($t('common.deleteSuccess'));
+  await getSubList();
+}
+
+const columns = computed<DataTableColumns<StdSubRow>>(() => [
+  {
+    type: 'selection',
+    align: 'center',
+    width: 48
+  },
+  {
+    key: 'index',
+    title: $t('common.index'),
+    align: 'center',
+    width: 56,
+    render: (_: any, index: number) => index + 1
+  },
+  {
+    key: 'itemName',
+    title: '椤圭洰鍚嶇О',
+    align: 'left',
+    width: 400,
+    tree: true
+  } as any,
+  {
+    key: 'itemCode',
+    title: '椤圭洰浠g爜',
+    align: 'center',
+    width: 180
+  },
+  {
+    key: 'unit',
+    title: '鍗曚綅',
+    align: 'center',
+    width: 80
+  },
+  {
+    key: 'enable',
+    title: '鍚敤',
+    align: 'center',
+    width: 72,
+    render: row => (String(row.enable) === '1' ? '鍚敤' : '鍋滅敤')
+  },
+  {
+    key: 'score',
+    title: '鍒嗗��',
+    align: 'center',
+    width: 80
+  },
+  {
+    key: 'itemDes',
+    title: '鎻忚堪',
+    align: 'center',
+    minWidth: 160
+  },
+  {
+    key: 'operate',
+    title: $t('common.operate'),
+    align: 'center',
+    width: 130,
+    render: row => {
+      const divider = () => {
+        if (!hasAuth('qm:checkitem:edit') || !hasAuth('qm:checkitem:remove')) {
+          return null;
+        }
+        return <NDivider vertical />;
+      };
+
+      const editBtn = () => {
+        if (!hasAuth('qm:checkitem:edit')) {
+          return null;
+        }
+        return (
+          <ButtonIcon
+            text
+            type="primary"
+            icon="material-symbols:drive-file-rename-outline-outline"
+            tooltipContent={$t('common.edit')}
+            onClick={() => handleEdit(row)}
+          />
+        );
+      };
+
+      const deleteBtn = () => {
+        if (!hasAuth('qm:checkitem:remove')) {
+          return null;
+        }
+        return (
+          <ButtonIcon
+            text
+            type="error"
+            icon="material-symbols:delete-outline"
+            tooltipContent={$t('common.delete')}
+            popconfirmContent={$t('common.confirmDelete')}
+            onPositiveClick={() => handleDelete(row.id)}
+          />
+        );
+      };
+
+      return (
+        <div class="flex-center gap-8px">
+          {editBtn()}
+          {divider()}
+          {deleteBtn()}
+        </div>
+      );
+    }
+  }
+]);
+
+function rowKey(row: StdSubRow): DataTableRowKey {
+  return String(row.id);
+}
+</script>
+
+<template>
+  <NCard
+    :title="cardTitle"
+    :bordered="false"
+    size="small"
+    class="flex-col-stretch card-wrapper"
+    :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+  >
+    <template #header-extra>
+      <NSpace align="center">
+        <TableHeaderOperation
+          :disabled-delete="checkedRowKeys.length === 0"
+          :loading="loading"
+          :show-add="hasAuth('qm:checkitem:add')"
+          :show-delete="hasAuth('qm:checkitem:remove')"
+          :show-export="false"
+          @add="handleAdd"
+          @delete="handleBatchDelete"
+          @refresh="getSubList"
+        />
+        <NButton size="small" @click="showFullscreen = true">
+          <template #icon>
+            <icon-mdi-fullscreen class="text-icon" />
+          </template>
+          鍏ㄥ睆
+        </NButton>
+      </NSpace>
+    </template>
+
+    <NSpin :show="loading" class="h-full" content-class="h-full">
+      <div v-if="!props.stdId && !props.stdCode" class="h-full flex-center text-gray-400">
+        璇风偣鍑讳笂鏂硅〃鏍艰鏌ョ湅鏄庣粏
+      </div>
+      <NDataTable
+        v-else
+        v-model:checked-row-keys="checkedRowKeys"
+        :columns="columns as any"
+        :data="rows"
+        size="small"
+        flex-height
+        children-key="children"
+        :row-key="rowKey"
+        default-expand-all
+        striped
+        style="height: 200px"
+      />
+    </NSpin>
+  </NCard>
+
+  <Teleport to="body">
+    <div v-if="showFullscreen" class="fullscreen-mask" :style="fullscreenStyle" @click.self="showFullscreen = false">
+      <NCard
+        :title="cardTitle"
+        :bordered="false"
+        size="small"
+        class="flex-col-stretch fullscreen-card"
+        :content-style="{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }"
+      >
+        <template #header-extra>
+          <NSpace align="center">
+            <TableHeaderOperation
+              :disabled-delete="checkedRowKeys.length === 0"
+              :loading="loading"
+              :show-add="hasAuth('qm:checkitem:add')"
+              :show-delete="hasAuth('qm:checkitem:remove')"
+              :show-export="false"
+              @add="handleAdd"
+              @delete="handleBatchDelete"
+              @refresh="getSubList"
+            />
+            <NButton size="small" ghost type="error" @click="showFullscreen = false">
+              <template #icon>
+                <icon-mdi-close class="text-icon" />
+              </template>
+              鍏抽棴
+            </NButton>
+          </NSpace>
+        </template>
+
+        <NSpin :show="loading" class="h-full" content-class="h-full">
+          <div v-if="!props.stdId && !props.stdCode" class="h-full flex-center text-gray-400">
+            璇风偣鍑讳笂鏂硅〃鏍艰鏌ョ湅鏄庣粏
+          </div>
+          <NDataTable
+            v-else
+            v-model:checked-row-keys="checkedRowKeys"
+            :columns="columns as any"
+            :data="rows"
+            size="small"
+            flex-height
+            children-key="children"
+            :row-key="rowKey"
+            striped
+            class="fullscreen-table"
+          />
+        </NSpin>
+      </NCard>
+    </div>
+  </Teleport>
+
+  <CheckitemOperateDrawer
+    v-model:visible="drawerVisible"
+    :operate-type="operateType"
+    :row-data="editingData"
+    :std-code="props.stdId"
+    @submitted="getSubList"
+  />
+</template>
+
+<style scoped>
+:deep(.n-card__content) {
+  padding: 8px 12px;
+}
+
+:deep(.n-data-table-th),
+:deep(.n-data-table-td) {
+  padding: 4px 6px;
+}
+
+.fullscreen-mask {
+  position: absolute;
+  inset: 0;
+  z-index: 20;
+  background: #fff;
+  padding: 0;
+  display: flex;
+}
+
+.fullscreen-card {
+  width: 100%;
+  height: 100%;
+}
+
+.fullscreen-table {
+  height: 100%;
+}
+</style>

--
Gitblit v1.9.3