Procházet zdrojové kódy

重构卡管理页面

shmily1213 před 1 měsícem
rodič
revize
e1f9009564
34 změnil soubory, kde provedl 2490 přidání a 1079 odebrání
  1. 4 2
      back-ui/src/api/dz/cards.js
  2. 9 0
      back-ui/src/api/dz/control.js
  3. 1 1
      back-ui/src/assets/icons/svg/download.svg
  4. 22 0
      back-ui/src/assets/styles/element-ui.scss
  5. 8 0
      back-ui/src/assets/styles/index.scss
  6. 18 0
      back-ui/src/assets/styles/tailwind.css
  7. 10 0
      back-ui/src/common/enum.js
  8. 38 0
      back-ui/src/components/IeAgentSelect/index.vue
  9. 15 0
      back-ui/src/components/IeInstitutionSelect/index.vue
  10. 94 0
      back-ui/src/components/IeModal/index.vue
  11. 27 0
      back-ui/src/components/IeSelect/index.vue
  12. 2 0
      back-ui/src/directive/index.js
  13. 111 0
      back-ui/src/directive/numberDirective.js
  14. 175 0
      back-ui/src/hooks/useSchool.js
  15. 1 0
      back-ui/src/layout/components/AppMain.vue
  16. 0 0
      back-ui/src/views/dz/cards copy/components/ApplyCardDialog.vue
  17. 0 0
      back-ui/src/views/dz/cards copy/components/AssignCardDialog.vue
  18. 0 0
      back-ui/src/views/dz/cards copy/components/AssociateCampusDialog.vue
  19. 0 0
      back-ui/src/views/dz/cards copy/components/CardGenerationDialog.vue
  20. 0 0
      back-ui/src/views/dz/cards copy/components/CloseCardDialog.vue
  21. 0 0
      back-ui/src/views/dz/cards copy/components/EditStudentDialog.vue
  22. 0 0
      back-ui/src/views/dz/cards copy/components/PaymentDialog.vue
  23. 0 0
      back-ui/src/views/dz/cards copy/components/RefundDialog.vue
  24. 0 0
      back-ui/src/views/dz/cards copy/components/ReopenCardDialog.vue
  25. 0 0
      back-ui/src/views/dz/cards copy/config/form.js
  26. 0 0
      back-ui/src/views/dz/cards copy/config/table.js
  27. 950 0
      back-ui/src/views/dz/cards copy/index.vue
  28. 159 0
      back-ui/src/views/dz/cards/components/AssignDialog.vue
  29. 143 0
      back-ui/src/views/dz/cards/components/CardTable.vue
  30. 105 0
      back-ui/src/views/dz/cards/components/CustomButton.vue
  31. 90 0
      back-ui/src/views/dz/cards/components/EditDialog.vue
  32. 118 0
      back-ui/src/views/dz/cards/components/OpenDialog.vue
  33. 262 901
      back-ui/src/views/dz/cards/index.vue
  34. 128 175
      back-ui/src/views/system/dict/data.vue

+ 4 - 2
back-ui/src/api/dz/cards.js

@@ -66,7 +66,8 @@ export function assignCard(
   end,
   location,
   examType,
-  schoolId
+  schoolId,
+  days
 ) {
   return request({
     url: "/dz/cards/assignCard",
@@ -79,6 +80,7 @@ export function assignCard(
       location,
       examType,
       schoolId,
+      days,
     },
   });
 }
@@ -189,7 +191,7 @@ export function requestOpenCard(agentId, province, schoolId, beginCardNo, endCar
     method: "post",
     params: {
       agentId: agentId,
-      location: province,
+      locationId: province,
       schoolId,
       begin: beginCardNo,
       end: endCardNo,

+ 9 - 0
back-ui/src/api/dz/control.js

@@ -1,5 +1,14 @@
 import request from '@/utils/request'
 
+// 查询单招省份列表
+export function getControlProvinces(query) {
+  return request({
+    url: '/dz/control/provinces',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询单招省份状态列表
 export function listControl(query) {
   return request({

+ 1 - 1
back-ui/src/assets/icons/svg/download.svg

@@ -1 +1 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" fill="#333333" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" fill="#333333" p-id="3064"></path></svg>
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" p-id="3064"></path></svg>

+ 22 - 0
back-ui/src/assets/styles/element-ui.scss

@@ -93,4 +93,26 @@
 
 .el-dropdown .el-dropdown-link{
   color: var(--el-color-primary) !important;
+}
+
+
+.el-zoom-in-top-enter-from,
+.el-zoom-in-top-leave-to {
+  opacity: 0 !important;
+  transform: translateY(-8px) !important;
+}
+
+.el-zoom-in-top-enter-to,
+.el-zoom-in-top-leave-from {
+  opacity: 1 !important;
+  transform: translateY(0px) !important;
+}
+
+
+.el-overlay-dialog {
+  display: flex;
+  align-items: center;
+  .el-dialog:not(.is-fullscreen) {
+    margin-top: 0 !important;
+  }
 }

+ 8 - 0
back-ui/src/assets/styles/index.scss

@@ -177,3 +177,11 @@ aside {
     margin-bottom: 10px;
   }
 }
+
+
+.app-page {
+  min-height: 100%;
+  display: flex;
+  flex-direction: column;
+  padding: 16px;
+}

+ 18 - 0
back-ui/src/assets/styles/tailwind.css

@@ -12,4 +12,22 @@
     --color-danger: var(--el-color-danger);
     --color-success: var(--el-color-success);
     --color-info: var(--el-color-info);
+}
+
+@layer utilities {
+    /* 单行省略 */
+    .ellipsis-1 {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    /* 多行省略(2行) */
+    .ellipsis-2 {
+      overflow: hidden;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      line-clamp: 2;
+      -webkit-box-orient: vertical;
+      word-break: break-word;
+    }
 }

+ 10 - 0
back-ui/src/common/enum.js

@@ -0,0 +1,10 @@
+export const CARD_STATUS = [
+  {
+    label: '未激活',
+    value: 0,
+  },
+  {
+    label: '已激活',
+    value: 30,
+  },
+];

+ 38 - 0
back-ui/src/components/IeAgentSelect/index.vue

@@ -0,0 +1,38 @@
+<template>
+  <el-select ref="selectRef" v-bind="$attrs">
+    <el-option v-for="option in dataList" :key="option.agentId" :label="option.name" :value="option.agentId" />
+  </el-select>
+</template>
+<script setup>
+import { getAgentList } from '@/api/dz/cards';
+import { nextTick, onMounted } from 'vue';
+const props = defineProps({
+  deptId: {
+    type: Number,
+    default: null,
+  },
+});
+const selectRef = ref(null);
+const dataList = ref([])
+const loadData = async () => {
+  const { data } = await getAgentList({
+    deptId: props.deptId,
+  })
+  dataList.value = data;
+}
+
+watch(() => props.deptId, (newVal) => {
+  dataList.value = [];
+  const event = new Event('clear');
+  selectRef.value.deleteSelected(event);
+  nextTick(() => {
+    selectRef.value.blur();
+  });
+  loadData();
+});
+
+onMounted(() => {
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 15 - 0
back-ui/src/components/IeInstitutionSelect/index.vue

@@ -0,0 +1,15 @@
+<template>
+  <el-select v-bind="$attrs">
+    <el-option v-for="option in dataList" :key="option.deptId" :label="option.deptName" :value="option.deptId" />
+  </el-select>
+</template>
+<script setup>
+import { listDept } from '@/api/system/dept';
+const dataList = ref([])
+const loadData = async () => {
+  const { data } = await listDept({})
+  dataList.value = data.filter(item => item.deptId !== 100);
+}
+loadData();
+</script>
+<style lang="scss" scoped></style>

+ 94 - 0
back-ui/src/components/IeModal/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <el-dialog :title="title" v-model="visible" :width="width" append-to-body :destroy-on-close="true"
+    :close-on-click-modal="false" custom-class="ie-modal" @close="handleBeforeClose">
+    <slot v-if="show"></slot>
+    <template #footer>
+      <div>
+        <el-button @click="handleCancel" v-if="showCancel">{{ cancelText }}</el-button>
+        <el-button type="primary" @click="handleConfirm" :loading="loadingModel" v-if="showConfirm">
+          {{ confirmText }}
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+<script setup>
+import { ref, watch } from 'vue';
+
+const loadingModel = defineModel('loading')
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  width: {
+    type: String,
+    default: '500px'
+  },
+  showCancel: {
+    type: Boolean,
+    default: true
+  },
+  showConfirm: {
+    type: Boolean,
+    default: true
+  },
+  cancelText: {
+    type: String,
+    default: '取消'
+  },
+  confirmText: {
+    type: String,
+    default: '确定'
+  }
+})
+const visible = defineModel('visible')
+const show = ref(false);
+const emit = defineEmits(['cancel', 'confirm'])
+
+const handleBeforeClose = () => {
+  emit('beforeClose')
+}
+const handleCancel = () => {
+  emit('cancel')
+  close();
+}
+const handleConfirm = () => {
+  emit('confirm')
+}
+watch(visible, (newVal) => {
+  if (newVal) {
+    show.value = newVal;
+  } else {
+    setTimeout(() => {
+      show.value = false;
+    }, 300);
+  }
+})
+const open = () => {
+  visible.value = true
+}
+const close = () => {
+  visible.value = false
+}
+const showLoading = () => {
+  loadingModel.value = true
+}
+const hideLoading = () => {
+  loadingModel.value = false
+}
+
+defineExpose({
+  open,
+  close,
+  showLoading,
+  hideLoading
+})
+</script>
+<style lang="scss" scoped>
+.ie-modal {
+  .el-dialog:not(.is-fullscreen) {
+    margin-top: 0 !important;
+  }
+}
+</style>

+ 27 - 0
back-ui/src/components/IeSelect/index.vue

@@ -0,0 +1,27 @@
+<template>
+  <el-select v-bind="$attrs" @change="handleChange">
+    <el-option v-for="option in options" :key="option[valueKey]" :label="option[labelKey]" :value="option[valueKey]" />
+  </el-select>
+</template>
+<script setup>
+const props = defineProps({
+  options: {
+    type: Array,
+    default: () => [],
+  },
+  labelKey: {
+    type: String,
+    default: 'label',
+  },
+  valueKey: {
+    type: String,
+    default: 'value',
+  },
+})
+const emit = defineEmits(['change']);
+const handleChange = (val) => {
+  const fullItem = props.options.find(option => option[props.valueKey] === val);
+  emit('change', val, fullItem);
+}
+</script>
+<style lang="scss" scoped></style>

+ 2 - 0
back-ui/src/directive/index.js

@@ -1,9 +1,11 @@
 import hasRole from './permission/hasRole'
 import hasPermi from './permission/hasPermi'
 import copyText from './common/copyText'
+import { numberDirective } from './numberDirective'
 
 export default function directive(app){
   app.directive('hasRole', hasRole)
   app.directive('hasPermi', hasPermi)
   app.directive('copyText', copyText)
+  app.directive('number', numberDirective)
 }

+ 111 - 0
back-ui/src/directive/numberDirective.js

@@ -0,0 +1,111 @@
+// directives/numberDirective.js
+export const numberDirective = {
+    mounted(el, binding) {
+        const { value = {} } = binding;
+        const { allowDecimal = false, allowNegative = false } = value;
+        let isComposing = false;
+
+        // 查找 el-input 下的 input 元素
+        const inputElement = el.querySelector('input');
+
+        const handleInput = (e) => {
+            if (isComposing) {
+                return;
+            }
+            let input = inputElement.value;
+            
+            // 新处理逻辑:分步骤处理
+            let processed = input.replace(/[^-\d.]/g, ''); // 第一步:过滤非法字符
+            
+            // 处理负号
+            if (allowNegative) {
+                const hasNegative = processed.includes('-');
+                processed = processed.replace(/-/g, '');
+                if (hasNegative) {
+                    processed = '-' + processed;
+                }
+            } else {
+                processed = processed.replace(/-/g, '');
+            }
+
+            // 修改后的前导零处理逻辑
+            if (processed !== '0') {
+                // 优化后的正则表达式处理带负号的情况
+                processed = processed.replace(/^(-?)0+(?=[1-9])/, '$1');
+                // 处理纯零的情况(如-000 → -0)
+                if (/^-?0+$/.test(processed)) {
+                    processed = processed.startsWith('-') ? '-0' : '0';
+                }
+            }
+
+            // 处理小数点
+            if (allowDecimal) {
+                const parts = processed.split('.');
+                if (parts.length > 1) {
+                    // 保留第一个小数点,移除后续的小数点
+                    processed = parts[0] + '.' + parts.slice(1).join('').replace(/\./g, '');
+                }
+                // 处理以小数点开头的情况
+                if (processed.startsWith('.') && processed.length > 1) {
+                    processed = '0' + processed;
+                }
+                // 处理负号后直接跟小数点的情况
+                if (processed.startsWith('-.')) {
+                    processed = '-0.' + processed.slice(2);
+                }
+            } else {
+                processed = processed.replace(/\./g, '');
+            }
+
+            // 最终验证
+            const finalReg = allowNegative 
+                ? allowDecimal 
+                    ? /^-?\d*\.?\d*$/
+                    : /^-?\d*$/
+                : allowDecimal
+                    ? /^\d*\.?\d*$/
+                    : /^\d*$/;
+
+            if (!finalReg.test(processed)) {
+                processed = '';
+            }
+
+            // 特殊允许状态
+            if (allowNegative && processed === '-') {
+                inputElement.value = '-';
+                return;
+            }
+            if (allowDecimal && processed.endsWith('.') && !processed.startsWith('.') && processed !== '-.') {
+                inputElement.value = processed;
+                return;
+            }
+
+            // 设置最终值
+            inputElement.value = processed || '';
+        };
+
+        const handleCompositionStart = () => {
+            isComposing = true;
+        };
+
+        const handleCompositionEnd = () => {
+            isComposing = false;
+            handleInput({ target: inputElement });
+        };
+
+        inputElement.addEventListener('input', handleInput);
+        inputElement.addEventListener('compositionstart', handleCompositionStart);
+        inputElement.addEventListener('compositionend', handleCompositionEnd);
+    },
+    unmounted(el) {
+        const inputElement = el.querySelector('input');
+
+        const handleInput = (e) => {};
+        const handleCompositionStart = () => {};
+        const handleCompositionEnd = () => {};
+
+        inputElement.removeEventListener('input', handleInput);
+        inputElement.removeEventListener('compositionstart', handleCompositionStart);
+        inputElement.removeEventListener('compositionend', handleCompositionEnd);
+    }
+}

+ 175 - 0
back-ui/src/hooks/useSchool.js

@@ -0,0 +1,175 @@
+import { onMounted, ref, watch } from "vue";
+import { cascaderAreaList } from "@/api/system/area";
+import { listSchool } from "@/api/dz/school";
+import { listClasses } from "@/api/dz/classes";
+import { listCampus } from "@/api/dz/campus";
+import { getExamTypes } from "@/api/dz/cards";
+
+const defaultOptions = {
+  autoLoad: true,
+  loadCampus: false,
+  loadClass: false,
+  loadExamType: false,
+};
+const useSchool = (options = defaultOptions) => {
+  const { autoLoad, loadCampus, loadClass, loadExamType } = options;
+
+  const areaList = ref([]);
+  const selectedArea = ref([]);
+
+  const schoolList = ref([]);
+  const selectedSchool = ref();
+
+  const classList = ref([]);
+  const selectedClass = ref();
+
+  const campusList = ref([]);
+  const selectedCampus = ref();
+
+  const campusClassList = ref([]);
+  const selectedCampusClass = ref();
+
+  const examTypeList = ref([]);
+  const selectedExamType = ref('all');
+
+
+  const getExamTypeList = async () => {
+    examTypeList.value = [];
+    const res = await getExamTypes((getProvinceName() || '').replace('省', ''));
+    console.log(res)
+    examTypeList.value = res.data;
+  };
+
+  const getProvincesList = async () => {
+    const rows = await cascaderAreaList();
+    areaList.value = rows;
+  };
+
+  const getSchoolList = async () => {
+    const areaId = Array.isArray(selectedArea.value)
+      ? selectedArea.value?.[selectedArea.value.length - 1]
+      : selectedArea.value;
+    const res = await listSchool({
+      pro: areaId,
+    });
+    schoolList.value = res.rows;
+    console.log(res);
+  };
+
+  const getClassList = async () => {
+    const res = await listClasses();
+    classList.value = res.data;
+  };
+
+  const getCampusList = async () => {
+    const areaId = Array.isArray(selectedArea.value)
+      ? selectedArea.value?.[selectedArea.value.length - 1]
+      : selectedArea.value;
+    const res = await listCampus({
+      pro: areaId,
+    });
+    campusList.value = res.rows;
+  };
+
+  const getCampusClassList = async () => {
+    const res = await listClasses({
+      campusId: selectedCampus.value,
+    });
+    campusClassList.value = res.rows;
+  };
+
+  watch(
+    () => selectedArea.value,
+    (newVal) => {
+      selectedSchool.value = null;
+      selectedCampus.value = null;
+      if (selectedExamType !== 'all') {
+        selectedExamType.value = null;
+      }
+
+      if (newVal || (Array.isArray(newVal) && newVal.length > 0)) {
+        getSchoolList();
+        if (loadCampus) {
+          getCampusList();
+        }
+        if (loadExamType) {
+          getExamTypeList();
+        }
+      }
+    }
+  );
+
+  watch(
+    () => selectedSchool.value,
+    (newVal) => {
+      selectedClass.value = null;
+      if (newVal !== null) {
+        if (loadClass) {
+          getClassList();
+        }
+      }
+    }
+  );
+
+  watch(
+    () => selectedCampus.value,
+    (newVal) => {
+      selectedCampusClass.value = null;
+      if (newVal !== null) {
+        getCampusClassList();
+      }
+    }
+  );
+
+  const getProvinceName = () => {
+    const areaId = Array.isArray(selectedArea.value)
+      ? selectedArea.value?.[selectedArea.value.length - 1]
+      : selectedArea.value;
+    const area = areaList.value.find((item) => item.areaId === areaId);
+    return area?.areaName;
+  };
+
+  const init = () => {
+    getProvincesList();
+  }
+
+  const reset = () => {
+    selectedArea.value = [];
+    selectedSchool.value = null;
+    selectedClass.value = null;
+    selectedCampus.value = null;
+    selectedCampusClass.value = null;
+    selectedExamType.value = 'all';
+    console.log('end', selectedArea)
+  }
+
+  onMounted(() => {
+    if (autoLoad) {
+      getProvincesList();
+    }
+  });
+
+  return {
+    init,
+    reset,
+
+    areaList,
+    selectedArea,
+
+    schoolList,
+    selectedSchool,
+
+    classList,
+    selectedClass,
+
+    campusList,
+    selectedCampus,
+
+    campusClassList,
+    selectedCampusClass,
+
+    examTypeList,
+    selectedExamType
+  };
+};
+export default useSchool;

+ 1 - 0
back-ui/src/layout/components/AppMain.vue

@@ -39,6 +39,7 @@ function addIframe() {
 .app-main {
   /* 50= navbar  50  */
   min-height: calc(100vh - 50px);
+  height: 1px;
   width: 100%;
   position: relative;
   overflow: hidden;

+ 0 - 0
back-ui/src/views/dz/cards/components/ApplyCardDialog.vue → back-ui/src/views/dz/cards copy/components/ApplyCardDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/AssignCardDialog.vue → back-ui/src/views/dz/cards copy/components/AssignCardDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/AssociateCampusDialog.vue → back-ui/src/views/dz/cards copy/components/AssociateCampusDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/CardGenerationDialog.vue → back-ui/src/views/dz/cards copy/components/CardGenerationDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/CloseCardDialog.vue → back-ui/src/views/dz/cards copy/components/CloseCardDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/EditStudentDialog.vue → back-ui/src/views/dz/cards copy/components/EditStudentDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/PaymentDialog.vue → back-ui/src/views/dz/cards copy/components/PaymentDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/RefundDialog.vue → back-ui/src/views/dz/cards copy/components/RefundDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/components/ReopenCardDialog.vue → back-ui/src/views/dz/cards copy/components/ReopenCardDialog.vue


+ 0 - 0
back-ui/src/views/dz/cards/config/form.js → back-ui/src/views/dz/cards copy/config/form.js


+ 0 - 0
back-ui/src/views/dz/cards/config/table.js → back-ui/src/views/dz/cards copy/config/table.js


+ 950 - 0
back-ui/src/views/dz/cards copy/index.vue

@@ -0,0 +1,950 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单组件 -->
+    <SearchForm v-show="showSearch" :config="searchConfig" :model-value="queryParams"
+      @update:model-value="handleSearchFormUpdate" :on-search="handleQuery" :on-reset="resetQuery" :show-expand="true"
+      :expand-count="6" label-width="68px" />
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['dz:cards:add']">制卡</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" plain @click="handleAssignCard" v-hasPermi="['dz:cards:assign']"
+          style="border-color: #67c23a; color: #67c23a; font-weight: 500">
+          <svg-icon icon-class="peoples" class="mr-1" style="font-size: 16px" />
+          分配卡
+        </el-button>
+      </el-col>
+      <!-- <el-col :span="1.5">
+        <el-button
+                type="success"
+                plain
+                icon="Edit"
+                :disabled="single"
+                @click="handleUpdate"
+                v-hasPermi="['dz:cards:edit']"
+          >修改</el-button
+        >
+      </el-col> -->
+      <el-col :span="1.5">
+        <el-button type="warning" plain :disabled="multiple" @click="handlePayment" v-hasPermi="['dz:cards:pay']"
+          style="border-color: #e6a23c; color: #e6a23c; font-weight: 500">
+          <svg-icon icon-class="money" class="mr-1" style="font-size: 16px" />
+          缴费
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain :disabled="multiple" @click="handleCloseCard" v-hasPermi="['dz:cards:close']"
+          style="border-color: #f56c6c; color: #f56c6c; font-weight: 500">
+          <svg-icon icon-class="lock" class="mr-1" style="font-size: 16px" />
+          关卡
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain :disabled="multiple" @click="handleReopenCard" v-hasPermi="['dz:cards:reopen']"
+          style="border-color: #13c2c2; color: #13c2c2; font-weight: 500">
+          <svg-icon icon-class="enter" class="mr-1" style="font-size: 16px" />
+          重开
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain :disabled="multiple" @click="handleRefund" v-hasPermi="['dz:cards:refund']"
+          style="border-color: #ff4d4f; color: #ff4d4f; font-weight: 500">
+          <svg-icon icon-class="money" class="mr-1" style="font-size: 16px" />
+          退费
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain @click="handleAssociateCampus" v-hasPermi="['dz:cards:associateCampus']"
+          style="border-color: #1890ff; color: #1890ff; font-weight: 500">
+          <svg-icon icon-class="link" class="mr-1" style="font-size: 16px" />
+          关联校区
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" plain @click="handleApplyCard" v-hasPermi="['dz:cards:openFinished']"
+          style="border-color: #52c41a; color: #52c41a; font-weight: 500">
+          <svg-icon icon-class="edit" class="mr-1" style="font-size: 16px" />
+          直接开卡
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete"
+          v-hasPermi="['dz:cards:remove']" style="border-color: #ff4d4f; color: #ff4d4f; font-weight: 500">
+          删除
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="Download" @click="handleExport" v-hasPermi="['dz:cards:export']"
+          style="border-color: #722ed1; color: #722ed1; font-weight: 500">导出</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- Table组件 -->
+    <Table :data="cardsList" :columns="tableColumns" :actions="tableActions" :loading="loading" :total="total"
+      :queryParams="queryParams" v-bind="tableProps" @action="handleTableAction"
+      @selection-change="handleSelectionChange" @getList="getList">
+      <!-- 序号插槽 -->
+      <template #index="{ row, $index }">
+        {{ getRowIndex($index) }}
+      </template>
+
+      <!-- 姓名-手机插槽 -->
+      <template #studentInfo="{ row }">
+        <div class="student-info">
+          <div class="student-name">{{ row.nickName || "-" }}</div>
+          <div class="student-phone">{{ row.phonenumber || "-" }}</div>
+        </div>
+      </template>
+    </Table>
+
+    <!-- 弹窗组件 -->
+    <CardGenerationDialog v-model="cardGenerationOpen" :institution-list="institutionList" :school-list="schoolList"
+      :card-type-options="card_type" @success="handleDialogSuccess" />
+
+    <AssignCardDialog v-model="assignCardOpen" :selected-cards="selectedRows" :school-list="schoolList"
+      :card-type-options="card_type" @success="handleDialogSuccess" />
+
+    <EditStudentDialog v-model="open" :card-id="currentCardId" :class-list="classList" @success="handleDialogSuccess" />
+
+    <PaymentDialog v-model="paymentOpen" :card-no="currentCardNo" :card-ids="selectedCardIds"
+      :selected-cards="selectedRows" @success="handlePaymentSuccess" />
+
+    <CloseCardDialog v-model="closeCardOpen" :card-no="currentCloseCardNo" :card-ids="selectedCardIds"
+      @success="handleCloseCardSuccess" />
+
+    <ReopenCardDialog v-model="reopenCardOpen" :card-no="currentReopenCardNo" :card-ids="selectedCardIds"
+      @success="handleReopenCardSuccess" />
+
+    <RefundDialog v-model="refundOpen" :card-no="currentRefundCardNo" @confirm="handleRefundConfirm" />
+
+    <AssociateCampusDialog v-model="associateCampusOpen" :selected-cards="selectedRows"
+      @success="handleAssociateCampusSuccess" />
+
+    <ApplyCardDialog v-model="applyCardOpen" :selected-cards="selectedRows" @success="handleApplyCardSuccess" />
+  </div>
+</template>
+
+<style scoped>
+.student-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.student-name {
+  font-weight: 500;
+  margin-bottom: 2px;
+}
+
+.student-phone {
+  font-size: 12px;
+  color: #909399;
+}
+</style>
+
+<script setup name="Cards">
+import {
+  listCards,
+  getCards,
+  delCards,
+  addCards,
+  updateCards,
+} from "@/api/dz/cards";
+import SearchForm from "@/components/SearchForm/index.vue";
+import Table from "@/components/Table/index.vue";
+import CardGenerationDialog from "./components/CardGenerationDialog.vue";
+import AssignCardDialog from "./components/AssignCardDialog.vue";
+import EditStudentDialog from "./components/EditStudentDialog.vue";
+import PaymentDialog from "./components/PaymentDialog.vue";
+import CloseCardDialog from "./components/CloseCardDialog.vue";
+import ReopenCardDialog from "./components/ReopenCardDialog.vue";
+import RefundDialog from "./components/RefundDialog.vue";
+import AssociateCampusDialog from "./components/AssociateCampusDialog.vue";
+import ApplyCardDialog from "./components/ApplyCardDialog.vue";
+import formInfo from "./config/form.js";
+import tableConfig from "./config/table.js";
+// import { listUniversity } from "@/api/dz/school";
+import { assignCard, issueCard, getCampusSchoolList } from "@/api/dz/cards";
+import { listDept } from "@/api/system/dept";
+import { listAgent } from "@/api/dz/agent";
+import { getClassesBySchoolId } from "@/api/dz/classes";
+
+const { proxy } = getCurrentInstance();
+const {
+  exam_type,
+  card_status,
+  card_distribute_status,
+  card_time_status,
+  bool_values,
+  card_pay_status,
+  card_type,
+} = proxy.useDict(
+  "exam_type",
+  "card_status",
+  "card_distribute_status",
+  "card_time_status",
+  "bool_values",
+  "card_pay_status",
+  "card_type"
+);
+
+const cardsList = ref([]);
+const schoolList = ref([]);
+const campusList = ref([]); // 校区列表
+const classList = ref([]); // 班级列表
+const open = ref(false);
+const cardGenerationOpen = ref(false); // 制卡对话框
+const assignCardOpen = ref(false); // 分配卡对话框
+const paymentOpen = ref(false); // 缴费对话框
+const closeCardOpen = ref(false); // 关卡对话框
+const reopenCardOpen = ref(false); // 重开对话框
+const refundOpen = ref(false); // 退费对话框
+const associateCampusOpen = ref(false); // 关联校区对话框
+const applyCardOpen = ref(false); // 直接开卡对话框
+const currentCardNo = ref([]); // 当前缴费的卡号(支持数组)
+const currentCloseCardNo = ref([]); // 当前关卡的卡号(支持数组)
+const currentReopenCardNo = ref([]); // 当前重开的卡号(支持数组)
+const currentRefundCardNo = ref(""); // 当前退费的卡号
+const agentList = ref([]); // 代理商列表
+const institutionList = ref([]); // 机构列表
+const selectedRows = ref([]); // 选中的行
+const selectedCardIds = computed(() =>
+  selectedRows.value.map((row) => row.cardId)
+); // 选中的卡ID列表
+const currentCardId = ref(null); // 当前编辑的卡片ID
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+
+// 搜索配置
+const searchConfig = computed(() => {
+  const config = [...formInfo.info];
+
+  // 动态设置选项数据
+  config.forEach((item) => {
+    switch (item.name) {
+      case "type":
+        item.option = card_type.value || [];
+        break;
+      case "status":
+        item.option = card_status.value || [];
+        break;
+      case "distributeStatus":
+        item.option = card_distribute_status.value || [];
+        break;
+      // 移除registerSchoolId,实体类中不存在
+      case "timeStatus":
+        item.option = card_time_status.value || [];
+        break;
+      case "payStatus":
+        item.option = card_pay_status.value || [];
+        break;
+      case "isSettlement":
+        item.option = bool_values.value || [];
+        break;
+      case "assignSchoolId":
+        item.option = schoolList.value || [];
+        break;
+      case "registerSchoolId":
+        item.option = schoolList.value || [];
+        break;
+      case "campusId":
+        item.option = campusList.value || [];
+        break;
+      case "examType":
+        item.option = exam_type.value || [];
+        break;
+    }
+  });
+
+  return config;
+});
+
+// 表格配置
+const tableColumns = computed(() => {
+  const columns = [...tableConfig.columns];
+
+  // 动态设置字典选项
+  columns.forEach((column) => {
+    switch (column.prop) {
+      case "type":
+        column.options = card_type.value || [];
+        break;
+      case "status":
+        column.options = card_status.value || [];
+        break;
+      case "distributeStatus":
+        column.options = card_distribute_status.value || [];
+        break;
+      case "timeStatus":
+        column.options = card_time_status.value || [];
+        break;
+      case "payStatus":
+        column.options = card_pay_status.value || [];
+        break;
+      case "isSettlement":
+        column.options = bool_values.value || [];
+        break;
+      // 移除studentCategory,实体类中不存在
+      case "registerSchoolId":
+      case "assignSchoolId":
+        column.options = schoolList.value || [];
+        break;
+      case "studentCategory":
+      case "classId":
+      case "schoolClassId":
+      case "campusId":
+        column.options = []; // 需要从相应API获取
+        break;
+    }
+  });
+
+  return columns;
+});
+
+const tableActions = computed(() => tableConfig.actions);
+const tableProps = computed(() => tableConfig.tableProps);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    // 搜索表单字段
+    areaIds: [],
+    assignSchoolId: null,
+    registerSchoolId: null,
+    campusId: null,
+    agentId: null,
+    cardNo: null,
+    password: null,
+    cardNoRange: null,
+    begin: null,
+    end: null,
+    type: null,
+    studentCategory: null, // 分配考生类型
+    studentName: null, // 学生姓名
+    phone: null, // 手机号
+    registerSchoolId: null, // 注册学校
+    schoolClassId: null, // 校区班级
+    distributeStatus: null,
+    status: null,
+    timeStatus: null,
+    isSettlement: null,
+
+    // 其他字段(用于数据传输)
+    cardNo: null,
+    password: null,
+    payStatus: null,
+    isSettlement: null,
+    deptId: null,
+    leftAgentId: null,
+    schoolId: null,
+    classId: null,
+    year: null,
+    endYear: null,
+    openId: null,
+    distributeTime: null,
+    outDate: null,
+    openTime: null,
+    payTime: null,
+    activeTime: null,
+    settlementTime: null,
+    refundTime: null,
+    closeTime: null,
+    createTime: null,
+    updateTime: null,
+  },
+  rules: {
+    schoolId: [{ required: true, message: "学校不能为空", trigger: "change" }],
+    classId: [{ required: true, message: "班级不能为空", trigger: "change" }],
+    nickName: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
+    phone: [
+      { required: true, message: "手机号不能为空", trigger: "blur" },
+      {
+        pattern: /^1[3-9]\d{9}$/,
+        message: "请输入正确的手机号",
+        trigger: "blur",
+      },
+    ],
+    chineseMathEnglish: [
+      { required: true, message: "语数英成绩不能为空", trigger: "blur" },
+    ],
+    vocationalSkills: [
+      { required: true, message: "职业技能成绩不能为空", trigger: "blur" },
+    ],
+  },
+});
+
+const { queryParams, form } = toRefs(data);
+
+// 监听queryParams的原始变化
+watch(
+  () => data.queryParams,
+  (newParams) => {
+    if (newParams.areaIds && newParams.areaIds.length > 0) {
+      getSchoolList();
+    }
+  },
+  { immediate: true, deep: true }
+);
+
+/** 获取行序号 */
+function getRowIndex(index) {
+  const pageNum = queryParams.value?.pageNum || 1;
+  const pageSize = queryParams.value?.pageSize || 10;
+  return (pageNum - 1) * pageSize + index + 1;
+}
+
+/** 查询学习卡列表 */
+function getList() {
+  loading.value = true;
+  listCards(queryParams.value).then((response) => {
+    cardsList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+
+/** 获取学校列表 */
+function getSchoolList() {
+  // 如果没有选择省市区,则不获取学校列表
+  if (!queryParams.value.areaIds || queryParams.value.areaIds.length === 0) {
+    schoolList.value = [];
+    return;
+  }
+
+  // 构造location参数:如果有省市区选择,取最后一个值(区)作为location
+  const location =
+    queryParams.value.areaIds[queryParams.value.areaIds.length - 1];
+
+  // 传递pageNum和pageSize参数
+  const requestParams = {
+    location,
+    pageNum: 1,
+    pageSize: 9999, // 获取所有学校选项
+  };
+
+  // listUniversity(requestParams)
+  //   .then((response) => {
+  //     // 根据API返回数据结构处理
+  //     let schoolData = [];
+  //     if (response.data) {
+  //       schoolData = Array.isArray(response.data)
+  //         ? response.data
+  //         : response.data.rows || response.data.list || [];
+  //     } else if (response.rows) {
+  //       schoolData = response.rows;
+  //     } else if (response.list) {
+  //       schoolData = response.list;
+  //     } else if (Array.isArray(response)) {
+  //       schoolData = response;
+  //     }
+  //
+  //     // 确保数据格式符合配置要求
+  //     schoolData = schoolData.map((item) => {
+  //       // 如果API返回的是 {id, name, ...} 格式,直接使用
+  //       if (item.id && item.name) {
+  //         return item;
+  //       }
+  //       // 如果是其他格式,需要转换
+  //       return {
+  //         id: item.id || item.value || item.schoolId,
+  //         name: item.name || item.label || item.schoolName || item.title,
+  //       };
+  //     });
+  //
+  //     schoolList.value = schoolData;
+  //   })
+  //   .catch((error) => {
+  //     console.error("获取学校列表失败:", error);
+  //     schoolList.value = [];
+  //   });
+}
+
+/** 获取校区列表 */
+function getCampusListData() {
+  getCampusSchoolList({
+    campus: true,
+    pageNum: 1,
+    pageSize: 9999, // 获取所有校区
+  })
+    .then((response) => {
+      if (response.code === 200) {
+        // 处理API返回的数据结构
+        let campusData = [];
+        if (response.data) {
+          campusData = Array.isArray(response.data) ? response.data : [];
+        } else if (response.rows) {
+          campusData = response.rows;
+        } else if (response.list) {
+          campusData = response.list;
+        }
+
+        // 确保数据格式符合配置要求
+        campusData = campusData.map((item) => {
+          // 如果API返回的是 {id, name, ...} 格式,直接使用
+          if (item.id && item.name) {
+            return item;
+          }
+          // 如果是其他格式,需要转换
+          return {
+            id: item.id || item.value || item.campusId,
+            name: item.name || item.label || item.campusName || item.title,
+          };
+        });
+
+        campusList.value = campusData;
+      }
+    })
+    .catch((error) => {
+      console.error("获取校区列表失败:", error);
+      campusList.value = [];
+    });
+}
+
+// 取消按钮
+// 处理弹窗成功事件
+function handleDialogSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList(); // 刷新列表
+}
+
+// 获取机构列表
+async function getInstitutionList() {
+  try {
+    const response = await listDept({});
+    institutionList.value = response.data || response.rows || response || [];
+  } catch (error) {
+    console.error("获取机构列表失败:", error);
+    institutionList.value = [];
+  }
+}
+
+// 获取班级列表
+async function getClassList() {
+  try {
+    // 这里应该调用获取班级列表的API
+    // 暂时使用模拟数据
+    classList.value = [
+      { id: 1, name: "2501" },
+      { id: 2, name: "2502" },
+      { id: 3, name: "2503" },
+    ];
+  } catch (error) {
+    console.error("获取班级列表失败:", error);
+    classList.value = [];
+  }
+}
+
+// 获取代理商列表
+async function getAgentList() {
+  try {
+    const response = await listAgent({});
+    const agentData = response.data || response.rows || response || [];
+
+    // 确保数据格式符合前端期望
+    agentList.value = agentData.map((item) => ({
+      id: item.agentId,
+      name: item.name,
+    }));
+  } catch (error) {
+    console.error("获取代理商列表失败:", error);
+    agentList.value = [];
+  }
+}
+
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = {
+    cardId: null,
+    cardNo: null,
+    password: null,
+    type: null,
+    status: null,
+    distributeStatus: null,
+    timeStatus: null,
+    payStatus: null,
+    isSettlement: null,
+    deptId: null,
+    agentId: null,
+    leftAgentId: null,
+    campusId: null,
+    assignSchoolId: null,
+    schoolId: null,
+    classesId: null,
+    year: null,
+    endYear: null,
+    openId: null,
+    remark: null,
+    distributeTime: null,
+    outDate: null,
+    openTime: null,
+    payTime: null,
+    activeTime: null,
+    settlementTime: null,
+    refundTime: null,
+    closeTime: null,
+    createTime: null,
+    updateTime: null,
+  };
+  proxy.resetForm("cardsRef");
+}
+
+/** 搜索按钮操作 */
+/** 表格操作处理 */
+function handleTableAction(action, row) {
+  switch (action.key) {
+    case "edit":
+      handleUpdate(row); // 修改时获取行数据
+      break;
+    case "delete":
+      handleDelete(row);
+      break;
+    default:
+      console.warn("Unknown action:", action.key);
+  }
+}
+
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+
+/** 处理SearchForm组件的update:model-value事件 */
+function handleSearchFormUpdate(newData) {
+  Object.assign(data.queryParams, newData);
+}
+
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  console.log("Selection changed:", selection);
+  selectedRows.value = selection;
+  ids.value = selection.map((item) => item.cardId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 制卡按钮操作 */
+function handleAdd() {
+  cardGenerationOpen.value = true;
+  getInstitutionList(); // 获取机构列表
+}
+
+/** 缴费按钮操作 */
+function handlePayment() {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("请选择要缴费的卡片");
+    return;
+  }
+
+  // 获取所有选中卡片的卡号数组
+  const cardNos = selectedRows.value.map(
+    (card) => card.cardNo || card.id || "未知"
+  );
+  currentCardNo.value = cardNos;
+  paymentOpen.value = true;
+}
+
+// 处理缴费成功
+function handlePaymentSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList(); // 刷新列表
+}
+
+// 处理缴费确认(保留兼容性)
+function handlePaymentConfirm(cardNo) {
+  proxy.$modal.msgSuccess(`缴费成功!卡号:${cardNo}`);
+  getList(); // 刷新列表
+}
+
+/** 关卡按钮操作 */
+function handleCloseCard() {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("请选择要关卡的卡片");
+    return;
+  }
+
+  // 获取所有选中卡片的卡号数组
+  const cardNos = selectedRows.value.map(
+    (card) => card.cardNo || card.id || "未知"
+  );
+  currentCloseCardNo.value = cardNos;
+  closeCardOpen.value = true;
+}
+
+// 处理关卡成功
+function handleCloseCardSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList(); // 刷新列表
+}
+
+// 处理关卡确认(保留兼容性)
+function handleCloseCardConfirm(cardNo) {
+  proxy.$modal.msgSuccess(`关卡成功!卡号:${cardNo}`);
+  getList(); // 刷新列表
+}
+
+/** 重开按钮操作 */
+function handleReopenCard() {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("请选择要重开的卡片");
+    return;
+  }
+
+  // 获取所有选中卡片的卡号数组
+  const cardNos = selectedRows.value.map(
+    (card) => card.cardNo || card.id || "未知"
+  );
+  currentReopenCardNo.value = cardNos;
+  reopenCardOpen.value = true;
+}
+
+// 处理重开成功
+function handleReopenCardSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList(); // 刷新列表
+}
+
+// 处理重开确认(保留兼容性)
+function handleReopenCardConfirm(cardNo) {
+  proxy.$modal.msgSuccess(`重开成功!卡号:${cardNo}`);
+  getList(); // 刷新列表
+}
+
+/** 退费按钮操作 */
+function handleRefund() {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("请选择要退费的卡片");
+    return;
+  }
+
+  // 获取第一个选中卡片的卡号
+  const firstCard = selectedRows.value[0];
+  currentRefundCardNo.value = firstCard.cardNo || firstCard.id || "未知";
+  refundOpen.value = true;
+}
+
+// 处理退费确认
+function handleRefundConfirm(cardNo) {
+  proxy.$modal.msgSuccess(`退费成功!卡号:${cardNo}`);
+  getList(); // 刷新列表
+}
+
+/** 关联校区按钮操作 */
+function handleAssociateCampus() {
+  if (selectedRows.value.length === 0) {
+    // proxy.$modal.msgWarning("请选择要关联校区的卡片");
+    // return;
+  }
+  associateCampusOpen.value = true;
+}
+
+// 处理关联校区成功
+function handleAssociateCampusSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList();
+}
+
+// 处理关联校区确认
+function handleAssociateCampusConfirm(data) {
+  proxy.$modal.msgSuccess(
+    `关联校区成功!卡号段:${data.beginCardNo}-${data.endCardNo}`
+  );
+  getList(); // 刷新列表
+}
+
+// 获取代理商列表
+async function getAgentListData() {
+  try {
+    const response = await getAgentList({ pageNum: 1, pageSize: 1000 });
+    if (response.code === 200) {
+      agentList.value = response.data || [];
+    }
+  } catch (error) {
+    console.error("获取代理商列表失败:", error);
+    agentList.value = [];
+  }
+}
+
+/** 直接开卡按钮操作 */
+function handleApplyCard() {
+  if (selectedRows.value.length === 0) {
+    // proxy.$modal.msgWarning("请选择要申请开卡的卡片");
+    // return;
+  }
+  applyCardOpen.value = true;
+}
+
+// 处理直接开卡成功
+function handleApplyCardSuccess(message) {
+  proxy.$modal.msgSuccess(message);
+  getList();
+}
+
+// 处理直接开卡确认
+function handleApplyCardConfirm(data) {
+  proxy.$modal.msgSuccess(
+    `直接开卡成功!卡号段:${data.beginCardNo}-${data.endCardNo}`
+  );
+  getList(); // 刷新列表
+}
+
+/** 分配卡按钮操作 */
+function handleAssignCard() {
+  console.log("分配卡按钮点击,当前选中的行:", selectedRows.value);
+  assignCardOpen.value = true;
+  getInstitutionList(); // 获取机构列表
+  getAgentList(); // 获取代理商列表
+}
+
+/** 修改按钮操作 */
+async function handleUpdate(row) {
+  reset();
+  const _cardId = row.cardId || ids.value;
+  currentCardId.value = _cardId;
+
+  try {
+    // 获取学习卡详细信息
+    const response = await getCards(_cardId);
+    if (response.code === 200) {
+      const cardData = response.data;
+
+      // 将后端数据对应到表单字段
+      form.value = {
+        cardId: cardData.cardId,
+        cardNo: cardData.cardNo,
+        password: cardData.password,
+        type: cardData.type,
+        status: cardData.status,
+        distributeStatus: cardData.distributeStatus,
+        timeStatus: cardData.timeStatus,
+        payStatus: cardData.payStatus,
+        isSettlement: cardData.isSettlement,
+        deptId: cardData.deptId,
+        agentId: cardData.agentId,
+        leftAgentId: cardData.leftAgentId,
+        campusId: cardData.campusId,
+        assignSchoolId: cardData.assignSchoolId,
+        schoolId: cardData.schoolId,
+        classId: cardData.classId,
+        year: cardData.year,
+        endYear: cardData.endYear,
+        openId: cardData.openId,
+        nickName: cardData.nickName,
+        mobile: cardData.mobile,
+        chineseMathEnglish: cardData.chineseMathEnglish,
+        vocationalSkills: cardData.vocationalSkills,
+        studentCategory: cardData.studentCategory,
+        assignExamType: cardData.assignExamType,
+        areaIds: cardData.areaIds,
+        remark: cardData.remark,
+      };
+
+      // 获取班级列表
+      await getClassList();
+
+      // 打开编辑弹窗
+      open.value = true;
+    } else {
+      proxy.$modal.msgError("获取学习卡信息失败");
+    }
+  } catch (error) {
+    console.error("获取学习卡详细信息失败:", error);
+    proxy.$modal.msgError("获取学习卡信息失败");
+  }
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.$refs["cardsRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.cardId != null) {
+        updateCards(form.value).then((response) => {
+          proxy.$modal.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addCards(form.value).then((response) => {
+          proxy.$modal.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const _cardIds = row.cardId || ids.value;
+  const cardIdsArray = Array.isArray(_cardIds) ? _cardIds : [_cardIds];
+
+  if (cardIdsArray.length === 0) {
+    proxy.$modal.msgWarning("请选择要删除的数据");
+    return;
+  }
+
+  const message =
+    cardIdsArray.length === 1
+      ? `是否确认删除学习卡编号为"${cardIdsArray[0]}"的数据项?`
+      : `是否确认删除选中的${cardIdsArray.length}条学习卡数据?`;
+
+  proxy.$modal
+    .confirm(message)
+    .then(function () {
+      return delCards(cardIdsArray);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("删除成功");
+    })
+    .catch(() => { });
+}
+
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download(
+    "dz/cards/export",
+    {
+      ...queryParams.value,
+    },
+    `cards_${new Date().getTime()}.xlsx`
+  );
+}
+
+getList();
+getCampusListData(); // 获取校区列表
+
+// 监听地址选择变化,自动获取学校列表
+watch(
+  () => queryParams.value.areaIds,
+  (newAreaIds) => {
+    if (newAreaIds && newAreaIds.length > 0) {
+      getSchoolList();
+    } else {
+      schoolList.value = [];
+    }
+  },
+  { immediate: true, deep: true }
+);
+</script>

+ 159 - 0
back-ui/src/views/dz/cards/components/AssignDialog.vue

@@ -0,0 +1,159 @@
+<template>
+  <IeModal title="分配卡" confirmText="确认分配" ref="modalRef" width="500px" @confirm="handleConfirm">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="平台机构" prop="deptId" required>
+        <ie-institution-select v-model="form.deptId" class="w-[350px]!" />
+      </el-form-item>
+      <el-form-item label="卡类型" prop="cardType">
+        <ie-select v-model="form.cardType" :options="card_type" class="w-[350px]!" clearable />
+      </el-form-item>
+      <el-form-item label="卡号段" prop="cardNoRange" required>
+        <div class="flex items-center gap-x-3">
+          <el-input v-model="form.begin" type="text" maxlength="8" v-number class="w-[160px]!" placeholder="请输入开始卡号" />
+          <span class="text-gray-500">-</span>
+          <el-input v-model="form.end" type="text" maxlength="8" v-number class="w-[160px]!" placeholder="请输入结束卡号" />
+        </div>
+      </el-form-item>
+      <el-form-item label="代理商" prop="agentId" required>
+        <ie-agent-select v-model="form.agentId" :deptId="form.deptId" class="w-[350px]!" filterable />
+      </el-form-item>
+      <el-form-item label="省份" prop="provinceId">
+        <ie-select v-model="selectedArea" :options="areaList" label-key="areaName" value-key="areaId"
+          class="w-[350px]!" />
+      </el-form-item>
+      <el-form-item label="学校" prop="schoolId">
+        <ie-select v-model="selectedSchool" :options="schoolList" label-key="name" value-key="id" filterable
+          class="w-[350px]!" />
+      </el-form-item>
+      <el-form-item label="考生类型" prop="assignExamType">
+        <ie-select v-model="selectedExamType" :options="examTypeOptions" class="w-[350px]!" />
+      </el-form-item>
+      <el-form-item v-if="form.cardType === '9'" label="有效期" prop="days">
+        <el-input-number v-model="form.days" :min="1" :max="10000" :step="1" :precision="0" class="w-full!">
+          <template #suffix>天</template>
+        </el-input-number>
+      </el-form-item>
+    </el-form>
+  </IeModal>
+</template>
+<script setup>
+import useSchool from '@/hooks/useSchool';
+import IeModal from '@/components/IeModal/index.vue';
+import IeSelect from '@/components/IeSelect/index.vue';
+import IeAgentSelect from '@/components/IeAgentSelect/index.vue';
+import IeInstitutionSelect from '@/components/IeInstitutionSelect/index.vue';
+import { assignCard } from '@/api/dz/cards';
+import { getCurrentInstance } from 'vue';
+
+
+const { proxy } = getCurrentInstance();
+const {
+  card_type,
+  exam_type,
+} = proxy.useDict("card_type", "exam_type");
+const {
+  areaList,
+  selectedArea,
+  schoolList,
+  selectedSchool,
+  examTypeList,
+  selectedExamType
+} = useSchool({ loadExamType: true });
+
+const modalRef = ref(null);
+const formRef = ref(null);
+const defaultForm = {
+  deptId: null,
+  agentId: null,
+  cardType: '',
+  count: 1,
+  assignExamType: 'all',
+  days: 1,
+};
+const examTypeOptions = computed(() => {
+  return [{
+    label: '全部',
+    value: 'all',
+  }, ...examTypeList.value.map(item => ({
+    label: item.dictLabel,
+    value: item.dictValue,
+  }))];
+});
+const form = ref({ ...defaultForm })
+// 自定义验证
+const validateCardNoRange = (rule, value, callback) => {
+  if (!form.value.begin || !form.value.end) {
+    callback(new Error('请输入卡号段'))
+  } else if (Number(form.value.begin) > Number(form.value.end)) {
+    callback(new Error('开始卡号不能大于结束卡号'))
+  } else {
+    callback()
+  }
+}
+
+watch([() => form.value.begin, () => form.value.end], () => {
+  form.value.cardNoRange = `${form.value.begin}-${form.value.end}`;
+})
+watch(() => selectedArea.value, (val) => {
+  form.value.provinceId = val;
+})
+watch(() => selectedSchool.value, (val) => {
+  form.value.schoolId = val;
+})
+watch(() => selectedExamType.value, (val) => {
+  form.value.assignExamType = val;
+})
+const rules = ref({
+  deptId: [
+    { required: true, message: '请选择平台机构', trigger: 'change' },
+  ],
+  cardType: [
+    { required: true, message: '请选择卡类型', trigger: 'change' },
+  ],
+  cardNoRange: [
+    { validator: validateCardNoRange, trigger: ['blur', 'change'] },
+  ],
+  agentId: [
+    { required: true, message: '请选择代理商', trigger: 'change' },
+  ],
+  // provinceId: [
+  //   { required: true, message: '请选择省份', trigger: 'change' },
+  // ],
+  // schoolId: [
+  //   { required: true, message: '请选择学校', trigger: 'change' },
+  // ],
+  // assignExamType: [
+  //   { required: true, message: '请选择考生类型', trigger: 'change' },
+  // ],
+  days: [
+    { required: true, message: '请输入有效期', trigger: 'change' },
+  ],
+})
+
+const open = () => {
+  form.value = { ...defaultForm };
+  modalRef.value.open()
+}
+const close = () => {
+  modalRef.value.close()
+}
+const emit = defineEmits(['refresh'])
+const handleConfirm = () => {
+  formRef.value.validate(async (valid) => {
+    if (valid) {
+      const { cardType, agentId, begin, end, provinceId, assignExamType, schoolId, days } = form.value;
+      await assignCard(cardType, agentId, begin, end, provinceId, assignExamType, schoolId, days);
+      proxy.$modal.msgSuccess('分配卡成功')
+      close();
+      setTimeout(() => {
+        emit('refresh')
+      }, 300);
+    }
+  })
+}
+defineExpose({
+  open,
+  close
+})
+</script>
+<style lang="scss" scoped></style>

+ 143 - 0
back-ui/src/views/dz/cards/components/CardTable.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="mt-4 flex-1 min-h-1 relative">
+    <div class="absolute top-0 left-0 w-full h-full">
+      <el-table :data="data" class="w-full" height="100%" @selection-change="handleSelectionChange" v-loading="loading">
+        <el-table-column label="" type="selection" min-width="60" fixed="left"></el-table-column>
+        <el-table-column label="卡号" prop="cardNo" align="center" min-width="100" fixed="left"></el-table-column>
+        <el-table-column label="密码" prop="password" align="center" min-width="100"></el-table-column>
+        <el-table-column label="姓名—手机" prop="studentInfo" align="center" min-width="150">
+          <template #default="scope">
+            <div class="text-[13px]">{{ scope.row.nickName || '-' }}</div>
+            <div v-if="scope.row.phonenumber" class="text-[12px] text-gray-600">{{ scope.row.phonenumber }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="机构" prop="deptName" align="center" min-width="120">
+          <template #default="scope">
+            <div>{{ scope.row.deptName || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="省份" prop="location" align="center" min-width="120">
+          <template #default="scope">
+            <div>{{ scope.row.location || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="代理商" prop="agentName" align="center" min-width="120">
+          <template #default="scope">
+            <div>{{ scope.row.agentName || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="单招年份" prop="endYear" align="center" min-width="120">
+          <template #default="scope">
+            <div>{{ scope.row.endYear || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="注册学校" prop="schoolName" align="center" min-width="120" >
+          <template #default="scope">
+            <div class="text-[13px] ellipsis-2">{{ scope.row.schoolName || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="注册班级" prop="className" align="center" min-width="110">
+          <template #default="scope">
+            <div class="text-[13px] ellipsis-2">{{ scope.row.className || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="培训学校" prop="campusName" align="center" min-width="100">
+          <template #default="scope">
+            <div class="text-[13px] ellipsis-2">{{ scope.row.campusName || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="培训班级" prop="campusClassName" align="center" min-width="120">
+          <template #default="scope">
+            <div class="text-[13px] ellipsis-2">{{ scope.row.campusClassName || '-' }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="考生类型(注册)" prop="assignExamType" align="center" min-width="140">
+          <template #default="scope">
+            <dict-tag v-if="assignExamType" :options="exam_type" :value="scope.row.assignExamType" />
+            <span>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="卡类型" prop="type" align="center" min-width="120">
+          <template #default="scope">
+            <dict-tag :options="card_type" :value="scope.row.type" />
+          </template>
+        </el-table-column>
+        <el-table-column label="分配状态" prop="distributeStatus" align="center" min-width="100">
+          <template #default="scope">
+            <dict-tag :options="card_distribute_status" :value="scope.row.distributeStatus" />
+          </template>
+        </el-table-column>
+        <el-table-column label="使用状态" prop="status" align="center" min-width="100">
+          <template #default="scope">
+            <dict-tag :options="card_status" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="过期状态" prop="timeStatus" align="center" min-width="100">
+          <template #default="scope">
+            <dict-tag :options="card_time_status" :value="scope.row.timeStatus" />
+          </template>
+        </el-table-column>
+        <el-table-column label="缴费状态" prop="payStatus" align="center" min-width="100">
+          <template #default="scope">
+            <dict-tag :options="card_pay_status" :value="scope.row.payStatus" />
+          </template>
+        </el-table-column>
+        <el-table-column label="结算状态" prop="isSettlement" align="center" min-width="100">
+          <template #default="scope">
+            <dict-tag :options="bool_values" :value="scope.row.isSettlement" />
+          </template>
+        </el-table-column>
+        <el-table-column label="分配时间" prop="distributeTime" align="center" min-width="120">
+          <template #default="scope">
+            <span>{{ scope.row.distributeTime ? parseTime(scope.row.distributeTime, '{y}-{m}-{d}') : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="激活时间" prop="activeTime" align="center" min-width="120">
+          <template #default="scope">
+            <span>{{ scope.row.activeTime ? parseTime(scope.row.activeTime, '{y}-{m}-{d}') : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="到期时间" prop="outDate" align="center" min-width="120">
+          <template #default="scope">
+            <span>{{ scope.row.outDate ? parseTime(scope.row.outDate, '{y}-{m}-{d}') : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="缴费时间" prop="payTime" align="center" min-width="120">
+          <template #default="scope">
+            <span>{{ scope.row.payTime ? parseTime(scope.row.payTime, '{y}-{m}-{d}') : '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" min-width="110" fixed="right" align="center">
+          <template #default="scope">
+            <el-button type="danger" text icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+<script setup>
+import DictTag from '@/components/DictTag/index.vue';
+import { getCurrentInstance } from 'vue';
+const { proxy } = getCurrentInstance();
+const { card_type, exam_type, card_distribute_status, card_status, bool_values, card_time_status, card_pay_status } = 
+proxy.useDict("card_type", "exam_type", "card_distribute_status", "card_status", "bool_values", "card_time_status", "card_pay_status");
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => [],
+  },
+  loading: {
+    type: Boolean,
+    default: false,
+  },
+});
+const emit = defineEmits(['selectionChange', 'delete']);
+const handleSelectionChange = (selection) => {
+  emit('selectionChange', selection);
+};
+const handleDelete = (row) => {
+  emit('delete', row);
+};
+</script>
+<style lang="scss" scoped></style>

+ 105 - 0
back-ui/src/views/dz/cards/components/CustomButton.vue

@@ -0,0 +1,105 @@
+<template>
+  <el-button
+    :class="['custom-color-button']"
+    :style="buttonStyles"
+    v-bind="$attrs"
+  >
+    <slot></slot>
+  </el-button>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  /**
+   * 按钮主题颜色,如 #0076ff
+   */
+  color: {
+    type: String,
+    default: '#0076ff',
+    validator: (value) => {
+      // 验证是否为有效的颜色值(hex、rgb、rgba)
+      return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value) || 
+             /^rgb/.test(value) || 
+             /^rgba/.test(value)
+    }
+  }
+})
+
+/**
+ * 将颜色转换为 rgba(支持 hex、rgb、rgba)
+ * @param {String} color - 颜色值,如 #0076ff、rgb(0, 118, 255)、rgba(0, 118, 255, 1)
+ * @param {Number} alpha - 透明度,0-1(仅在 color 为 hex 或 rgb 时使用)
+ * @returns {String} rgba 颜色值
+ */
+const colorToRgba = (color, alpha = 1) => {
+  // 如果已经是 rgba 格式,直接返回
+  if (color.startsWith('rgba')) {
+    return color
+  }
+  
+  // 如果是 rgb 格式,转换为 rgba
+  if (color.startsWith('rgb')) {
+    return color.replace('rgb', 'rgba').replace(')', `, ${alpha})`)
+  }
+  
+  // 如果是 hex 格式,转换为 rgba
+  if (color.startsWith('#')) {
+    const cleanHex = color.replace('#', '')
+    
+    // 处理 3 位 hex 颜色
+    const fullHex = cleanHex.length === 3
+      ? cleanHex.split('').map(char => char + char).join('')
+      : cleanHex
+    
+    const r = parseInt(fullHex.substring(0, 2), 16)
+    const g = parseInt(fullHex.substring(2, 4), 16)
+    const b = parseInt(fullHex.substring(4, 6), 16)
+    
+    return `rgba(${r}, ${g}, ${b}, ${alpha})`
+  }
+  
+  // 如果都不匹配,返回原值(可能是 CSS 颜色名称)
+  return color
+}
+
+/**
+ * 计算按钮样式(使用 CSS 变量,便于在 CSS 中使用)
+ */
+const buttonStyles = computed(() => {
+  const baseColor = props.color
+  const lightBgColor = colorToRgba(baseColor, 0.1)
+  
+  return {
+    '--custom-color': baseColor,
+    '--custom-bg': lightBgColor,
+    '--custom-border': baseColor,
+    '--custom-text': baseColor,
+    transition: 'all 0.3s ease',
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.custom-color-button {
+  // 默认状态:边框和文字使用主题色,背景使用浅色
+  color: var(--custom-text) !important;
+  background-color: var(--custom-bg) !important;
+  border-color: var(--custom-border) !important;
+  
+  // 悬浮状态:文字白色,背景纯色
+  &:not(.is-disabled):hover {
+    color: #ffffff !important;
+    background-color: var(--custom-color) !important;
+    border-color: var(--custom-color) !important;
+    opacity: 1 !important;
+  }
+  
+  // 禁用状态
+  &.is-disabled {
+    opacity: 0.6;
+    cursor: not-allowed;
+  }
+}
+</style>

+ 90 - 0
back-ui/src/views/dz/cards/components/EditDialog.vue

@@ -0,0 +1,90 @@
+<template>
+  <IeModal :title="title" :confirmText="confirmButtonText" ref="modalRef" width="500px" @confirm="handleConfirm">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="平台机构" prop="institutionId" :required="true">
+        <ie-institution-select v-model="form.institutionId" />
+      </el-form-item>
+      <el-form-item label="卡类型" prop="cardType">
+        <ie-select v-model="form.cardType" :options="card_type" />
+      </el-form-item>
+      <el-form-item label="卡数量" prop="count">
+        <el-input-number v-model="form.count" :min="1" :max="10000" :step="1" :precision="0" class="w-full!" />
+      </el-form-item>
+    </el-form>
+  </IeModal>
+</template>
+<script setup>
+import IeModal from '@/components/IeModal/index.vue';
+import IeSelect from '@/components/IeSelect/index.vue';
+import IeInstitutionSelect from '@/components/IeInstitutionSelect/index.vue';
+import { issueCard } from '@/api/dz/cards';
+import { getCurrentInstance } from 'vue';
+
+const { proxy } = getCurrentInstance();
+const {
+  card_type,
+} = proxy.useDict("card_type");
+
+const modalRef = ref(null);
+const formRef = ref(null);
+const form = ref({
+  institutionId: null,
+  cardType: null,
+  count: 1,
+})
+const operation = ref('add')
+const rules = ref({
+  institutionId: [{ required: true, message: '请选择平台机构', trigger: 'change' }],
+  cardType: [{ required: true, message: '请选择卡类型', trigger: 'change' }],
+  count: [{ required: true, message: '请输入卡数量', trigger: 'blur' }],
+})
+const title = computed(() => {
+  return operation.value === 'add' ? '制卡' : '修改';
+})
+const confirmButtonText = computed(() => {
+  return operation.value === 'add' ? '确认制卡' : '确认修改';
+})
+
+// 获取卡类型枚举
+const getCardTypeEnum = (typeValue) => {
+  const cardTypeMap = {
+    6: "Platform",
+    2: "Dept",
+    7: "ECard",
+    8: "Test",
+  };
+  return cardTypeMap[typeValue] || "ECard";
+}
+
+const open = (type = 'add', data = { institutionId: null, cardType: null, count: 1 }) => {
+  operation.value = type;
+  form.value = data;
+  modalRef.value.open()
+}
+const close = () => {
+  modalRef.value.close()
+}
+const emit = defineEmits(['refresh'])
+const handleConfirm = () => {
+  formRef.value.validate((valid) => {
+    if (valid) {
+      const { institutionId, cardType, count } = form.value;
+      modalRef.value.showLoading()
+      issueCard(institutionId, getCardTypeEnum(cardType), count).then(res => {
+        proxy.$modal.msgSuccess('制卡成功')
+        close();
+        setTimeout(() => {
+          emit('refresh')
+        }, 300);
+      }).finally(() => {
+        modalRef.value.hideLoading()
+      })
+    }
+  })
+}
+defineExpose({
+  open,
+  close
+})
+</script>
+<style lang="scss" scoped></style>

+ 118 - 0
back-ui/src/views/dz/cards/components/OpenDialog.vue

@@ -0,0 +1,118 @@
+<template>
+  <IeModal title="直接开卡" confirmText="确认开卡" ref="modalRef" width="500px" @confirm="handleConfirm" @beforeClose="close">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="卡号段" prop="cardNoRange" required>
+        <div class="flex items-center gap-x-3">
+          <el-input v-model="form.begin" type="text" maxlength="8" v-number class="w-[160px]!" placeholder="请输入开始卡号" />
+          <span class="text-gray-500">-</span>
+          <el-input v-model="form.end" type="text" maxlength="8" v-number class="w-[160px]!" placeholder="请输入结束卡号" />
+        </div>
+      </el-form-item>
+      <el-form-item label="代理商" prop="agentId" required>
+        <ie-agent-select v-model="form.agentId" class="w-[350px]!" filterable />
+      </el-form-item>
+      <el-form-item label="省份" prop="provinceId" required>
+        <ie-select v-model="selectedArea" :options="areaList" label-key="areaName" value-key="areaId" class="w-[350px]!"
+          clearable />
+      </el-form-item>
+      <el-form-item label="学校" prop="schoolId" required>
+        <ie-select v-model="selectedSchool" :options="schoolList" label-key="name" value-key="id" filterable
+          class="w-[350px]!" />
+      </el-form-item>
+    </el-form>
+  </IeModal>
+</template>
+<script setup>
+import useSchool from '@/hooks/useSchool';
+import IeModal from '@/components/IeModal/index.vue';
+import IeSelect from '@/components/IeSelect/index.vue';
+import IeAgentSelect from '@/components/IeAgentSelect/index.vue';
+import IeInstitutionSelect from '@/components/IeInstitutionSelect/index.vue';
+import { requestOpenCard } from '@/api/dz/cards';
+import { getCurrentInstance } from 'vue';
+
+const { proxy } = getCurrentInstance();
+
+const {
+  init,
+  reset,
+  areaList,
+  selectedArea,
+  schoolList,
+  selectedSchool,
+} = useSchool({ autoLoad: false });
+
+const visible = ref(false);
+const modalRef = ref(null);
+const formRef = ref(null);
+const form = ref({
+  institutionId: null,
+  cardType: null,
+  count: 1,
+})
+// 自定义验证
+const validateCardNoRange = (rule, value, callback) => {
+  if (!form.value.begin || !form.value.end) {
+    callback(new Error('请输入卡号段'))
+  } else if (Number(form.value.begin) > Number(form.value.end)) {
+    callback(new Error('开始卡号不能大于结束卡号'))
+  } else {
+    callback()
+  }
+}
+
+watch([() => form.value.begin, () => form.value.end], () => {
+  form.value.cardNoRange = `${form.value.begin}-${form.value.end}`;
+})
+watch(() => selectedArea.value, (val) => {
+  form.value.provinceId = val;
+})
+watch(() => selectedSchool.value, (val) => {
+  form.value.schoolId = val;
+})
+
+const rules = ref({
+  cardNoRange: [
+    { validator: validateCardNoRange, trigger: ['blur', 'change'] },
+  ],
+  agentId: [
+    { required: true, message: '请选择代理商', trigger: 'change' },
+  ],
+  provinceId: [
+    { required: true, message: '请选择省份', trigger: 'change' },
+  ],
+  schoolId: [
+    { required: true, message: '请选择学校', trigger: 'change' },
+  ],
+})
+
+
+
+const open = () => {
+  init();
+  form.value = {};
+  modalRef.value.open()
+}
+const close = () => {
+  reset();
+  modalRef.value.close()
+}
+const emit = defineEmits(['refresh'])
+const handleConfirm = () => {
+  formRef.value.validate(async (valid) => {
+    if (valid) {
+      await requestOpenCard(form.value.agentId, form.value.provinceId, form.value.schoolId, form.value.begin, form.value.end);
+      proxy.$modal.msgSuccess('开卡申请成功')
+      close();
+      setTimeout(() => {
+        emit('refresh')
+      }, 300);
+    }
+  })
+}
+defineExpose({
+  open,
+  close
+})
+</script>
+<style lang="scss" scoped></style>

+ 262 - 901
back-ui/src/views/dz/cards/index.vue

@@ -1,178 +1,170 @@
 <template>
-  <div class="app-container">
-    <!-- 搜索表单组件 -->
-    <SearchForm v-show="showSearch" :config="searchConfig" :model-value="queryParams"
-      @update:model-value="handleSearchFormUpdate" :on-search="handleQuery" :on-reset="resetQuery" :show-expand="true"
-      :expand-count="6" label-width="68px" />
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['dz:cards:add']">制卡</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain @click="handleAssignCard" v-hasPermi="['dz:cards:assign']"
-          style="border-color: #67c23a; color: #67c23a; font-weight: 500">
-          <svg-icon icon-class="peoples" class="mr-1" style="font-size: 16px" />
-          分配卡
-        </el-button>
-      </el-col>
-      <!-- <el-col :span="1.5">
-        <el-button
-                type="success"
-                plain
-                icon="Edit"
-                :disabled="single"
-                @click="handleUpdate"
-                v-hasPermi="['dz:cards:edit']"
-          >修改</el-button
-        >
-      </el-col> -->
-      <el-col :span="1.5">
-        <el-button type="warning" plain :disabled="multiple" @click="handlePayment" v-hasPermi="['dz:cards:pay']"
-          style="border-color: #e6a23c; color: #e6a23c; font-weight: 500">
-          <svg-icon icon-class="money" class="mr-1" style="font-size: 16px" />
-          缴费
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain :disabled="multiple" @click="handleCloseCard" v-hasPermi="['dz:cards:close']"
-          style="border-color: #f56c6c; color: #f56c6c; font-weight: 500">
-          <svg-icon icon-class="lock" class="mr-1" style="font-size: 16px" />
-          关卡
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="primary" plain :disabled="multiple" @click="handleReopenCard" v-hasPermi="['dz:cards:reopen']"
-          style="border-color: #13c2c2; color: #13c2c2; font-weight: 500">
-          <svg-icon icon-class="enter" class="mr-1" style="font-size: 16px" />
-          重开
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain :disabled="multiple" @click="handleRefund" v-hasPermi="['dz:cards:refund']"
-          style="border-color: #ff4d4f; color: #ff4d4f; font-weight: 500">
-          <svg-icon icon-class="money" class="mr-1" style="font-size: 16px" />
-          退费
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="primary" plain @click="handleAssociateCampus" v-hasPermi="['dz:cards:associateCampus']"
-          style="border-color: #1890ff; color: #1890ff; font-weight: 500">
-          <svg-icon icon-class="link" class="mr-1" style="font-size: 16px" />
-          关联校区
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain @click="handleApplyCard" v-hasPermi="['dz:cards:openFinished']"
-          style="border-color: #52c41a; color: #52c41a; font-weight: 500">
-          <svg-icon icon-class="edit" class="mr-1" style="font-size: 16px" />
-          直接开卡
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete"
-          v-hasPermi="['dz:cards:remove']" style="border-color: #ff4d4f; color: #ff4d4f; font-weight: 500">
-          删除
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Download" @click="handleExport" v-hasPermi="['dz:cards:export']"
-          style="border-color: #722ed1; color: #722ed1; font-weight: 500">导出</el-button>
-      </el-col>
+  <div class="app-page">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="省份筛选" prop="provinceId">
+        <el-cascader class="w-[180px]!" :options="areaList" :props="cascaderProps" v-model="selectedArea" clearable />
+      </el-form-item>
+      <el-form-item label="分配学校" prop="assignSchoolId">
+        <ie-select v-model="selectedSchool" :options="schoolList" label-key="name" value-key="id" filterable
+          class="w-[180px]!" />
+      </el-form-item>
+      <el-form-item label="注册学校" prop="schoolId">
+        <ie-select v-model="queryParams.schoolId" :options="schoolList" label-key="name" value-key="id" filterable
+          class="w-[180px]!" />
+      </el-form-item>
+      <el-form-item label="注册学校班级" prop="classId">
+        <ie-select v-model="selectedClass" :options="classList" label-key="name" value-key="classId" filterable
+          class="w-[180px]!" />
+      </el-form-item>
+      <el-form-item label="培训学校" prop="campusId">
+        <ie-select v-model="selectedCampus" :options="campusList" label-key="name" value-key="id" filterable
+          class="w-[180px]!" @change="handleCampusChange" />
+      </el-form-item>
+      <el-form-item label="培训学校班级" prop="campusClassId">
+        <ie-select v-model="selectedCampusClass" :options="campusClassList" label-key="name" value-key="classId"
+          filterable class="w-[180px]!" />
+      </el-form-item>
+      <el-form-item label="代理商" prop="agentId">
+        <ie-agent-select v-model="queryParams.agentId" class="w-[180px]!" filterable />
+      </el-form-item>
+      <el-form-item label="姓名" prop="nickName">
+        <el-input v-model="queryParams.nickName" type="text" class="w-[180px]!" placeholder="请输入姓名" clearable />
+      </el-form-item>
+      <el-form-item label="手机" prop="mobile">
+        <el-input v-model="queryParams.mobile" type="text" maxlength="11" class="w-[180px]!" placeholder="请输入手机号"
+          clearable />
+      </el-form-item>
+      <el-form-item label="卡号段" prop="begin">
+        <div class="flex items-center gap-x-3">
+          <el-input v-model="queryParams.begin" type="text" maxlength="11" class="w-[180px]!" placeholder="请输入开始卡号" />
+          <span class="text-gray-500">-</span>
+          <el-input v-model="queryParams.end" type="text" maxlength="11" class="w-[180px]!" placeholder="请输入结束卡号" />
+        </div>
+      </el-form-item>
+      <el-form-item label="卡分配日期" prop="days">
+        <el-date-picker v-model="queryParams.days" type="daterange" range-separator="至" start-placeholder="开始日期"
+          end-placeholder="结束日期" class="w-[282px]!" />
+      </el-form-item>
+      <el-form-item label="平台机构" prop="deptId">
+        <ie-institution-select v-model="queryParams.deptId" class="w-[180px]!" clearable />
+      </el-form-item>
+      <el-form-item label="考生类型" prop="assignExamType">
+        <ie-select v-model="queryParams.assignExamType" :options="exam_type" class="w-[180px]!" clearable />
+      </el-form-item>
+      <el-form-item label="卡类型" prop="cardType">
+        <ie-select v-model="queryParams.cardType" :options="card_type" class="w-[180px]!" clearable />
+      </el-form-item>
+      <el-form-item label="分配状态" prop="distributeStatus">
+        <ie-select v-model="queryParams.distributeStatus" :options="card_distribute_status" class="w-[180px]!"
+          clearable />
+      </el-form-item>
+      <el-form-item label="使用状态" prop="cardStatus">
+        <ie-select v-model="queryParams.cardStatus" :options="CARD_STATUS" class="w-[180px]!" clearable />
+      </el-form-item>
+
+      <el-form-item label="过期状态" prop="timeStatus">
+        <ie-select v-model="queryParams.timeStatus" :options="card_time_status" class="w-[180px]!" clearable />
+      </el-form-item>
+      <el-form-item label="结算状态" prop="payStatus">
+        <ie-select v-model="queryParams.payStatus" :options="card_pay_status" class="w-[180px]!" clearable />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row class="mt-2">
+      <CustomButton icon="plus" v-hasPermi="['dz:cards:add']" @click="handleAddCard">制卡</CustomButton>
+      <CustomButton color="#67c23a" v-hasPermi="['dz:cards:assign']" @click="handleAssign">
+        <svg-icon icon-class="peoples" class="mr-1" style="font-size: 12px" />
+        分配卡
+      </CustomButton>
+      <CustomButton color="#E6A23C" v-hasPermi="['dz:cards:pay']" :disabled="batchDisabled" @click="handlePay">
+        <svg-icon icon-class="money" class="mr-1" style="font-size: 12px" />
+        缴费
+      </CustomButton>
+      <CustomButton color="#F56C6C" v-hasPermi="['dz:cards:close']" :disabled="batchDisabled" @click="handleClose">
+        <svg-icon icon-class="lock" class="mr-1" style="font-size: 12px" />
+        关卡
+      </CustomButton>
+      <CustomButton color="#67C23A" v-hasPermi="['dz:cards:reopen']" :disabled="batchDisabled" @click="handleReopen">
+        <svg-icon icon-class="enter" class="mr-1" style="font-size: 12px" />
+        重开
+      </CustomButton>
+      <CustomButton color="#E6A23C" v-hasPermi="['dz:cards:refund']" :disabled="batchDisabled" @click="handleRefund">
+        <svg-icon icon-class="money" class="mr-1" style="font-size: 12px" />
+        退费
+      </CustomButton>
+      <CustomButton color="#9C27B0" v-hasPermi="['dz:cards:associateCampus']">
+        <svg-icon icon-class="link" class="mr-1" style="font-size: 12px" />
+        关联校区
+      </CustomButton>
+      <CustomButton color="#00BCD4" v-hasPermi="['dz:cards:openFinished']" @click="handleOpenCard">
+        <svg-icon icon-class="enter" class="mr-1" style="font-size: 12px" />
+        直接开卡
+      </CustomButton>
+      <CustomButton icon="delete" color="#F56C6C" v-hasPermi="['dz:cards:remove']" :disabled="batchDisabled"
+        @click="handleDeleteBatch">
+        删除
+      </CustomButton>
+      <CustomButton color="#FFC107" :disabled="batchDisabled">
+        <svg-icon icon-class="chart" class="mr-1" style="font-size: 12px" />
+        结算
+      </CustomButton>
+      <CustomButton color="#009688" :disabled="batchDisabled">
+        <svg-icon icon-class="time" class="mr-1" style="font-size: 14px" />
+        续期
+      </CustomButton>
+      <CustomButton color="#673AB7" :disabled="batchDisabled">
+        <svg-icon icon-class="edit" class="mr-1" style="font-size: 12px" />
+        修改
+      </CustomButton>
+      <CustomButton color="#606266" v-hasPermi="['dz:cards:export']" @click="handleExport">
+        <svg-icon icon-class="download" class="mr-1" style="font-size: 14px" />
+        导出
+      </CustomButton>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
-
-    <!-- Table组件 -->
-    <Table :data="cardsList" :columns="tableColumns" :actions="tableActions" :loading="loading" :total="total"
-      :queryParams="queryParams" v-bind="tableProps" @action="handleTableAction"
-      @selection-change="handleSelectionChange" @getList="getList">
-      <!-- 序号插槽 -->
-      <template #index="{ row, $index }">
-        {{ getRowIndex($index) }}
-      </template>
-
-      <!-- 姓名-手机插槽 -->
-      <template #studentInfo="{ row }">
-        <div class="student-info">
-          <div class="student-name">{{ row.nickName || "-" }}</div>
-          <div class="student-phone">{{ row.phonenumber || "-" }}</div>
-        </div>
-      </template>
-    </Table>
-
-    <!-- 弹窗组件 -->
-    <CardGenerationDialog v-model="cardGenerationOpen" :institution-list="institutionList" :school-list="schoolList"
-      :card-type-options="card_type" @success="handleDialogSuccess" />
-
-    <AssignCardDialog v-model="assignCardOpen" :selected-cards="selectedRows" :school-list="schoolList"
-      :card-type-options="card_type" @success="handleDialogSuccess" />
-
-    <EditStudentDialog v-model="open" :card-id="currentCardId" :class-list="classList" @success="handleDialogSuccess" />
-
-    <PaymentDialog v-model="paymentOpen" :card-no="currentCardNo" :card-ids="selectedCardIds"
-      :selected-cards="selectedRows" @success="handlePaymentSuccess" />
-
-    <CloseCardDialog v-model="closeCardOpen" :card-no="currentCloseCardNo" :card-ids="selectedCardIds"
-      @success="handleCloseCardSuccess" />
-
-    <ReopenCardDialog v-model="reopenCardOpen" :card-no="currentReopenCardNo" :card-ids="selectedCardIds"
-      @success="handleReopenCardSuccess" />
-
-    <RefundDialog v-model="refundOpen" :card-no="currentRefundCardNo" @confirm="handleRefundConfirm" />
-
-    <AssociateCampusDialog v-model="associateCampusOpen" :selected-cards="selectedRows"
-      @success="handleAssociateCampusSuccess" />
-
-    <ApplyCardDialog v-model="applyCardOpen" :selected-cards="selectedRows" @success="handleApplyCardSuccess" />
+    <card-table :data="cardList" :loading="loading" @selectionChange="handleSelectionChange" @delete="handleDelete" />
+    <div class="flex justify-end">
+      <Pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        @pagination="getList" />
+    </div>
+    <EditDialog ref="editDialogRef" @refresh="getList" />
+    <OpenDialog ref="openDialogRef" @refresh="getList" />
+    <AssignDialog ref="assignDialogRef" @refresh="getList" />
   </div>
 </template>
 
-<style scoped>
-.student-info {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-
-.student-name {
-  font-weight: 500;
-  margin-bottom: 2px;
-}
-
-.student-phone {
-  font-size: 12px;
-  color: #909399;
-}
-</style>
-
-<script setup name="Cards">
-import {
-  listCards,
-  getCards,
-  delCards,
-  addCards,
-  updateCards,
-} from "@/api/dz/cards";
-import SearchForm from "@/components/SearchForm/index.vue";
-import Table from "@/components/Table/index.vue";
-import CardGenerationDialog from "./components/CardGenerationDialog.vue";
-import AssignCardDialog from "./components/AssignCardDialog.vue";
-import EditStudentDialog from "./components/EditStudentDialog.vue";
-import PaymentDialog from "./components/PaymentDialog.vue";
-import CloseCardDialog from "./components/CloseCardDialog.vue";
-import ReopenCardDialog from "./components/ReopenCardDialog.vue";
-import RefundDialog from "./components/RefundDialog.vue";
-import AssociateCampusDialog from "./components/AssociateCampusDialog.vue";
-import ApplyCardDialog from "./components/ApplyCardDialog.vue";
-import formInfo from "./config/form.js";
-import tableConfig from "./config/table.js";
-// import { listUniversity } from "@/api/dz/school";
-import { assignCard, issueCard, getCampusSchoolList } from "@/api/dz/cards";
-import { listDept } from "@/api/system/dept";
-import { listAgent } from "@/api/dz/agent";
-import { getClassesBySchoolId } from "@/api/dz/classes";
+<script setup>
+import useSchool from '@/hooks/useSchool';
+import IeSelect from '@/components/IeSelect/index.vue';
+import IeAgentSelect from '@/components/IeAgentSelect/index.vue';
+import IeInstitutionSelect from '@/components/IeInstitutionSelect/index.vue';
+import CardTable from './components/CardTable.vue';
+import Pagination from '@/components/Pagination/index.vue';
+import CustomButton from './components/CustomButton.vue';
+import EditDialog from './components/EditDialog.vue';
+import OpenDialog from './components/OpenDialog.vue';
+import AssignDialog from './components/AssignDialog.vue';
+import { listCards, delCards, payCard, closeCard, reopenCard, refundCard } from '@/api/dz/cards';
+import { CARD_STATUS } from '@/common/enum';
+import { getCurrentInstance } from 'vue';
 
 const { proxy } = getCurrentInstance();
+const {
+  areaList,
+  selectedArea,
+  schoolList,
+  selectedSchool,
+  classList,
+  selectedClass,
+  campusList,
+  selectedCampus,
+  campusClassList,
+  selectedCampusClass,
+} = useSchool({ loadCampus: true, loadClass: true });
 const {
   exam_type,
   card_status,
@@ -181,770 +173,139 @@ const {
   bool_values,
   card_pay_status,
   card_type,
-} = proxy.useDict(
-  "exam_type",
-  "card_status",
-  "card_distribute_status",
-  "card_time_status",
-  "bool_values",
-  "card_pay_status",
-  "card_type"
-);
-
-const cardsList = ref([]);
-const schoolList = ref([]);
-const campusList = ref([]); // 校区列表
-const classList = ref([]); // 班级列表
-const open = ref(false);
-const cardGenerationOpen = ref(false); // 制卡对话框
-const assignCardOpen = ref(false); // 分配卡对话框
-const paymentOpen = ref(false); // 缴费对话框
-const closeCardOpen = ref(false); // 关卡对话框
-const reopenCardOpen = ref(false); // 重开对话框
-const refundOpen = ref(false); // 退费对话框
-const associateCampusOpen = ref(false); // 关联校区对话框
-const applyCardOpen = ref(false); // 直接开卡对话框
-const currentCardNo = ref([]); // 当前缴费的卡号(支持数组)
-const currentCloseCardNo = ref([]); // 当前关卡的卡号(支持数组)
-const currentReopenCardNo = ref([]); // 当前重开的卡号(支持数组)
-const currentRefundCardNo = ref(""); // 当前退费的卡号
-const agentList = ref([]); // 代理商列表
-const institutionList = ref([]); // 机构列表
-const selectedRows = ref([]); // 选中的行
-const selectedCardIds = computed(() =>
-  selectedRows.value.map((row) => row.cardId)
-); // 选中的卡ID列表
-const currentCardId = ref(null); // 当前编辑的卡片ID
-const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref([]);
-const single = ref(true);
-const multiple = ref(true);
-const total = ref(0);
-const title = ref("");
-
-// 搜索配置
-const searchConfig = computed(() => {
-  const config = [...formInfo.info];
-
-  // 动态设置选项数据
-  config.forEach((item) => {
-    switch (item.name) {
-      case "type":
-        item.option = card_type.value || [];
-        break;
-      case "status":
-        item.option = card_status.value || [];
-        break;
-      case "distributeStatus":
-        item.option = card_distribute_status.value || [];
-        break;
-      // 移除registerSchoolId,实体类中不存在
-      case "timeStatus":
-        item.option = card_time_status.value || [];
-        break;
-      case "payStatus":
-        item.option = card_pay_status.value || [];
-        break;
-      case "isSettlement":
-        item.option = bool_values.value || [];
-        break;
-      case "assignSchoolId":
-        item.option = schoolList.value || [];
-        break;
-      case "registerSchoolId":
-        item.option = schoolList.value || [];
-        break;
-      case "campusId":
-        item.option = campusList.value || [];
-        break;
-      case "examType":
-        item.option = exam_type.value || [];
-        break;
-    }
-  });
-
-  return config;
-});
-
-// 表格配置
-const tableColumns = computed(() => {
-  const columns = [...tableConfig.columns];
-
-  // 动态设置字典选项
-  columns.forEach((column) => {
-    switch (column.prop) {
-      case "type":
-        column.options = card_type.value || [];
-        break;
-      case "status":
-        column.options = card_status.value || [];
-        break;
-      case "distributeStatus":
-        column.options = card_distribute_status.value || [];
-        break;
-      case "timeStatus":
-        column.options = card_time_status.value || [];
-        break;
-      case "payStatus":
-        column.options = card_pay_status.value || [];
-        break;
-      case "isSettlement":
-        column.options = bool_values.value || [];
-        break;
-      // 移除studentCategory,实体类中不存在
-      case "registerSchoolId":
-      case "assignSchoolId":
-        column.options = schoolList.value || [];
-        break;
-      case "studentCategory":
-      case "classId":
-      case "schoolClassId":
-      case "campusId":
-        column.options = []; // 需要从相应API获取
-        break;
-    }
-  });
-
-  return columns;
-});
-
-const tableActions = computed(() => tableConfig.actions);
-const tableProps = computed(() => tableConfig.tableProps);
-
-const data = reactive({
-  form: {},
-  queryParams: {
-    pageNum: 1,
-    pageSize: 10,
-    // 搜索表单字段
-    areaIds: [],
-    assignSchoolId: null,
-    registerSchoolId: null,
-    campusId: null,
-    agentId: null,
-    cardNo: null,
-    password: null,
-    cardNoRange: null,
-    begin: null,
-    end: null,
-    type: null,
-    studentCategory: null, // 分配考生类型
-    studentName: null, // 学生姓名
-    phone: null, // 手机号
-    registerSchoolId: null, // 注册学校
-    schoolClassId: null, // 校区班级
-    distributeStatus: null,
-    status: null,
-    timeStatus: null,
-    isSettlement: null,
-
-    // 其他字段(用于数据传输)
-    cardNo: null,
-    password: null,
-    payStatus: null,
-    isSettlement: null,
-    deptId: null,
-    leftAgentId: null,
-    schoolId: null,
-    classId: null,
-    year: null,
-    endYear: null,
-    openId: null,
-    distributeTime: null,
-    outDate: null,
-    openTime: null,
-    payTime: null,
-    activeTime: null,
-    settlementTime: null,
-    refundTime: null,
-    closeTime: null,
-    createTime: null,
-    updateTime: null,
-  },
-  rules: {
-    schoolId: [{ required: true, message: "学校不能为空", trigger: "change" }],
-    classId: [{ required: true, message: "班级不能为空", trigger: "change" }],
-    nickName: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
-    phone: [
-      { required: true, message: "手机号不能为空", trigger: "blur" },
-      {
-        pattern: /^1[3-9]\d{9}$/,
-        message: "请输入正确的手机号",
-        trigger: "blur",
-      },
-    ],
-    chineseMathEnglish: [
-      { required: true, message: "语数英成绩不能为空", trigger: "blur" },
-    ],
-    vocationalSkills: [
-      { required: true, message: "职业技能成绩不能为空", trigger: "blur" },
-    ],
-  },
-});
-
-const { queryParams, form } = toRefs(data);
-
-// 监听queryParams的原始变化
-watch(
-  () => data.queryParams,
-  (newParams) => {
-    if (newParams.areaIds && newParams.areaIds.length > 0) {
-      getSchoolList();
-    }
-  },
-  { immediate: true, deep: true }
-);
-
-/** 获取行序号 */
-function getRowIndex(index) {
-  const pageNum = queryParams.value?.pageNum || 1;
-  const pageSize = queryParams.value?.pageSize || 10;
-  return (pageNum - 1) * pageSize + index + 1;
-}
-
-/** 查询学习卡列表 */
-function getList() {
-  loading.value = true;
-  listCards(queryParams.value).then((response) => {
-    cardsList.value = response.rows;
-    total.value = response.total;
-    loading.value = false;
-  });
-}
-
-/** 获取学校列表 */
-function getSchoolList() {
-  // 如果没有选择省市区,则不获取学校列表
-  if (!queryParams.value.areaIds || queryParams.value.areaIds.length === 0) {
-    schoolList.value = [];
-    return;
-  }
-
-  // 构造location参数:如果有省市区选择,取最后一个值(区)作为location
-  const location =
-    queryParams.value.areaIds[queryParams.value.areaIds.length - 1];
-
-  // 传递pageNum和pageSize参数
-  const requestParams = {
-    location,
-    pageNum: 1,
-    pageSize: 9999, // 获取所有学校选项
-  };
-
-  // listUniversity(requestParams)
-  //   .then((response) => {
-  //     // 根据API返回数据结构处理
-  //     let schoolData = [];
-  //     if (response.data) {
-  //       schoolData = Array.isArray(response.data)
-  //         ? response.data
-  //         : response.data.rows || response.data.list || [];
-  //     } else if (response.rows) {
-  //       schoolData = response.rows;
-  //     } else if (response.list) {
-  //       schoolData = response.list;
-  //     } else if (Array.isArray(response)) {
-  //       schoolData = response;
-  //     }
-  //
-  //     // 确保数据格式符合配置要求
-  //     schoolData = schoolData.map((item) => {
-  //       // 如果API返回的是 {id, name, ...} 格式,直接使用
-  //       if (item.id && item.name) {
-  //         return item;
-  //       }
-  //       // 如果是其他格式,需要转换
-  //       return {
-  //         id: item.id || item.value || item.schoolId,
-  //         name: item.name || item.label || item.schoolName || item.title,
-  //       };
-  //     });
-  //
-  //     schoolList.value = schoolData;
-  //   })
-  //   .catch((error) => {
-  //     console.error("获取学校列表失败:", error);
-  //     schoolList.value = [];
-  //   });
-}
-
-/** 获取校区列表 */
-function getCampusListData() {
-  getCampusSchoolList({
-    campus: true,
-    pageNum: 1,
-    pageSize: 9999, // 获取所有校区
-  })
-    .then((response) => {
-      if (response.code === 200) {
-        // 处理API返回的数据结构
-        let campusData = [];
-        if (response.data) {
-          campusData = Array.isArray(response.data) ? response.data : [];
-        } else if (response.rows) {
-          campusData = response.rows;
-        } else if (response.list) {
-          campusData = response.list;
-        }
-
-        // 确保数据格式符合配置要求
-        campusData = campusData.map((item) => {
-          // 如果API返回的是 {id, name, ...} 格式,直接使用
-          if (item.id && item.name) {
-            return item;
-          }
-          // 如果是其他格式,需要转换
-          return {
-            id: item.id || item.value || item.campusId,
-            name: item.name || item.label || item.campusName || item.title,
-          };
-        });
-
-        campusList.value = campusData;
-      }
-    })
-    .catch((error) => {
-      console.error("获取校区列表失败:", error);
-      campusList.value = [];
-    });
-}
-
-// 取消按钮
-// 处理弹窗成功事件
-function handleDialogSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList(); // 刷新列表
-}
-
-// 获取机构列表
-async function getInstitutionList() {
-  try {
-    const response = await listDept({});
-    institutionList.value = response.data || response.rows || response || [];
-  } catch (error) {
-    console.error("获取机构列表失败:", error);
-    institutionList.value = [];
-  }
-}
-
-// 获取班级列表
-async function getClassList() {
-  try {
-    // 这里应该调用获取班级列表的API
-    // 暂时使用模拟数据
-    classList.value = [
-      { id: 1, name: "2501" },
-      { id: 2, name: "2502" },
-      { id: 3, name: "2503" },
-    ];
-  } catch (error) {
-    console.error("获取班级列表失败:", error);
-    classList.value = [];
-  }
-}
-
-// 获取代理商列表
-async function getAgentList() {
-  try {
-    const response = await listAgent({});
-    const agentData = response.data || response.rows || response || [];
-
-    // 确保数据格式符合前端期望
-    agentList.value = agentData.map((item) => ({
-      id: item.agentId,
-      name: item.name,
-    }));
-  } catch (error) {
-    console.error("获取代理商列表失败:", error);
-    agentList.value = [];
-  }
-}
-
-function cancel() {
-  open.value = false;
-  reset();
-}
-
-// 表单重置
-function reset() {
-  form.value = {
-    cardId: null,
-    cardNo: null,
-    password: null,
-    type: null,
-    status: null,
-    distributeStatus: null,
-    timeStatus: null,
-    payStatus: null,
-    isSettlement: null,
-    deptId: null,
-    agentId: null,
-    leftAgentId: null,
-    campusId: null,
-    assignSchoolId: null,
-    schoolId: null,
-    classesId: null,
-    year: null,
-    endYear: null,
-    openId: null,
-    remark: null,
-    distributeTime: null,
-    outDate: null,
-    openTime: null,
-    payTime: null,
-    activeTime: null,
-    settlementTime: null,
-    refundTime: null,
-    closeTime: null,
-    createTime: null,
-    updateTime: null,
-  };
-  proxy.resetForm("cardsRef");
-}
-
-/** 搜索按钮操作 */
-/** 表格操作处理 */
-function handleTableAction(action, row) {
-  switch (action.key) {
-    case "edit":
-      handleUpdate(row); // 修改时获取行数据
-      break;
-    case "delete":
-      handleDelete(row);
-      break;
-    default:
-      console.warn("Unknown action:", action.key);
-  }
-}
-
-function handleQuery() {
-  queryParams.value.pageNum = 1;
+} = proxy.useDict("exam_type", "card_status", "card_distribute_status", "card_time_status", "bool_values", "card_pay_status", "card_type");
+const cascaderProps = {
+  label: "areaName",
+  value: "areaId",
+  checkStrictly: true,
+}
+const queryParams = ref({})
+const showSearch = ref(true)
+const cardList = ref([])
+const total = ref(0)
+const selectedRows = ref([])
+const loading = ref(false)
+
+const ids = computed(() => {
+  return selectedRows.value.map(item => item.cardId);
+})
+const batchDisabled = computed(() => {
+  return selectedRows.value.length === 0;
+})
+
+const handleQuery = () => {
+  queryParams.page = 1;
   getList();
 }
 
-/** 重置按钮操作 */
-function resetQuery() {
-  proxy.resetForm("queryRef");
+const resetQuery = () => {
+  selectedArea.value = [];
+  selectedSchool.value = null;
+  selectedClass.value = null;
+  selectedCampus.value = null;
+  selectedCampusClass.value = null;
+  queryParams.value = {};
   handleQuery();
 }
 
-/** 处理SearchForm组件的update:model-value事件 */
-function handleSearchFormUpdate(newData) {
-  Object.assign(data.queryParams, newData);
+const getList = () => {
+  loading.value = true;
+  listCards({
+    ...queryParams.value,
+    areaIds: selectedArea.value,
+    assignSchoolId: selectedSchool.value,
+    classId: selectedClass.value,
+    campusId: selectedCampus.value
+  }).then(res => {
+    cardList.value = res.rows;
+    total.value = res.total;
+  }).finally(() => {
+    loading.value = false;
+  })
 }
 
-// 多选框选中数据
-function handleSelectionChange(selection) {
-  console.log("Selection changed:", selection);
+const handleSelectionChange = (selection) => {
   selectedRows.value = selection;
-  ids.value = selection.map((item) => item.cardId);
-  single.value = selection.length != 1;
-  multiple.value = !selection.length;
-}
-
-/** 制卡按钮操作 */
-function handleAdd() {
-  cardGenerationOpen.value = true;
-  getInstitutionList(); // 获取机构列表
-}
-
-/** 缴费按钮操作 */
-function handlePayment() {
-  if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要缴费的卡片");
-    return;
-  }
-
-  // 获取所有选中卡片的卡号数组
-  const cardNos = selectedRows.value.map(
-    (card) => card.cardNo || card.id || "未知"
-  );
-  currentCardNo.value = cardNos;
-  paymentOpen.value = true;
-}
-
-// 处理缴费成功
-function handlePaymentSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList(); // 刷新列表
-}
-
-// 处理缴费确认(保留兼容性)
-function handlePaymentConfirm(cardNo) {
-  proxy.$modal.msgSuccess(`缴费成功!卡号:${cardNo}`);
-  getList(); // 刷新列表
-}
-
-/** 关卡按钮操作 */
-function handleCloseCard() {
-  if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要关卡的卡片");
-    return;
-  }
-
-  // 获取所有选中卡片的卡号数组
-  const cardNos = selectedRows.value.map(
-    (card) => card.cardNo || card.id || "未知"
-  );
-  currentCloseCardNo.value = cardNos;
-  closeCardOpen.value = true;
-}
-
-// 处理关卡成功
-function handleCloseCardSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList(); // 刷新列表
-}
-
-// 处理关卡确认(保留兼容性)
-function handleCloseCardConfirm(cardNo) {
-  proxy.$modal.msgSuccess(`关卡成功!卡号:${cardNo}`);
-  getList(); // 刷新列表
-}
-
-/** 重开按钮操作 */
-function handleReopenCard() {
-  if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要重开的卡片");
-    return;
-  }
-
-  // 获取所有选中卡片的卡号数组
-  const cardNos = selectedRows.value.map(
-    (card) => card.cardNo || card.id || "未知"
-  );
-  currentReopenCardNo.value = cardNos;
-  reopenCardOpen.value = true;
-}
-
-// 处理重开成功
-function handleReopenCardSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList(); // 刷新列表
-}
-
-// 处理重开确认(保留兼容性)
-function handleReopenCardConfirm(cardNo) {
-  proxy.$modal.msgSuccess(`重开成功!卡号:${cardNo}`);
-  getList(); // 刷新列表
-}
-
-/** 退费按钮操作 */
-function handleRefund() {
-  if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要退费的卡片");
-    return;
-  }
-
-  // 获取第一个选中卡片的卡号
-  const firstCard = selectedRows.value[0];
-  currentRefundCardNo.value = firstCard.cardNo || firstCard.id || "未知";
-  refundOpen.value = true;
-}
-
-// 处理退费确认
-function handleRefundConfirm(cardNo) {
-  proxy.$modal.msgSuccess(`退费成功!卡号:${cardNo}`);
-  getList(); // 刷新列表
-}
-
-/** 关联校区按钮操作 */
-function handleAssociateCampus() {
-  if (selectedRows.value.length === 0) {
-    // proxy.$modal.msgWarning("请选择要关联校区的卡片");
-    // return;
-  }
-  associateCampusOpen.value = true;
-}
-
-// 处理关联校区成功
-function handleAssociateCampusSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList();
 }
 
-// 处理关联校区确认
-function handleAssociateCampusConfirm(data) {
-  proxy.$modal.msgSuccess(
-    `关联校区成功!卡号段:${data.beginCardNo}-${data.endCardNo}`
-  );
-  getList(); // 刷新列表
+const editDialogRef = ref(null);
+const handleAddCard = () => {
+  editDialogRef.value.open()
 }
 
-// 获取代理商列表
-async function getAgentListData() {
-  try {
-    const response = await getAgentList({ pageNum: 1, pageSize: 1000 });
-    if (response.code === 200) {
-      agentList.value = response.data || [];
-    }
-  } catch (error) {
-    console.error("获取代理商列表失败:", error);
-    agentList.value = [];
-  }
+const handleDelete = (row) => {
+  proxy.$modal.confirm(`是否确认删除学习卡号为"${row.cardNo}"的数据项?`).then(() => {
+    delCards(row.cardId).then(() => {
+      proxy.$modal.msgSuccess('删除成功')
+      getList()
+    })
+  });
 }
 
-/** 直接开卡按钮操作 */
-function handleApplyCard() {
-  if (selectedRows.value.length === 0) {
-    // proxy.$modal.msgWarning("请选择要申请开卡的卡片");
-    // return;
-  }
-  applyCardOpen.value = true;
+const handleDeleteBatch = () => {
+  proxy.$modal.confirm(`是否确认删除所选数据 (${ids.value.length}项) ?`).then(() => {
+    delCards(ids.value).then(() => {
+      proxy.$modal.msgSuccess('删除成功')
+      getList()
+    })
+  });
 }
 
-// 处理直接开卡成功
-function handleApplyCardSuccess(message) {
-  proxy.$modal.msgSuccess(message);
-  getList();
+const openDialogRef = ref(null);
+const handleOpenCard = () => {
+  openDialogRef.value.open()
 }
 
-// 处理直接开卡确认
-function handleApplyCardConfirm(data) {
-  proxy.$modal.msgSuccess(
-    `直接开卡成功!卡号段:${data.beginCardNo}-${data.endCardNo}`
-  );
-  getList(); // 刷新列表
+const handlePay = () => {
+  proxy.$modal.confirm(`是否确认缴费所选数据 (${ids.value.length}项) ?`).then(() => {
+    payCard(ids.value).then(() => {
+      proxy.$modal.msgSuccess('缴费成功')
+      getList()
+    })
+  })
 }
 
-/** 分配卡按钮操作 */
-function handleAssignCard() {
-  console.log("分配卡按钮点击,当前选中的行:", selectedRows.value);
-  assignCardOpen.value = true;
-  getInstitutionList(); // 获取机构列表
-  getAgentList(); // 获取代理商列表
+const handleClose = () => {
+  proxy.$modal.confirm(`是否确认关卡所选数据 (${ids.value.length}项) ?`).then(() => {
+    closeCard(ids.value).then(() => {
+      proxy.$modal.msgSuccess('关卡成功')
+      getList()
+    })
+  })
 }
 
-/** 修改按钮操作 */
-async function handleUpdate(row) {
-  reset();
-  const _cardId = row.cardId || ids.value;
-  currentCardId.value = _cardId;
-
-  try {
-    // 获取学习卡详细信息
-    const response = await getCards(_cardId);
-    if (response.code === 200) {
-      const cardData = response.data;
-
-      // 将后端数据对应到表单字段
-      form.value = {
-        cardId: cardData.cardId,
-        cardNo: cardData.cardNo,
-        password: cardData.password,
-        type: cardData.type,
-        status: cardData.status,
-        distributeStatus: cardData.distributeStatus,
-        timeStatus: cardData.timeStatus,
-        payStatus: cardData.payStatus,
-        isSettlement: cardData.isSettlement,
-        deptId: cardData.deptId,
-        agentId: cardData.agentId,
-        leftAgentId: cardData.leftAgentId,
-        campusId: cardData.campusId,
-        assignSchoolId: cardData.assignSchoolId,
-        schoolId: cardData.schoolId,
-        classId: cardData.classId,
-        year: cardData.year,
-        endYear: cardData.endYear,
-        openId: cardData.openId,
-        nickName: cardData.nickName,
-        mobile: cardData.mobile,
-        chineseMathEnglish: cardData.chineseMathEnglish,
-        vocationalSkills: cardData.vocationalSkills,
-        studentCategory: cardData.studentCategory,
-        assignExamType: cardData.assignExamType,
-        areaIds: cardData.areaIds,
-        remark: cardData.remark,
-      };
-
-      // 获取班级列表
-      await getClassList();
-
-      // 打开编辑弹窗
-      open.value = true;
-    } else {
-      proxy.$modal.msgError("获取学习卡信息失败");
-    }
-  } catch (error) {
-    console.error("获取学习卡详细信息失败:", error);
-    proxy.$modal.msgError("获取学习卡信息失败");
-  }
+const handleReopen = () => {
+  proxy.$modal.confirm(`是否确认重开所选数据 (${ids.value.length}项) ?`).then(() => {
+    reopenCard(ids.value).then(() => {
+      proxy.$modal.msgSuccess('重开成功')
+      getList()
+    })
+  })
 }
 
-/** 提交按钮 */
-function submitForm() {
-  proxy.$refs["cardsRef"].validate((valid) => {
-    if (valid) {
-      if (form.value.cardId != null) {
-        updateCards(form.value).then((response) => {
-          proxy.$modal.msgSuccess("修改成功");
-          open.value = false;
-          getList();
-        });
-      } else {
-        addCards(form.value).then((response) => {
-          proxy.$modal.msgSuccess("新增成功");
-          open.value = false;
-          getList();
-        });
-      }
-    }
-  });
+const handleRefund = () => {
+  proxy.$modal.confirm(`是否确认退费所选数据 (${ids.value.length}项) ?`).then(() => {
+    refundCard(ids.value).then(() => {
+      proxy.$modal.msgSuccess('退费成功')
+      getList()
+    })
+  })
 }
 
-/** 删除按钮操作 */
-function handleDelete(row) {
-  const _cardIds = row.cardId || ids.value;
-  const cardIdsArray = Array.isArray(_cardIds) ? _cardIds : [_cardIds];
-
-  if (cardIdsArray.length === 0) {
-    proxy.$modal.msgWarning("请选择要删除的数据");
-    return;
-  }
-
-  const message =
-    cardIdsArray.length === 1
-      ? `是否确认删除学习卡编号为"${cardIdsArray[0]}"的数据项?`
-      : `是否确认删除选中的${cardIdsArray.length}条学习卡数据?`;
-
-  proxy.$modal
-    .confirm(message)
-    .then(function () {
-      return delCards(cardIdsArray);
-    })
-    .then(() => {
-      getList();
-      proxy.$modal.msgSuccess("删除成功");
-    })
-    .catch(() => { });
+const assignDialogRef = ref(null);
+const handleAssign = () => {
+  assignDialogRef.value.open()
 }
 
-/** 导出按钮操作 */
-function handleExport() {
-  proxy.download(
-    "dz/cards/export",
-    {
-      ...queryParams.value,
-    },
-    `cards_${new Date().getTime()}.xlsx`
-  );
+const handleExport = () => {
+  proxy.download('dz/cards/export', {
+    ...queryParams.value
+  }, `cards_${new Date().getTime()}.xlsx`)
 }
 
-getList();
-getCampusListData(); // 获取校区列表
 
-// 监听地址选择变化,自动获取学校列表
-watch(
-  () => queryParams.value.areaIds,
-  (newAreaIds) => {
-    if (newAreaIds && newAreaIds.length > 0) {
-      getSchoolList();
-    } else {
-      schoolList.value = [];
-    }
-  },
-  { immediate: true, deep: true }
-);
+onMounted(() => {
+  handleQuery();
+})
 </script>
+<style lang="scss" scoped></style>

+ 128 - 175
back-ui/src/views/system/dict/data.vue

@@ -1,178 +1,128 @@
 <template>
-   <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="字典名称" prop="dictType">
-            <el-select v-model="queryParams.dictType" style="width: 200px">
-               <el-option
-                  v-for="item in typeOptions"
-                  :key="item.dictId"
-                  :label="item.dictName"
-                  :value="item.dictType"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item label="字典标签" prop="dictLabel">
-            <el-input
-               v-model="queryParams.dictLabel"
-               placeholder="请输入字典标签"
-               clearable
-               style="width: 200px"
-               @keyup.enter="handleQuery"
-            />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
-               <el-option
-                  v-for="dict in sys_normal_disable"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+      <el-form-item label="字典名称" prop="dictType">
+        <el-select v-model="queryParams.dictType" style="width: 200px">
+          <el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="字典标签" prop="dictLabel">
+        <el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable style="width: 200px"
+          @keyup.enter="handleQuery" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
+          <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:dict:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:dict:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:dict:remove']"
-            >删除</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Download"
-               @click="handleExport"
-               v-hasPermi="['system:dict:export']"
-            >导出</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Close"
-               @click="handleClose"
-            >关闭</el-button>
-         </el-col>
-         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
+          v-hasPermi="['system:dict:edit']">修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
+          v-hasPermi="['system:dict:remove']">删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="Download" @click="handleExport"
+          v-hasPermi="['system:dict:export']">导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
 
-      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="字典编码" align="center" prop="dictCode" />
-         <el-table-column label="字典标签" align="center" prop="dictLabel">
-            <template #default="scope">
-               <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
-               <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
-            </template>
-         </el-table-column>
-         <el-table-column label="字典键值" align="center" prop="dictValue" />
-         <el-table-column label="字典排序" align="center" prop="dictSort" />
-         <el-table-column label="状态" align="center" prop="status">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">修改</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+    <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="字典编码" align="center" prop="dictCode" />
+      <el-table-column label="字典标签" align="center" prop="dictLabel">
+        <template #default="scope">
+          <span
+            v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{
+              scope.row.dictLabel }}</span>
+          <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass"
+            :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="字典键值" align="center" prop="dictValue" />
+      <el-table-column label="字典排序" align="center" prop="dictSort" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+            v-hasPermi="['system:dict:edit']">修改</el-button>
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+            v-hasPermi="['system:dict:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
 
-      <pagination
-         v-show="total > 0"
-         :total="total"
-         v-model:page="queryParams.pageNum"
-         v-model:limit="queryParams.pageSize"
-         @pagination="getList"
-      />
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
 
-      <!-- 添加或修改参数配置对话框 -->
-      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-         <el-form ref="dataRef" :model="form" :rules="rules" label-width="80px">
-            <el-form-item label="字典类型">
-               <el-input v-model="form.dictType" :disabled="true" />
-            </el-form-item>
-            <el-form-item label="数据标签" prop="dictLabel">
-               <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
-            </el-form-item>
-            <el-form-item label="数据键值" prop="dictValue">
-               <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
-            </el-form-item>
-            <el-form-item label="样式属性" prop="cssClass">
-               <el-input v-model="form.cssClass" placeholder="请输入样式属性" />
-            </el-form-item>
-            <el-form-item label="显示排序" prop="dictSort">
-               <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
-            </el-form-item>
-            <el-form-item label="回显样式" prop="listClass">
-               <el-select v-model="form.listClass">
-                  <el-option
-                     v-for="item in listClassOptions"
-                     :key="item.value"
-                     :label="item.label + '(' + item.value + ')'"
-                     :value="item.value"
-                  ></el-option>
-               </el-select>
-            </el-form-item>
-            <el-form-item label="状态" prop="status">
-               <el-radio-group v-model="form.status">
-                  <el-radio
-                     v-for="dict in sys_normal_disable"
-                     :key="dict.value"
-                     :value="dict.value"
-                  >{{ dict.label }}</el-radio>
-               </el-radio-group>
-            </el-form-item>
-            <el-form-item label="备注" prop="remark">
-               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
-   </div>
+    <!-- 添加或修改参数配置对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="dataRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="字典类型">
+          <el-input v-model="form.dictType" :disabled="true" />
+        </el-form-item>
+        <el-form-item label="数据标签" prop="dictLabel">
+          <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
+        </el-form-item>
+        <el-form-item label="数据键值" prop="dictValue">
+          <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
+        </el-form-item>
+        <el-form-item label="样式属性" prop="cssClass">
+          <el-input v-model="form.cssClass" placeholder="请输入样式属性" />
+        </el-form-item>
+        <el-form-item label="显示排序" prop="dictSort">
+          <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
+        </el-form-item>
+        <el-form-item label="回显样式" prop="listClass">
+          <el-select v-model="form.listClass">
+            <el-option v-for="item in listClassOptions" :key="item.value" :label="item.label + '(' + item.value + ')'"
+              :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label
+              }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup name="Data">
@@ -197,8 +147,8 @@ const typeOptions = ref([])
 const route = useRoute()
 // 数据标签回显样式
 const listClassOptions = ref([
-  { value: "default", label: "默认" }, 
-  { value: "primary", label: "主要" }, 
+  { value: "default", label: "默认" },
+  { value: "primary", label: "主要" },
   { value: "success", label: "成功" },
   { value: "info", label: "信息" },
   { value: "warning", label: "警告" },
@@ -206,7 +156,10 @@ const listClassOptions = ref([
 ])
 
 const data = reactive({
-  form: {},
+  form: {
+    // listClass: 'default',
+    // status: '0',
+  },
   queryParams: {
     pageNum: 1,
     pageSize: 10,
@@ -341,13 +294,13 @@ function submitForm() {
 /** 删除按钮操作 */
 function handleDelete(row) {
   const dictCodes = row.dictCode || ids.value
-  proxy.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function() {
+  proxy.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function () {
     return delData(dictCodes)
   }).then(() => {
     getList()
     proxy.$modal.msgSuccess("删除成功")
     useDictStore().removeDict(queryParams.value.dictType)
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 /** 导出按钮操作 */