<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import {
  NButton,
  NCard,
  NCode,
  NDynamicInput,
  NForm,
  NFormItem,
  NGi,
  NGrid,
  NInput,
  NInputNumber,
  NSpace,
  NSwitch,
  NTabPane,
  NTabs
} from 'naive-ui';
import {
  fetchGetSchoolDetailJsonAll,
  fetchUpdateSchoolDetailAccommodationJson,
  fetchUpdateSchoolDetailEmploymentReportJson,
  fetchUpdateSchoolDetailPhotoJson,
  fetchUpdateSchoolDetailResearchJson,
  fetchUpdateSchoolDetailSatisfactionJson,
  fetchUpdateSchoolDetailScholarshipJson,
  fetchUpdateSchoolDetailSpecialMajorJson,
  fetchUpdateSchoolDetailSubjectReviewsJson,
  fetchUpdateSchoolDetailUnivMajorsJson,
  fetchUpdateSchoolDetailUnivPostgraduateJson
} from '@/service/api/art/school-detail';

defineOptions({
  name: 'SchoolDetailJson'
});

interface Props {
  schoolId?: CommonType.IdType | null;
  inModal?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  schoolId: null,
  inModal: false
});

interface SatisfactionDetail {
  name: string;
  votes: string;
  score: string;
  s1: string;
  s2: string;
  s3: string;
  s4: string;
  s5: string;
}

interface ScholarshipItem {
  name: string;
  value: string;
}

interface SpecialMajorItem {
  majorId: string;
  majorName: string;
}

interface SpecialMajorGroup {
  type: string;
  majorList: SpecialMajorItem[];
}

interface EmploymentReportItem {
  reportName: string;
  onlinePreview: string;
  reportUrl: string;
}

interface PhotoSceneryItem {
  cover: string;
  size: number | null;
  scenery: string;
}

interface PhotoJson {
  staticUrl: string;
  scenerys: PhotoSceneryItem[];
}

interface AccommodationItem {
  name: string;
  value: string;
}

interface SubjectReviewItem {
  subjectName: string;
  assessLevel: string;
  assessGrade: string;
}

interface SubjectReviewGroup {
  type: string;
  subList: SubjectReviewItem[];
}

interface ResearchBlock {
  name: string;
  value: string;
}

interface ResearchJson {
  features: ResearchBlock;
  teacher: ResearchBlock;
  laboratory: ResearchBlock[];
  subject: ResearchBlock[];
  program: ResearchBlock[];
  base: Record<string, string>;
}

interface UnivMajorItem {
  majorId: string;
  majorName: string;
  majorTag: string;
  year: string;
}

interface UnivMajorGroup {
  className: string;
  majorList: UnivMajorItem[];
}

interface UnivPostgraduateItem {
  year: number | null;
  exemption: number | null;
}

const schoolId = ref<CommonType.IdType | null>(props.schoolId ?? null);
const showJsonPreview = ref(true);
const loadingAll = ref(false);
const saving = ref({
  satisfaction: false,
  scholarship: false,
  specialMajor: false,
  employmentReport: false,
  photo: false,
  accommodation: false,
  subjectReviews: false,
  research: false,
  univMajors: false,
  univPostgraduate: false
});

const satisfactionDetails = ref<SatisfactionDetail[]>([]);
const scholarshipList = ref<ScholarshipItem[]>([]);
const specialMajorList = ref<SpecialMajorGroup[]>([]);
const employmentReportList = ref<EmploymentReportItem[]>([]);
const photoJson = ref<PhotoJson>({
  staticUrl: '',
  scenerys: []
});
const accommodationList = ref<AccommodationItem[]>([]);
const subjectReviewsList = ref<SubjectReviewGroup[]>([]);
const researchJson = ref<ResearchJson>({
  features: { name: '', value: '' },
  teacher: { name: '', value: '' },
  laboratory: [],
  subject: [],
  program: [],
  base: {}
});
const researchBasePairs = ref<Array<{ key: string; value: string }>>([]);
const univMajorsList = ref<UnivMajorGroup[]>([]);
const univPostgraduateList = ref<UnivPostgraduateItem[]>([]);

function ensureSchoolId() {
  if (schoolId.value === null || schoolId.value === undefined || schoolId.value === '') {
    window.$message?.warning('请先输入学校ID');
    return false;
  }
  return true;
}

function normalizeId(value: CommonType.IdType | null | undefined): CommonType.IdType | null {
  if (value === null || value === undefined || value === '') return null;

  if (typeof value === 'number') return value;

  const trimmed = String(value).trim();
  if (!trimmed) return null;

  const asNumber = Number(trimmed);
  if (Number.isSafeInteger(asNumber) && String(asNumber) === trimmed) {
    return asNumber;
  }

  return trimmed;
}

function createSatisfactionDetail(): SatisfactionDetail {
  return { name: '', votes: '', score: '', s1: '', s2: '', s3: '', s4: '', s5: '' };
}

function createScholarshipItem(): ScholarshipItem {
  return { name: '', value: '' };
}

function createSpecialMajorGroup(): SpecialMajorGroup {
  return { type: '', majorList: [] };
}

function createSpecialMajorItem(): SpecialMajorItem {
  return { majorId: '', majorName: '' };
}

function createEmploymentReportItem(): EmploymentReportItem {
  return { reportName: '', onlinePreview: '', reportUrl: '' };
}

function createPhotoSceneryItem(): PhotoSceneryItem {
  return { cover: '', size: null, scenery: '' };
}

function createAccommodationItem(): AccommodationItem {
  return { name: '', value: '' };
}

function createSubjectReviewGroup(): SubjectReviewGroup {
  return { type: '', subList: [] };
}

function createSubjectReviewItem(): SubjectReviewItem {
  return { subjectName: '', assessLevel: '', assessGrade: '' };
}

function createResearchBlock(): ResearchBlock {
  return { name: '', value: '' };
}

function createUnivMajorGroup(): UnivMajorGroup {
  return { className: '', majorList: [] };
}

function createUnivMajorItem(): UnivMajorItem {
  return { majorId: '', majorName: '', majorTag: '', year: '' };
}

function createUnivPostgraduateItem(): UnivPostgraduateItem {
  return { year: null, exemption: null };
}

function syncResearchBasePairs(base: Record<string, string>) {
  researchBasePairs.value = Object.entries(base || {}).map(([key, value]) => ({ key, value }));
}

function buildResearchBaseObject() {
  const base: Record<string, string> = {};
  researchBasePairs.value.forEach(item => {
    const key = item.key.trim();
    if (!key) return;
    base[key] = item.value ?? '';
  });
  return base;
}

const satisfactionPreview = computed(() =>
  JSON.stringify({ satisfactionJson: { details: satisfactionDetails.value } }, null, 2)
);
const scholarshipPreview = computed(
  () =>
    JSON.stringify(
      {
        scholarshipJson: scholarshipList.value.map(item => ({
          [item.name || '奖学金']: item.value
        }))
      },
      null,
      2
    )
);
const specialMajorPreview = computed(() => JSON.stringify({ specialMajorJson: specialMajorList.value }, null, 2));
const employmentReportPreview = computed(() =>
  JSON.stringify({ employmentReportJson: employmentReportList.value }, null, 2)
);
const photoPreview = computed(() => JSON.stringify({ photoJson: photoJson.value }, null, 2));
const accommodationPreview = computed(() => JSON.stringify({ accommodationJson: accommodationList.value }, null, 2));
const subjectReviewsPreview = computed(() => JSON.stringify({ subjectReviewsJson: subjectReviewsList.value }, null, 2));
const researchPreview = computed(() =>
  JSON.stringify({ researchJson: { ...researchJson.value, base: buildResearchBaseObject() } }, null, 2)
);
const univMajorsPreview = computed(() => JSON.stringify({ univMajorsJson: univMajorsList.value }, null, 2));
const univPostgraduatePreview = computed(() =>
  JSON.stringify({ univPostgraduateJson: univPostgraduateList.value }, null, 2)
);

async function loadAll() {
  if (!ensureSchoolId()) return;
  loadingAll.value = true;
  const allRes = await fetchGetSchoolDetailJsonAll(schoolId.value as CommonType.IdType);

  if (!allRes.error) {
    const data = allRes.data;

    satisfactionDetails.value = (data?.satisfactionJson as { details?: SatisfactionDetail[] } | undefined)?.details ?? [];
    const rawScholarship = (data?.scholarshipJson as Array<Record<string, string>> | undefined) ?? [];
    scholarshipList.value = rawScholarship.map(item => {
      const [name = '', value = ''] = Object.entries(item)[0] ?? [];
      return { name, value };
    });
    specialMajorList.value = (data?.specialMajorJson as SpecialMajorGroup[] | undefined) ?? [];
    employmentReportList.value = (data?.employmentReportJson as EmploymentReportItem[] | undefined) ?? [];
    photoJson.value =
      (data?.photoJson as PhotoJson | undefined) ??
      ({
        staticUrl: '',
        scenerys: []
      } as PhotoJson);
    accommodationList.value = (data?.accommodationJson as AccommodationItem[] | undefined) ?? [];
    subjectReviewsList.value = (data?.subjectReviewsJson as SubjectReviewGroup[] | undefined) ?? [];

    const research = data?.researchJson as ResearchJson | undefined;
    if (research) {
      researchJson.value = {
        features: research.features ?? { name: '', value: '' },
        teacher: research.teacher ?? { name: '', value: '' },
        laboratory: research.laboratory ?? [],
        subject: research.subject ?? [],
        program: research.program ?? [],
        base: research.base ?? {}
      };
      syncResearchBasePairs(researchJson.value.base);
    }

    univMajorsList.value = (data?.univMajorsJson as UnivMajorGroup[] | undefined) ?? [];
    univPostgraduateList.value = (data?.univPostgraduateJson as UnivPostgraduateItem[] | undefined) ?? [];
  }

  loadingAll.value = false;
}

async function saveSatisfaction() {
  if (!ensureSchoolId()) return;
  saving.value.satisfaction = true;
  const { error } = await fetchUpdateSchoolDetailSatisfactionJson({
    schoolId: schoolId.value as CommonType.IdType,
    satisfactionJson: { details: satisfactionDetails.value }
  });
  saving.value.satisfaction = false;
  if (!error) window.$message?.success('满意度明细已保存');
}

async function saveScholarship() {
  if (!ensureSchoolId()) return;
  saving.value.scholarship = true;
  const { error } = await fetchUpdateSchoolDetailScholarshipJson({
    schoolId: schoolId.value as CommonType.IdType,
    scholarshipJson: scholarshipList.value.map(item => ({
      [item.name || '奖学金']: item.value
    }))
  });
  saving.value.scholarship = false;
  if (!error) window.$message?.success('奖学金已保存');
}

async function saveSpecialMajor() {
  if (!ensureSchoolId()) return;
  saving.value.specialMajor = true;
  const { error } = await fetchUpdateSchoolDetailSpecialMajorJson({
    schoolId: schoolId.value as CommonType.IdType,
    specialMajorJson: specialMajorList.value
  });
  saving.value.specialMajor = false;
  if (!error) window.$message?.success('特色专业已保存');
}

async function saveEmploymentReport() {
  if (!ensureSchoolId()) return;
  saving.value.employmentReport = true;
  const { error } = await fetchUpdateSchoolDetailEmploymentReportJson({
    schoolId: schoolId.value as CommonType.IdType,
    employmentReportJson: employmentReportList.value
  });
  saving.value.employmentReport = false;
  if (!error) window.$message?.success('就业报告已保存');
}

async function savePhoto() {
  if (!ensureSchoolId()) return;
  saving.value.photo = true;
  const { error } = await fetchUpdateSchoolDetailPhotoJson({
    schoolId: schoolId.value as CommonType.IdType,
    photoJson: photoJson.value
  });
  saving.value.photo = false;
  if (!error) window.$message?.success('学校图片已保存');
}

async function saveAccommodation() {
  if (!ensureSchoolId()) return;
  saving.value.accommodation = true;
  const { error } = await fetchUpdateSchoolDetailAccommodationJson({
    schoolId: schoolId.value as CommonType.IdType,
    accommodationJson: accommodationList.value
  });
  saving.value.accommodation = false;
  if (!error) window.$message?.success('建筑配套已保存');
}

async function saveSubjectReviews() {
  if (!ensureSchoolId()) return;
  saving.value.subjectReviews = true;
  const { error } = await fetchUpdateSchoolDetailSubjectReviewsJson({
    schoolId: schoolId.value as CommonType.IdType,
    subjectReviewsJson: subjectReviewsList.value
  });
  saving.value.subjectReviews = false;
  if (!error) window.$message?.success('学科评估已保存');
}

async function saveResearch() {
  if (!ensureSchoolId()) return;
  saving.value.research = true;
  const { error } = await fetchUpdateSchoolDetailResearchJson({
    schoolId: schoolId.value as CommonType.IdType,
    researchJson: {
      ...researchJson.value,
      base: buildResearchBaseObject()
    }
  });
  saving.value.research = false;
  if (!error) window.$message?.success('科研信息已保存');
}

async function saveUnivMajors() {
  if (!ensureSchoolId()) return;
  saving.value.univMajors = true;
  const { error } = await fetchUpdateSchoolDetailUnivMajorsJson({
    schoolId: schoolId.value as CommonType.IdType,
    univMajorsJson: univMajorsList.value
  });
  saving.value.univMajors = false;
  if (!error) window.$message?.success('专业标签已保存');
}

async function saveUnivPostgraduate() {
  if (!ensureSchoolId()) return;
  saving.value.univPostgraduate = true;
  const { error } = await fetchUpdateSchoolDetailUnivPostgraduateJson({
    schoolId: schoolId.value as CommonType.IdType,
    univPostgraduateJson: univPostgraduateList.value
  });
  saving.value.univPostgraduate = false;
  if (!error) window.$message?.success('保研信息已保存');
}

watch(
  researchBasePairs,
  () => {
    researchJson.value.base = buildResearchBaseObject();
  },
  { deep: true }
);

watch(
  () => props.schoolId,
  value => {
    if (value === null || value === undefined) return;
    schoolId.value = normalizeId(value);
    if (props.inModal) {
      loadAll();
    }
  },
  { immediate: true }
);
</script>

<template>
  <div class="flex-col-stretch gap-16px">
    <NCard title="院校详情 JSONB 管理" :bordered="false" size="small" class="card-wrapper">
      <NForm label-placement="left" label-width="80">
        <NGrid :cols="24" :x-gap="16">
          <NGi :span="8">
            <NFormItem label="学校ID">
              <NInput v-model:value="schoolId" class="w-full" clearable placeholder="请输入学校ID" />
            </NFormItem>
          </NGi>
          <NGi :span="8">
            <NFormItem label="JSON预览">
              <NSwitch v-model:value="showJsonPreview" />
            </NFormItem>
          </NGi>
          <NGi :span="8" class="flex items-center">
            <NButton type="primary" :loading="loadingAll" @click="loadAll">加载全部</NButton>
          </NGi>
        </NGrid>
      </NForm>
    </NCard>

    <NCard :bordered="false" size="small" class="card-wrapper">
      <NTabs type="line" animated>
        <NTabPane name="satisfaction" tab="满意度明细">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="satisfactionDetails" :on-create="createSatisfactionDetail">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <div class="flex flex-wrap gap-12px">
                    <NInput v-model:value="value.name" placeholder="维度名称" class="w-140px" />
                    <NInput v-model:value="value.votes" placeholder="投票数" class="w-110px" />
                    <NInput v-model:value="value.score" placeholder="评分" class="w-90px" />
                    <NInput v-model:value="value.s1" placeholder="S1" class="w-70px" />
                    <NInput v-model:value="value.s2" placeholder="S2" class="w-70px" />
                    <NInput v-model:value="value.s3" placeholder="S3" class="w-70px" />
                    <NInput v-model:value="value.s4" placeholder="S4" class="w-70px" />
                    <NInput v-model:value="value.s5" placeholder="S5" class="w-70px" />
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NSpace>
              <NButton type="primary" :loading="saving.satisfaction" @click="saveSatisfaction">保存</NButton>
            </NSpace>
            <NCode v-if="showJsonPreview" :code="satisfactionPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="scholarship" tab="奖学金">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="scholarshipList" :on-create="createScholarshipItem">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                  <div class="flex flex-wrap gap-12px w-full">
                    <NInput v-model:value="value.name" placeholder="类型（奖学金/助学金等）" class="w-200px" />
                    <NInput v-model:value="value.value" placeholder="说明" class="flex-1" />
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.scholarship" @click="saveScholarship">保存</NButton>
            <NCode v-if="showJsonPreview" :code="scholarshipPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="special-major" tab="特色专业">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="specialMajorList" :on-create="createSpecialMajorGroup">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <div class="flex-col-stretch gap-12px">
                    <div class="flex items-center gap-8px">
                      <span class="text-13px text-gray-500">特色类型</span>
                      <NInput v-model:value="value.type" placeholder="类型（国家级/省级等）" class="w-200px" />
                    </div>
                    <NDynamicInput v-model:value="value.majorList" :on-create="createSpecialMajorItem">
                      <template #default="{ value: major }">
                        <div class="rounded-6px border border-#e5e7eb bg-white p-10px">
                          <div class="flex flex-wrap items-center gap-12px">
                            <span class="text-13px text-gray-500">专业ID</span>
                            <NInput v-model:value="major.majorId" placeholder="专业ID" class="w-120px" />
                            <span class="text-13px text-gray-500">专业名称</span>
                            <NInput v-model:value="major.majorName" placeholder="专业名称" class="w-220px" />
                          </div>
                        </div>
                      </template>
                    </NDynamicInput>
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.specialMajor" @click="saveSpecialMajor">保存</NButton>
            <NCode v-if="showJsonPreview" :code="specialMajorPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="employment-report" tab="就业报告">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="employmentReportList" :on-create="createEmploymentReportItem">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <div class="flex-col-stretch gap-12px">
                    <div class="flex items-center gap-8px">
                      <span class="text-13px text-gray-500">报告名称</span>
                      <NInput v-model:value="value.reportName" placeholder="报告名称" class="flex-1" />
                    </div>
                    <div class="flex items-center gap-8px">
                      <span class="text-13px text-gray-500">预览地址</span>
                      <NInput v-model:value="value.onlinePreview" placeholder="预览地址" class="flex-1" />
                    </div>
                    <div class="flex items-center gap-8px">
                      <span class="text-13px text-gray-500">附件地址</span>
                      <NInput v-model:value="value.reportUrl" placeholder="附件上传地址" class="flex-1" />
                    </div>
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.employmentReport" @click="saveEmploymentReport">保存</NButton>
            <NCode v-if="showJsonPreview" :code="employmentReportPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="photo" tab="学校图片">
          <NSpace vertical :size="12">
            <NForm label-placement="left" label-width="90">
              <NFormItem label="静态域名">
                <NInput v-model:value="photoJson.staticUrl" placeholder="https://..." />
              </NFormItem>
            </NForm>
            <NDynamicInput v-model:value="photoJson.scenerys" :on-create="createPhotoSceneryItem">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <NGrid :cols="24" :x-gap="10">
                    <NGi :span="4">
                      <NFormItem label="分类名称" label-width="80">
                        <NInput v-model:value="value.scenery" placeholder="分类名称" class="w-160px" />
                      </NFormItem>
                    </NGi>
                    <NGi :span="16">
                      <NFormItem label="图片地址" label-width="80">
                        <NInput v-model:value="value.cover" placeholder="图片地址" />
                      </NFormItem>
                    </NGi>
                     <NGi :span="2">
                      <NFormItem label="排序" label-width="80">
                        <NInputNumber v-model:value="value.size" placeholder="排序" class="w-120px" />
                      </NFormItem>
                    </NGi>
                  </NGrid>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.photo" @click="savePhoto">保存</NButton>
            <NCode v-if="showJsonPreview" :code="photoPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="accommodation" tab="建筑配套">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="accommodationList" :on-create="createAccommodationItem">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                  <div class="flex flex-wrap gap-12px">
                    <NInput v-model:value="value.name" placeholder="名称" class="w-160px" />
                    <NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.accommodation" @click="saveAccommodation">保存</NButton>
            <NCode v-if="showJsonPreview" :code="accommodationPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="subject-reviews" tab="学科评估">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="subjectReviewsList" :on-create="createSubjectReviewGroup">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <div class="flex-col-stretch gap-12px">
                    <NInput v-model:value="value.type" placeholder="评估轮次" class="w-200px" />
                    <NDynamicInput v-model:value="value.subList" :on-create="createSubjectReviewItem">
                      <template #default="{ value: item }">
                        <div class="rounded-6px border border-#e5e7eb bg-white p-10px">
                          <div class="flex flex-wrap gap-12px">
                            <NInput v-model:value="item.subjectName" placeholder="学科名称" class="w-200px" />
                            <NInput v-model:value="item.assessLevel" placeholder="评级" class="w-120px" />
                            <NInput v-model:value="item.assessGrade" placeholder="档位" class="w-120px" />
                          </div>
                        </div>
                      </template>
                    </NDynamicInput>
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.subjectReviews" @click="saveSubjectReviews">保存</NButton>
            <NCode v-if="showJsonPreview" :code="subjectReviewsPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="research" tab="科研信息">
          <NSpace vertical :size="12">
            <NCard title="区块信息" size="small" :bordered="true">
              <NGrid :cols="24" :x-gap="16">
                <NGi :span="12">
                  <NFormItem label="特色名称" label-width="80">
                    <NInput v-model:value="researchJson.features.name" placeholder="名称" />
                  </NFormItem>
                </NGi>
                <NGi :span="12">
                  <NFormItem label="特色内容" label-width="80">
                    <NInput v-model:value="researchJson.features.value" placeholder="内容" />
                  </NFormItem>
                </NGi>
                <NGi :span="12">
                  <NFormItem label="师资名称" label-width="80">
                    <NInput v-model:value="researchJson.teacher.name" placeholder="名称" />
                  </NFormItem>
                </NGi>
                <NGi :span="12">
                  <NFormItem label="师资内容" label-width="80">
                    <NInput v-model:value="researchJson.teacher.value" placeholder="内容" />
                  </NFormItem>
                </NGi>
              </NGrid>
            </NCard>

            <NCard title="实验室" size="small" :bordered="true">
              <NDynamicInput v-model:value="researchJson.laboratory" :on-create="createResearchBlock">
                <template #default="{ value }">
                  <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                    <div class="flex flex-wrap gap-12px">
                      <NInput v-model:value="value.name" placeholder="名称" class="w-200px" />
                      <NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
                    </div>
                  </div>
                </template>
              </NDynamicInput>
            </NCard>

            <NCard title="学科" size="small" :bordered="true">
              <NDynamicInput v-model:value="researchJson.subject" :on-create="createResearchBlock">
                <template #default="{ value }">
                  <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                    <div class="flex flex-wrap gap-12px">
                      <NInput v-model:value="value.name" placeholder="名称" class="w-240px" />
                      <NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
                    </div>
                  </div>
                </template>
              </NDynamicInput>
            </NCard>

            <NCard title="项目/培养" size="small" :bordered="true">
              <NDynamicInput v-model:value="researchJson.program" :on-create="createResearchBlock">
                <template #default="{ value }">
                  <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                    <div class="flex flex-wrap gap-12px">
                      <NInput v-model:value="value.name" placeholder="名称" class="w-240px" />
                      <NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
                    </div>
                  </div>
                </template>
              </NDynamicInput>
            </NCard>

            <NCard title="基础数据" size="small" :bordered="true">
              <NDynamicInput v-model:value="researchBasePairs" :on-create="() => ({ key: '', value: '' })">
                <template #default="{ value }">
                  <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
                    <div class="flex flex-wrap gap-12px">
                      <NInput v-model:value="value.key" placeholder="字段" class="w-200px" />
                      <NInput v-model:value="value.value" placeholder="数值" class="flex-1" />
                    </div>
                  </div>
                </template>
              </NDynamicInput>
            </NCard>

            <NButton type="primary" :loading="saving.research" @click="saveResearch">保存</NButton>
            <NCode v-if="showJsonPreview" :code="researchPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="univ-majors" tab="专业标签">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="univMajorsList" :on-create="createUnivMajorGroup">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <div class="flex-col-stretch gap-12px">
                    <NInput v-model:value="value.className" placeholder="分类名称" class="w-200px" />
                    <NDynamicInput v-model:value="value.majorList" :on-create="createUnivMajorItem">
                      <template #default="{ value: item }">
                        <div class="rounded-6px border border-#e5e7eb bg-white p-10px">
                          <div class="flex flex-wrap gap-12px">
                            <NInput v-model:value="item.majorId" placeholder="专业ID" class="w-120px" />
                            <NInput v-model:value="item.majorName" placeholder="专业名称" class="w-200px" />
                            <NInput v-model:value="item.majorTag" placeholder="标签" class="w-120px" />
                            <NInput v-model:value="item.year" placeholder="学制" class="w-120px" />
                          </div>
                        </div>
                      </template>
                    </NDynamicInput>
                  </div>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.univMajors" @click="saveUnivMajors">保存</NButton>
            <NCode v-if="showJsonPreview" :code="univMajorsPreview" language="json" />
          </NSpace>
        </NTabPane>

        <NTabPane name="univ-postgraduate" tab="保研信息">
          <NSpace vertical :size="12">
            <NDynamicInput v-model:value="univPostgraduateList" :on-create="createUnivPostgraduateItem">
              <template #default="{ value }">
                <div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
                  <NGrid :cols="24" :x-gap="16">
                    <NGi :span="12">
                      <NFormItem label="年份" label-width="80">
                        <NInputNumber v-model:value="value.year" placeholder="年份" class="w-140px" />
                      </NFormItem>
                    </NGi>
                    <NGi :span="12">
                      <NFormItem label="保研率" label-width="80">
                        <NInputNumber v-model:value="value.exemption" placeholder="保研率" class="w-160px" />
                      </NFormItem>
                    </NGi>
                  </NGrid>
                </div>
              </template>
            </NDynamicInput>
            <NButton type="primary" :loading="saving.univPostgraduate" @click="saveUnivPostgraduate">保存</NButton>
            <NCode v-if="showJsonPreview" :code="univPostgraduatePreview" language="json" />
          </NSpace>
        </NTabPane>
      </NTabs>
    </NCard>
  </div>
</template>

<style scoped>
.w-70px {
  width: 70px;
}

.w-90px {
  width: 90px;
}

.w-110px {
  width: 110px;
}

.w-120px {
  width: 120px;
}

.w-140px {
  width: 140px;
}

.w-160px {
  width: 160px;
}

.w-200px {
  width: 200px;
}

.w-220px {
  width: 220px;
}

.w-240px {
  width: 240px;
}

.w-320px {
  width: 320px;
}
</style>
