From 9e583c3c3063bb1ee6b0f2c276593b1b5ef90425 Mon Sep 17 00:00:00 2001 From: guochunsi <1595020186@qq.com> Date: Tue, 6 Jan 2026 16:39:57 +0800 Subject: [PATCH 1/3] a --- COLUMN_VISIBILITY_SOLUTIONS.md | 163 +++ docs/按钮样式规范.md | 1 + package.json | 4 +- src/api/admin/dict.ts | 9 + .../{ => salaries}/professionalyearbounds.ts | 0 .../{ => salaries}/salaryexportrecord.ts | 0 .../{ => salaries}/teacherawardtax.ts | 0 .../{ => salaries}/teacherpayslip.ts | 0 .../{ => salaries}/teachersalary.ts | 0 src/components/GenderTag/index.vue | 114 ++ src/components/StatusTag/README.md | 149 +++ src/components/StatusTag/index.vue | 140 +++ src/components/TableColumn/Provider.vue | 17 + src/components/TableColumn/README.md | 76 ++ src/components/TableColumn/index.vue | 64 + src/components/TableColumnControl/README.md | 162 +++ src/components/TableColumnControl/USAGE.md | 196 +++ src/components/TableColumnControl/index.vue | 461 +++++++ src/composables/useTableColumns.ts | 411 ++++++ src/directives/v-column-visible.ts | 40 + src/excel/Export2Excel.js | 8 +- .../professional/common/material-show.vue | 4 +- .../salaries/professionalyearbounds/index.vue | 13 +- .../salaries/salaryexportrecord/index.vue | 2 +- .../salaries/teacherawardtax/index.vue | 10 +- .../salaries/teacherpayslip/index.vue | 15 +- .../salaries/teacherpayslip/salaryInfo.vue | 4 +- .../salaries/teachersalary/index.vue | 13 +- .../salaries/teachersalary/salaryInfo.vue | 2 +- .../teacherbase/action-dropdown.vue | 91 ++ src/views/professional/teacherbase/index.vue | 1098 ++++++++++------- .../professional/teacherbase/multiDialog.vue | 44 +- 32 files changed, 2830 insertions(+), 481 deletions(-) create mode 100644 COLUMN_VISIBILITY_SOLUTIONS.md rename src/api/professional/{ => salaries}/professionalyearbounds.ts (100%) rename src/api/professional/{ => salaries}/salaryexportrecord.ts (100%) rename src/api/professional/{ => salaries}/teacherawardtax.ts (100%) rename src/api/professional/{ => salaries}/teacherpayslip.ts (100%) rename src/api/professional/{ => salaries}/teachersalary.ts (100%) create mode 100644 src/components/GenderTag/index.vue create mode 100644 src/components/StatusTag/README.md create mode 100644 src/components/StatusTag/index.vue create mode 100644 src/components/TableColumn/Provider.vue create mode 100644 src/components/TableColumn/README.md create mode 100644 src/components/TableColumn/index.vue create mode 100644 src/components/TableColumnControl/README.md create mode 100644 src/components/TableColumnControl/USAGE.md create mode 100644 src/components/TableColumnControl/index.vue create mode 100644 src/composables/useTableColumns.ts create mode 100644 src/directives/v-column-visible.ts create mode 100644 src/views/professional/teacherbase/action-dropdown.vue diff --git a/COLUMN_VISIBILITY_SOLUTIONS.md b/COLUMN_VISIBILITY_SOLUTIONS.md new file mode 100644 index 0000000..f122150 --- /dev/null +++ b/COLUMN_VISIBILITY_SOLUTIONS.md @@ -0,0 +1,163 @@ +# 列显示/隐藏方案对比 + +## 方案1:TableColumn 包装组件 ⭐推荐 + +### 优点 +- ✅ 使用简单,只需替换组件名 +- ✅ 完全兼容 `el-table-column` 的所有属性和插槽 +- ✅ 代码清晰,易于维护 +- ✅ 无需修改现有代码结构 + +### 缺点 +- ❌ 需要创建一个新组件 +- ❌ 需要在使用的地方 provide `isColumnVisible` 函数 + +### 使用示例 + +```vue + + + +``` + +--- + +## 方案2:自定义指令 v-column-visible + +### 优点 +- ✅ 可以保留 `el-table-column` +- ✅ 使用相对简单 + +### 缺点 +- ❌ 仍然需要在每个列上添加指令 +- ❌ 实现较复杂,需要操作 DOM +- ❌ 可能不够优雅 + +### 使用示例 + +```vue + +``` + +--- + +## 方案3:使用 computed 动态生成列配置 + +### 优点 +- ✅ 最彻底,完全控制列的渲染 +- ✅ 性能最好(只渲染可见的列) + +### 缺点 +- ❌ 需要重构现有代码,改动较大 +- ❌ 需要将列配置抽离出来 +- ❌ 插槽处理较复杂 + +### 使用示例 + +```vue + + + +``` + +--- + +## 方案4:在 el-table 层面使用 provide + 自动处理 + +### 优点 +- ✅ 在表格层面统一处理 +- ✅ 子组件自动继承 + +### 缺点 +- ❌ 实现最复杂 +- ❌ 需要修改 Element Plus 的渲染逻辑 +- ❌ 可能影响性能 + +--- + +## 推荐方案 + +**推荐使用方案1(TableColumn 包装组件)**,因为: +1. 使用最简单,只需替换组件名 +2. 完全兼容现有代码 +3. 易于维护和理解 +4. 性能良好 + +## 迁移步骤(方案1) + +1. 导入组件: +```vue +import TableColumnProvider from '/@/components/TableColumn/Provider.vue' +import TableColumn from '/@/components/TableColumn/index.vue' +``` + +2. 用 `TableColumnProvider` 包裹所有列,并传入 `isColumnVisible`: +```vue + + + +``` + +3. 将所有 `el-table-column` 替换为 `TableColumn`,并移除 `v-if`: +```vue + + + + + +``` + diff --git a/docs/按钮样式规范.md b/docs/按钮样式规范.md index 73c6207..882fb0f 100644 --- a/docs/按钮样式规范.md +++ b/docs/按钮样式规范.md @@ -100,6 +100,7 @@ | 设置/配置 | `Setting` | | 锁定/解锁 | `Lock` | | 用户相关 | `User` | +| 调动/转换 | `Switch`| ### 使用示例 diff --git a/package.json b/package.json index 9b34ace..7dc922b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "driver.js": "^0.9.8", "echarts": "^5.4.1", "element-plus": "2.5.5", + "file-saver": "^2.0.5", "form-create-designer": "3.2.11-oem", "highlight.js": "^11.7.0", "html-to-image": "^1.11.13", @@ -70,7 +71,8 @@ "vue-router": "4.1.6", "vue3-tree-org": "^4.2.2", "vue3-video-play": "1.3.1-beta.6", - "vuedraggable": "^4.1.0" + "vuedraggable": "^4.1.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@swc/core": "1.6.13", diff --git a/src/api/admin/dict.ts b/src/api/admin/dict.ts index 92aca23..2f5a531 100644 --- a/src/api/admin/dict.ts +++ b/src/api/admin/dict.ts @@ -18,6 +18,15 @@ export function getTypeValue(type: string | number) { }); } +// 批量获取字典类型值 +export function getDictsByTypes(types: string[]) { + return request({ + url: '/admin/dict/item/typeList', + method: 'post', + data: types, + }); +} + export function fetchList(query: any) { return request({ url: '/admin/dict/list', diff --git a/src/api/professional/professionalyearbounds.ts b/src/api/professional/salaries/professionalyearbounds.ts similarity index 100% rename from src/api/professional/professionalyearbounds.ts rename to src/api/professional/salaries/professionalyearbounds.ts diff --git a/src/api/professional/salaryexportrecord.ts b/src/api/professional/salaries/salaryexportrecord.ts similarity index 100% rename from src/api/professional/salaryexportrecord.ts rename to src/api/professional/salaries/salaryexportrecord.ts diff --git a/src/api/professional/teacherawardtax.ts b/src/api/professional/salaries/teacherawardtax.ts similarity index 100% rename from src/api/professional/teacherawardtax.ts rename to src/api/professional/salaries/teacherawardtax.ts diff --git a/src/api/professional/teacherpayslip.ts b/src/api/professional/salaries/teacherpayslip.ts similarity index 100% rename from src/api/professional/teacherpayslip.ts rename to src/api/professional/salaries/teacherpayslip.ts diff --git a/src/api/professional/teachersalary.ts b/src/api/professional/salaries/teachersalary.ts similarity index 100% rename from src/api/professional/teachersalary.ts rename to src/api/professional/salaries/teachersalary.ts diff --git a/src/components/GenderTag/index.vue b/src/components/GenderTag/index.vue new file mode 100644 index 0000000..e613c73 --- /dev/null +++ b/src/components/GenderTag/index.vue @@ -0,0 +1,114 @@ + + + + + + diff --git a/src/components/StatusTag/README.md b/src/components/StatusTag/README.md new file mode 100644 index 0000000..8c4b0ff --- /dev/null +++ b/src/components/StatusTag/README.md @@ -0,0 +1,149 @@ +# StatusTag 组件 + +状态标签组件,用于显示状态值对应的标签文本,支持自定义样式和颜色。 + +## 功能特性 + +- ✅ 支持标签样式和纯文本样式两种显示模式 +- ✅ 支持自定义类型映射(typeMap)和颜色映射(colorMap) +- ✅ 内置默认样式('1' → warning/dark,'0' → primary/light) +- ✅ 外部传入优先,支持覆盖默认样式 + +## Props + +| 参数 | 说明 | 类型 | 默认值 | 必填 | +|------|------|------|--------|------| +| value | 当前状态值 | `string \| number` | `''` | 是 | +| options | 选项列表,格式:`[{label: '是', value: '1'}, {label: '否', value: '0'}]` | `Option[]` | `[]` | 是 | +| showTag | 是否显示标签样式(有边框和背景),`false` 为纯文本样式 | `boolean` | `true` | 否 | +| typeMap | 自定义类型映射,用于标签模式,如:`{'1': {type: 'warning', effect: 'dark'}}` | `Record` | `{}` | 否 | +| colorMap | 自定义颜色映射,用于纯文本模式,如:`{'1': '#E6A23C'}` | `Record` | `{}` | 否 | + +### Option 接口 + +```typescript +interface Option { + label: string // 显示文本 + value: string | number // 选项值 +} +``` + +## 默认样式 + +组件内置了默认的样式映射,无需传入 `typeMap` 或 `colorMap` 即可使用: + +- **值 '1' 或 1**: + - 标签模式:`warning` 类型 + `dark` 效果(橙色深色) + - 纯文本模式:`var(--el-color-warning)`(橙色) + +- **值 '0' 或 0**: + - 标签模式:`primary` 类型 + `light` 效果(蓝色浅色) + - 纯文本模式:`var(--el-color-primary)`(蓝色) + +- **其他值**: + - 标签模式:`info` 类型 + `light` 效果(灰色) + - 纯文本模式:默认文本颜色 + +## 使用示例 + +### 基础用法 + +```vue + + + +``` + +### 纯文本模式(无边框和背景) + +```vue + +``` + +### 自定义类型映射 + +```vue + +``` + +### 自定义颜色映射(纯文本模式) + +```vue + +``` + +### 完整示例 + +```vue + + + +``` + +## 注意事项 + +1. **必须传入 `options`**:组件不提供默认选项,必须通过 `options` prop 传入选项列表 +2. **值匹配**:组件会自动匹配字符串和数字类型的值(如 `'1'` 和 `1` 会被视为相同) +3. **样式优先级**:外部传入的 `typeMap` 和 `colorMap` 会覆盖默认样式 +4. **未匹配值**:如果 `value` 在 `options` 中找不到对应项,会显示 `'-'` + +## 样式说明 + +### 标签模式(showTag: true) + +使用 Element Plus 的 `el-tag` 组件,支持所有 `el-tag` 的类型和效果: +- `type`: `success` | `info` | `warning` | `danger` | `primary` +- `effect`: `dark` | `light` | `plain` + +### 纯文本模式(showTag: false) + +使用纯文本显示,通过 CSS 颜色控制样式,支持任何颜色值(CSS 变量、十六进制、RGB 等)。 + diff --git a/src/components/StatusTag/index.vue b/src/components/StatusTag/index.vue new file mode 100644 index 0000000..5916a7b --- /dev/null +++ b/src/components/StatusTag/index.vue @@ -0,0 +1,140 @@ + + + + + + diff --git a/src/components/TableColumn/Provider.vue b/src/components/TableColumn/Provider.vue new file mode 100644 index 0000000..b981fbf --- /dev/null +++ b/src/components/TableColumn/Provider.vue @@ -0,0 +1,17 @@ + + + + diff --git a/src/components/TableColumn/README.md b/src/components/TableColumn/README.md new file mode 100644 index 0000000..61d7ac8 --- /dev/null +++ b/src/components/TableColumn/README.md @@ -0,0 +1,76 @@ +# TableColumn 组件 + +一个自动处理列显示/隐藏的 `el-table-column` 包装组件。 + +## 功能 + +- 自动根据 `isColumnVisible` 函数控制列的显示/隐藏 +- 完全兼容 `el-table-column` 的所有属性和插槽 +- 无需在每个列上手动添加 `v-if="isColumnVisible('xxx')"` + +## 使用方法 + +### 1. 在父组件中提供 `isColumnVisible` 函数 + +```vue + + + +``` + +### 2. 直接使用(如果已经在父组件中 provide) + +```vue + + + +``` + +## Props + +继承 `el-table-column` 的所有属性,包括: +- `prop`: 列的字段名 +- `label`: 列的标题 +- `width`: 列宽度 +- `min-width`: 最小宽度 +- `fixed`: 是否固定 +- 等等... + +## Slots + +继承 `el-table-column` 的所有插槽,包括: +- `default`: 默认插槽 +- `header`: 表头插槽 +- 等等... + +## 注意事项 + +1. 需要在父组件中使用 `provide` 提供 `isColumnVisible` 函数,或者使用 `TableColumnProvider` 组件 +2. `isColumnVisible` 函数接收 `prop` 或 `label` 作为参数 +3. 如果既没有 `prop` 也没有 `label`,列将始终显示 + diff --git a/src/components/TableColumn/index.vue b/src/components/TableColumn/index.vue new file mode 100644 index 0000000..f0bf151 --- /dev/null +++ b/src/components/TableColumn/index.vue @@ -0,0 +1,64 @@ + + + + diff --git a/src/components/TableColumnControl/README.md b/src/components/TableColumnControl/README.md new file mode 100644 index 0000000..1528d15 --- /dev/null +++ b/src/components/TableColumnControl/README.md @@ -0,0 +1,162 @@ +# TableColumnControl 表格列显隐控制组件 + +一个通用的表格列显示/隐藏控制组件,支持动态控制表格列的显示状态。 + +## 功能特性 + +- ✅ 动态控制表格列的显示/隐藏 +- ✅ 支持全选/全不选 +- ✅ 支持重置为默认值 +- ✅ 支持 localStorage 持久化 +- ✅ 支持固定列(不可隐藏) +- ✅ 支持始终显示的列 +- ✅ 可自定义触发按钮样式 + +## 使用方法 + +### 基础用法 + +```vue + + + +``` + +### 使用 localStorage 持久化 + +```vue + +``` + +### 固定列(不可隐藏) + +```vue +const tableColumns = [ + { prop: 'name', label: '姓名', fixed: 'left' }, // 固定左侧,不可隐藏 + { prop: 'age', label: '年龄' }, + { prop: 'action', label: '操作', fixed: 'right' } // 固定右侧,不可隐藏 +] +``` + +### 始终显示的列 + +```vue +const tableColumns = [ + { prop: 'name', label: '姓名', alwaysShow: true }, // 始终显示,不可隐藏 + { prop: 'age', label: '年龄' } +] +``` + +### 自定义触发按钮 + +```vue + + + + + + + + +``` + +## Props + +| 参数 | 说明 | 类型 | 默认值 | +|------|------|------|--------| +| columns | 表格列配置数组 | Column[] | 必填 | +| modelValue | 当前显示的列的 prop 或 label 数组 | string[] | - | +| storageKey | localStorage 存储的 key,用于持久化 | string | - | +| triggerType | 触发按钮的类型 | 'default' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info' \| 'text' | 'default' | +| triggerSize | 触发按钮的大小 | 'large' \| 'default' \| 'small' | 'default' | +| triggerCircle | 触发按钮是否为圆形 | boolean | false | +| triggerText | 触发按钮的文字 | string | '' | +| triggerLink | 触发按钮是否为链接样式 | boolean | false | + +## Events + +| 事件名 | 说明 | 回调参数 | +|--------|------|----------| +| update:modelValue | 显示的列变化时触发 | (columns: string[]) | +| change | 显示的列变化时触发 | (columns: string[]) | + +## Column 接口 + +```typescript +interface Column { + prop?: string // 列的 prop,用于标识列 + label: string // 列的标签 + fixed?: boolean | 'left' | 'right' // 固定列,不可隐藏 + alwaysShow?: boolean // 始终显示的列,不可隐藏 + [key: string]: any // 其他属性 +} +``` + +## 插槽 + +| 插槽名 | 说明 | +|--------|------| +| trigger | 自定义触发按钮内容 | + diff --git a/src/components/TableColumnControl/USAGE.md b/src/components/TableColumnControl/USAGE.md new file mode 100644 index 0000000..9a9d4f5 --- /dev/null +++ b/src/components/TableColumnControl/USAGE.md @@ -0,0 +1,196 @@ +# TableColumnControl 使用指南 + +## 两种使用方式 + +### 方式一:自动提取(推荐)✨ + +自动从 `el-table` 中提取列配置,无需手动配置。 + +```vue + + + +``` + +**优点:** +- ✅ 无需手动配置列信息 +- ✅ 自动同步表格列的变化 +- ✅ 代码更简洁 + +### 方式二:手动配置 + +手动传入列配置,适合需要自定义列信息的场景。 + +```vue + + + +``` + +## 自动提取的配置选项 + +```typescript +interface AutoExtractOptions { + // 默认隐藏的列(prop 或 label) + defaultHidden?: string[] + + // 始终显示的列(prop 或 label) + alwaysShow?: string[] + + // 列配置映射(用于自定义列的显示名称等) + columnMap?: Record> +} +``` + +### 示例 + +```vue + +``` + +## 注意事项 + +1. **自动提取的限制**: + - 需要在表格渲染完成后才能提取列配置 + - 如果表格列是动态生成的,可能需要调用 `refreshColumns()` 方法 + +2. **固定列**: + - 使用 `fixed="left"` 或 `fixed="right"` 的列会自动标记为不可隐藏 + - 在 `alwaysShow` 中指定的列也会不可隐藏 + +3. **存储键(storageKey)**: + - 建议为每个页面使用唯一的 `storageKey`,避免列配置冲突 + - 格式建议:`页面名称-table-columns`,如 `user-list-table-columns` + +4. **性能考虑**: + - 自动提取会在组件挂载和表格更新时执行 + - 对于大型表格,建议使用手动配置以获得更好的性能 + +## 迁移指南 + +从手动配置迁移到自动提取: + +**之前(手动配置):** +```vue + +``` + +**之后(自动提取):** +```vue + +``` + +只需要: +1. 将 `:columns` 改为 `:table-ref="tableRef"` +2. 在 `el-table` 上添加 `ref="tableRef"` +3. 移除 `tableColumnConfig` 和 `isColumnVisible` 函数(如果不再需要) + diff --git a/src/components/TableColumnControl/index.vue b/src/components/TableColumnControl/index.vue new file mode 100644 index 0000000..394d08c --- /dev/null +++ b/src/components/TableColumnControl/index.vue @@ -0,0 +1,461 @@ + + + + + + + diff --git a/src/composables/useTableColumns.ts b/src/composables/useTableColumns.ts new file mode 100644 index 0000000..b0db92a --- /dev/null +++ b/src/composables/useTableColumns.ts @@ -0,0 +1,411 @@ +import { ref, computed, watch, onMounted, nextTick, type Ref } from 'vue' +import type { TableInstance } from 'element-plus' + +export interface ColumnConfig { + prop?: string + label: string + width?: number | string + minWidth?: number | string + fixed?: boolean | 'left' | 'right' + alwaysShow?: boolean + [key: string]: any +} + +/** + * 自动从 el-table 提取列配置的 composable + * @param tableRef el-table 的 ref + * @param storageKey localStorage 存储的 key,用于持久化 + * @param options 额外配置选项 + */ +export function useTableColumns( + tableRef: Ref | any, // 支持多种类型的 ref + storageKey?: string, + options?: { + // 默认隐藏的列(prop 或 label) + defaultHidden?: string[] + // 始终显示的列(prop 或 label) + alwaysShow?: string[] + // 列配置映射(用于自定义列的显示名称等) + columnMap?: Record> + } +) { + const columns = ref([]) + const visibleColumns = ref([]) + + // 获取表格实例的辅助函数 + const getTableInstance = (): TableInstance | null => { + // 方法1: 直接从 tableRef.value 获取 + if (tableRef?.value) { + return tableRef.value + } + // 方法2: 如果 tableRef 本身有 value 属性(可能是直接的 ref 对象) + if ((tableRef as any).value) { + return (tableRef as any).value + } + // 方法3: 如果 tableRef 本身就是表格实例(不应该发生,但作为备用) + if ((tableRef as any).$el) { + return tableRef as any + } + return null + } + + // 从 el-table 提取列配置 + const extractColumns = (): ColumnConfig[] => { + console.log('[useTableColumns] extractColumns 开始执行') + + const tableInstance = getTableInstance() + if (!tableInstance) { + console.warn('[useTableColumns] tableRef.value 为空') + console.warn('[useTableColumns] tableRef:', tableRef) + console.warn('[useTableColumns] tableRef?.value:', tableRef?.value) + return [] + } + + const table = tableInstance + console.log('[useTableColumns] tableInstance 存在:', table) + const extracted: ColumnConfig[] = [] + + try { + // 方法1: 从 table.store 中提取(Element Plus 内部实现) + const store = (table as any).store + console.log('[useTableColumns] store:', store) + + if (store) { + // 尝试多种路径访问列数据 + let tableColumns: any[] = [] + + if (store.states && store.states.columns) { + tableColumns = store.states.columns.value || [] + } else if (store.columns) { + tableColumns = Array.isArray(store.columns) ? store.columns : (store.columns.value || []) + } else if ((table as any).columns) { + tableColumns = Array.isArray((table as any).columns) ? (table as any).columns : ((table as any).columns.value || []) + } + + if (tableColumns.length > 0) { + tableColumns.forEach((col: any) => { + // 跳过序号列和没有 label 的列 + if (!col.label || col.type === 'index') return + + const config: ColumnConfig = { + prop: col.property || col.prop || '', + label: col.label || '', + width: col.width, + minWidth: col.minWidth, + fixed: col.fixed, + } + + // 应用自定义映射 + if (options?.columnMap && config.prop) { + const mapped = options.columnMap[config.prop] + if (mapped) { + Object.assign(config, mapped) + } + } + + // 应用 alwaysShow 配置 + if (options?.alwaysShow) { + const key = config.prop || config.label + if (key && options.alwaysShow.includes(key)) { + config.alwaysShow = true + } + } + + extracted.push(config) + }) + + if (extracted.length > 0) { + return extracted + } + } + } + + // 方法2: 从 DOM 中提取(备用方案,更可靠) + const tableEl = (table as any).$el + console.log('[useTableColumns] tableEl:', tableEl) + + if (tableEl) { + // 尝试多种选择器来查找表头 + let columnHeaders: NodeListOf | null = null + + // 方法2.1: 从主表格头部查找 + const headerWrapper = tableEl.querySelector('.el-table__header-wrapper') + console.log('[useTableColumns] headerWrapper:', headerWrapper) + if (headerWrapper) { + columnHeaders = headerWrapper.querySelectorAll('th') + console.log('[useTableColumns] 从 headerWrapper 找到列数:', columnHeaders?.length || 0) + } + + // 方法2.2: 如果找不到,从整个表格查找 + if (!columnHeaders || columnHeaders.length === 0) { + columnHeaders = tableEl.querySelectorAll('th') + console.log('[useTableColumns] 从整个表格找到列数:', columnHeaders?.length || 0) + } + + // 方法2.3: 如果还是找不到,尝试查找固定列 + if (!columnHeaders || columnHeaders.length === 0) { + const fixedLeft = tableEl.querySelector('.el-table__fixed-left') + console.log('[useTableColumns] fixedLeft:', fixedLeft) + if (fixedLeft) { + columnHeaders = fixedLeft.querySelectorAll('th') + console.log('[useTableColumns] 从 fixedLeft 找到列数:', columnHeaders?.length || 0) + } + } + + if (!columnHeaders || columnHeaders.length === 0) { + console.warn('[useTableColumns] 未找到任何列头元素') + return [] + } + + console.log('[useTableColumns] 最终找到列数量:', columnHeaders.length) + + // 创建一个映射,通过列索引匹配 prop + const propMap = new Map() + + // 尝试从表格的 slot 或配置中获取 prop + // 通过检查表格的列定义来匹配 + if (store && store.states && store.states.columns) { + const tableColumns = store.states.columns.value || [] + tableColumns.forEach((col: any, idx: number) => { + if (col.property || col.prop) { + propMap.set(idx, col.property || col.prop) + } + }) + } + + columnHeaders.forEach((th: HTMLElement, index: number) => { + const label = th.textContent?.trim() || '' + console.log(`[useTableColumns] 列 ${index}: label="${label}"`) + // 排除序号列和空列 + if (!label || label === '序号') { + console.log(`[useTableColumns] 列 ${index} 被跳过(序号列或空列)`) + return + } + + // 检查是否是固定列 + let fixed: 'left' | 'right' | undefined + const thParent = th.closest('table') + if (thParent) { + if (thParent.classList.contains('el-table__fixed-left') || th.closest('.el-table__fixed-left')) { + fixed = 'left' + } else if (thParent.classList.contains('el-table__fixed-right') || th.closest('.el-table__fixed-right')) { + fixed = 'right' + } else if (th.classList.contains('is-left')) { + fixed = 'left' + } else if (th.classList.contains('is-right')) { + fixed = 'right' + } + } + + // 尝试从属性中获取宽度 + const widthAttr = th.getAttribute('width') || th.style.width + let width: number | string | undefined + if (widthAttr) { + const numWidth = parseInt(widthAttr) + width = isNaN(numWidth) ? widthAttr : numWidth + } + + // 尝试从对应的 body cell 中获取 data-key 或其他属性来匹配 prop + let prop = propMap.get(index) + + // 如果没有找到 prop,尝试从 label 推断(简单匹配) + if (!prop) { + // 对于常见的列,可以通过 label 推断 prop + const labelToPropMap: Record = { + '是否退休': 'tied', + '姓名/工号': 'nameNo', + '性别': 'sex', + '部门': 'deptName', + '学历学位': 'dgreeName', + '职称等级': 'professionalTitle', + '岗位级别': 'stationLevelName', + '职业资格等级': 'levelName', + '职业资格工种': 'workName', + '用工性质': 'employmentNatureName', + '手机': 'telPhone', + '家庭住址': 'homeAddress', + '授课类型': 'teacherCate', + '操作': 'action', + } + prop = labelToPropMap[label] + } + + const columnConfig = { + prop: prop || `column_${index}`, + label, + width, + fixed, + } + console.log(`[useTableColumns] 提取到列配置:`, columnConfig) + extracted.push(columnConfig) + }) + + console.log('[useTableColumns] 总共提取到列数:', extracted.length) + if (extracted.length > 0) { + console.log('[useTableColumns] 提取到的所有列:', extracted) + return extracted + } + } + } catch (error) { + // 提取失败,输出错误信息 + console.error('[useTableColumns] 提取列配置时出错:', error) + } + + console.warn('[useTableColumns] 提取失败,返回空数组') + return [] + } + + // 初始化列配置 + const initColumns = async () => { + // 等待多个渲染周期,确保表格完全渲染 + await nextTick() + await new Promise(resolve => setTimeout(resolve, 300)) + await nextTick() + + let extracted = extractColumns() + + // 如果第一次提取失败,多次重试 + if (extracted.length === 0) { + for (let i = 0; i < 5; i++) { + await new Promise(resolve => setTimeout(resolve, 200)) + extracted = extractColumns() + if (extracted.length > 0) break + } + } + + if (extracted.length > 0) { + columns.value = extracted + + // 初始化可见列 + if (storageKey) { + const saved = localStorage.getItem(storageKey) + if (saved) { + try { + visibleColumns.value = JSON.parse(saved) + // 验证保存的列是否仍然存在 + const validColumns = columns.value + .filter(col => !col.alwaysShow && col.fixed === undefined) + .map(col => col.prop || col.label) + visibleColumns.value = visibleColumns.value.filter(col => validColumns.includes(col)) + } catch (e) { + initDefaultVisibleColumns() + } + } else { + initDefaultVisibleColumns() + } + } else { + initDefaultVisibleColumns() + } + } else { + console.warn('[useTableColumns] initColumns: 提取失败,未设置 columns.value') + } + } + + // 初始化默认可见列 + const initDefaultVisibleColumns = () => { + const defaultHidden = options?.defaultHidden || [] + // 默认显示所有列(除了默认隐藏的列) + visibleColumns.value = columns.value + .filter(col => { + const key = col.prop || col.label + return !col.alwaysShow && + col.fixed === undefined && + !defaultHidden.includes(key) + }) + .map(col => col.prop || col.label) + + // 如果所有列都被隐藏了,至少显示所有非固定列 + if (visibleColumns.value.length === 0 && columns.value.length > 0) { + visibleColumns.value = columns.value + .filter(col => !col.alwaysShow && col.fixed === undefined) + .map(col => col.prop || col.label) + } + } + + // 判断列是否可见 + const isColumnVisible = (propOrLabel: string): boolean => { + // 如果列配置还没有提取完成,默认显示所有列(避免初始渲染时所有列被隐藏) + if (columns.value.length === 0) { + return true + } + + const column = columns.value.find(col => + (col.prop || col.label) === propOrLabel + ) + + // 如果找不到对应的列配置,默认显示(可能是新添加的列) + if (!column) { + return true + } + + // 固定列和始终显示的列始终显示 + if (column.fixed !== undefined || column.alwaysShow) { + return true + } + + // 如果可见列列表为空,默认显示所有列(初始状态) + if (visibleColumns.value.length === 0) { + return true + } + + // 检查是否在可见列列表中 + return visibleColumns.value.includes(propOrLabel) + } + + // 更新可见列 + const updateVisibleColumns = (newColumns: string[]) => { + visibleColumns.value = newColumns + if (storageKey) { + localStorage.setItem(storageKey, JSON.stringify(newColumns)) + } + } + + // 监听表格变化,重新提取列配置 + watch(() => { + // 尝试多种方式获取 tableRef.value + if (tableRef?.value) { + return tableRef.value + } + if ((tableRef as any).value) { + return (tableRef as any).value + } + return null + }, (newVal, oldVal) => { + if (newVal && newVal !== oldVal) { + console.log('[useTableColumns] tableRef.value 变化,开始提取列配置') + // 延迟一下,确保表格已经渲染 + setTimeout(() => { + initColumns() + }, 200) + } else if (newVal && !oldVal) { + // 如果从 undefined 变为有值,也触发提取 + console.log('[useTableColumns] tableRef.value 从 undefined 变为有值,开始提取列配置') + setTimeout(() => { + initColumns() + }, 200) + } + }, { immediate: true }) // 改为 true,立即检查一次 + + // 组件挂载后初始化 + onMounted(() => { + // 延迟初始化,确保表格已经渲染 + // 如果 tableRef.value 已经有值,立即初始化;否则等待 watch 触发 + const tableInstance = getTableInstance() + if (tableInstance) { + setTimeout(() => { + initColumns() + }, 300) + } else { + console.log('[useTableColumns] onMounted: tableRef.value 还未就绪,等待 watch 触发') + } + }) + + return { + columns: computed(() => columns.value), + visibleColumns: computed(() => visibleColumns.value), + isColumnVisible, + updateVisibleColumns, + refreshColumns: initColumns, + } +} + diff --git a/src/directives/v-column-visible.ts b/src/directives/v-column-visible.ts new file mode 100644 index 0000000..a1e3a1e --- /dev/null +++ b/src/directives/v-column-visible.ts @@ -0,0 +1,40 @@ +/** + * 列显示/隐藏指令 + * 用法: v-column-visible="'propName'" 或 v-column-visible="propName" + */ +import type { Directive } from 'vue' + +interface ColumnVisibleBinding { + value: string // prop 或 label + arg?: string +} + +export const vColumnVisible: Directive = { + mounted(el, binding) { + // 获取 isColumnVisible 函数(需要从父组件注入或通过其他方式获取) + const isColumnVisible = (el as any).__isColumnVisible || (() => true) + const propOrLabel = binding.value + + if (!isColumnVisible(propOrLabel)) { + // 隐藏该列(通过隐藏父元素) + const columnElement = el.closest('.el-table-column') || el.parentElement + if (columnElement) { + (columnElement as HTMLElement).style.display = 'none' + } + } + }, + updated(el, binding) { + const isColumnVisible = (el as any).__isColumnVisible || (() => true) + const propOrLabel = binding.value + const columnElement = el.closest('.el-table-column') || el.parentElement + + if (columnElement) { + if (isColumnVisible(propOrLabel)) { + (columnElement as HTMLElement).style.display = '' + } else { + (columnElement as HTMLElement).style.display = 'none' + } + } + } +} + diff --git a/src/excel/Export2Excel.js b/src/excel/Export2Excel.js index 7970071..33c6493 100644 --- a/src/excel/Export2Excel.js +++ b/src/excel/Export2Excel.js @@ -1,7 +1,9 @@ /* eslint-disable */ -require('script-loader!file-saver'); -require('script-loader!@/excel/Blob'); -require('script-loader!xlsx/dist/xlsx.core.min'); +import { saveAs } from 'file-saver' +import * as XLSX from 'xlsx' +// 现代浏览器已原生支持 Blob,不需要 polyfill +// 如果需要支持旧浏览器,可以取消下面的注释 +// import '/@/excel/Blob.js' function generateArray(table) { var out = []; var rows = table.querySelectorAll('tr'); diff --git a/src/views/professional/common/material-show.vue b/src/views/professional/common/material-show.vue index ae7c940..f5e9cfa 100644 --- a/src/views/professional/common/material-show.vue +++ b/src/views/professional/common/material-show.vue @@ -8,8 +8,8 @@ + diff --git a/src/views/professional/teacherbase/index.vue b/src/views/professional/teacherbase/index.vue index 81463e6..473e554 100644 --- a/src/views/professional/teacherbase/index.vue +++ b/src/views/professional/teacherbase/index.vue @@ -16,7 +16,6 @@ @@ -25,7 +24,6 @@ @@ -37,7 +35,6 @@ reserve-keyword clearable @change="chooseSecDept" - style="width: 200px" placeholder="请选择部门" > @@ -106,7 +100,6 @@ filterable reserve-keyword clearable - style="width: 200px" placeholder="请选择职称等级" > - 双师 - + --> 导入信息 + + + @@ -272,99 +276,71 @@ v-loading="state.loading" border > - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -390,10 +366,9 @@ -
- +
@@ -401,6 +376,10 @@
+ + + +
@@ -409,7 +388,7 @@
- + - + - - + + - + - + - + @@ -471,20 +452,22 @@
- + - + @@ -492,7 +475,9 @@ @@ -507,7 +492,7 @@
- + - + - + @@ -579,18 +564,23 @@ 银行信息 - - - + + + - - + + - - + + +
+ + + + +
+
+ + 岗位信息 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + 工作时间 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + 其他信息 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
@@ -961,249 +1194,7 @@ - - -
- - -
-
- - 岗位信息 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - 工作时间 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - 其他信息 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
+
@@ -1405,7 +1396,7 @@ import { useMessage, useMessageBox } from '/@/hooks/message' import axios from 'axios' // 导入图标 - import { User, Phone, Document, CreditCard, Briefcase, Flag, Plus, Edit, Delete, Connection, School, Medal, Files, Clock, Setting } from '@element-plus/icons-vue' + import { User, Phone, Document, CreditCard, Briefcase, Flag, Plus, Edit, Delete, Connection, School, Medal, Files, Clock, Setting, Operation, Switch, CircleCheck, CircleClose, Refresh, Download } from '@element-plus/icons-vue' // 导入api(统一使用 /@/ 路径别名) import { @@ -1420,6 +1411,7 @@ updateInout, exportTeacherInfo as exportTeacherInfoApi } from '/@/api/professional/professionaluser/teacherbase' + import {getDictsByTypes} from '/@/api/admin/dict' import {getNationalList} from '/@/api/basic/basicnation' import {addPoliticssStatus, dePoObj} from '/@/api/professional/professionaluser/professionalpoliticsstatus' import {addAcadeRelation, delEduObj} from '/@/api/professional/professionaluser/professionalteacheracademicrelation' @@ -1444,7 +1436,11 @@ // 导入工具 import { Session } from '/@/utils/storage' + // 导入 composables(已移除,改为通过 TableColumnControl 组件暴露) // 导入组件(使用 defineAsyncComponent 异步加载,优化性能) + const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue')) + const GenderTag = defineAsyncComponent(() => import('/@/components/GenderTag/index.vue')) + const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue')) const ShowEvidence = defineAsyncComponent(() => import("../common/showEvidence.vue")); const ShowHonorEdvince = defineAsyncComponent(() => import("../common/showHonorEdvince.vue")); const ExportTeacherInfoDialog = defineAsyncComponent(() => import('./export-teacher-info.vue')); @@ -1453,6 +1449,10 @@ const PoliticsDialog = defineAsyncComponent(() => import('./politics-dialog.vue')); const RelationDialog = defineAsyncComponent(() => import('./relation-dialog.vue')); const StatusLockDialog = defineAsyncComponent(() => import('./status-lock-dialog.vue')); + const ActionDropdown = defineAsyncComponent(() => import('./action-dropdown.vue')); + const TableColumnControl = defineAsyncComponent(() => import('/@/components/TableColumnControl/index.vue')); + const TableColumn = defineAsyncComponent(() => import('/@/components/TableColumn/index.vue')); + const TableColumnProvider = defineAsyncComponent(() => import('/@/components/TableColumn/Provider.vue')); // Pagination 组件已在模板中使用,无需导入(全局组件) // 导入工具 import {validateNull} from "/@/utils/validate"; @@ -1570,6 +1570,7 @@ const qualificationList = ref([]) const degreeList = ref([]) const activeName = ref('') + const subActiveName = ref('subBaseInfo') // 子标签页当前激活的标签 const dialogImageUrl = ref('') const nationalList = ref([]) const fileList = ref([]) @@ -1588,6 +1589,33 @@ const zgzData = ref([]) const nowImage = ref("") const dataPolitics = ref([]) + + // 表格引用 + const tableRef = ref() + // TableColumnControl 组件引用 + const tableColumnControlRef = ref() + + // 调试:监听 tableRef.value 的变化 + watch(() => tableRef.value, (newVal, oldVal) => { + console.log('[index.vue] tableRef.value 变化:', { newVal, oldVal, hasValue: !!newVal }) + if (newVal) { + console.log('[index.vue] tableRef.value 已赋值,表格实例:', newVal) + const tableEl = (newVal as any).$el + console.log('[index.vue] tableRef.value.$el:', tableEl) + } + }, { immediate: true }) + + // 从 TableColumnControl 获取 isColumnVisible 和 visibleTableColumns + const isColumnVisible = (propOrLabel: string) => { + if (tableColumnControlRef.value?.isColumnVisible) { + return tableColumnControlRef.value.isColumnVisible(propOrLabel) + } + // 默认显示所有列(在 TableColumnControl 初始化之前) + return true + } + + const visibleTableColumns = ref([]) + // 静态配置数据 const defaultProps = { label: "deptName", @@ -1646,14 +1674,20 @@ ], telPhone: [ {required: true, message: '请输入正确的电话号码', trigger: 'blur'}, - {min: 11, max: 11, message: '长度为 11', trigger: 'blur'} + {pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'} ], idCard: [ {required: true, message: '请输入正确身份证', trigger: 'blur'}, - {min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur'} + {min: 15, max: 18, message: '长度在 15 到 18 个字符', trigger: 'blur'}, + {pattern: /^(\d{15}|\d{17}[\dXx])$/, message: '身份证号格式不正确(15位或18位,18位最后一位可以是X)', trigger: 'blur'} ], telPhoneTwo: [ - {required: false, min: 11, max: 11, message: '长度为 11', trigger: 'blur'} + {required: false, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'} + ], + bankNo: [ + {required: true, message: '请输入银行卡号', trigger: 'blur'}, + {pattern: /^\d{16,30}$/, message: '银行卡号格式不正确(16-30位数字)', trigger: 'blur'}, + {min: 16, max: 30, message: '银行卡号长度为 16 到 30 位', trigger: 'blur'} ], bankOpen: [ {required: true, message: '请输入开户行', trigger: 'blur'} @@ -1793,6 +1827,46 @@ } }) + // 合并表单验证规则 + const mergedRules = computed(() => { + // 将 baseInfo 的规则加上 baseInfo 前缀 + const baseInfoRules: Record = {} + Object.keys(rules).forEach(key => { + // 在编辑模式下,某些字段被禁用(如 realName, teacherNo),不需要验证 + // 根据 isAdd 状态动态调整验证规则 + if (isAdd.value) { + // 新增模式:使用原始规则 + baseInfoRules[`baseInfo.${key}`] = rules[key] + } else { + // 编辑模式:对于被禁用的字段,移除必填验证 + if (key === 'realName' || key === 'teacherNo') { + // 这些字段在编辑模式下被禁用,移除必填验证 + const rule = rules[key] + if (Array.isArray(rule)) { + baseInfoRules[`baseInfo.${key}`] = rule.filter((r: any) => !r.required) + } else { + baseInfoRules[`baseInfo.${key}`] = rule + } + } else { + baseInfoRules[`baseInfo.${key}`] = rules[key] + } + } + }) + + // 岗位信息规则:始终包含所有岗位信息规则,以便在保存时能够验证 + const stationRules: Record = {} + Object.keys(stationFormValidate).forEach(key => { + stationRules[`professionalStationRelation.${key}`] = stationFormValidate[key] + }) + + return { + // baseInfo 的规则(加上 baseInfo 前缀) + ...baseInfoRules, + // professionalStationRelation 的规则(始终包含) + ...stationRules + } + }) + // 生命周期 onMounted(async () => { // 先初始化基础数据 @@ -1801,6 +1875,17 @@ await loadSearchDictData() // 初始化完成后加载表格数据 getDataList() + + // 调试:检查 tableRef.value 是否被赋值 + await nextTick() + setTimeout(() => { + console.log('[index.vue] onMounted: tableRef.value:', tableRef.value) + if (tableRef.value) { + console.log('[index.vue] onMounted: tableRef.value.$el:', (tableRef.value as any).$el) + } else { + console.warn('[index.vue] onMounted: tableRef.value 仍未赋值') + } + }, 500) }) // 加载搜索条件字典数据 @@ -1836,16 +1921,19 @@ // 暴露方法给模板使用(如果需要) // watch 监听器 - watch(activeName, (val) => { + watch(activeName, (val, oldVal) => { //监听切换状态-计划单 - if (val != "first") { - setTimeout(() => { + // 从其他标签页切换到非"first"标签页时检查基础信息 + // 避免在初始化时触发检查(oldVal 为空字符串表示初始化) + // 同时需要确保对话框已打开(dialogFromVisible 为 true) + if (val != "first" && oldVal && oldVal !== '' && dialogFromVisible.value) { + setTimeout(() => { if (!form.baseInfo.id || form.baseInfo.id == '') { message.info("请先完善基础信息") - saveSubmit(); - } - }, 500) + activeName.value = "first" } + }, 500) + } }) watch(() => form.professionalStationRelation.employmentNature, (newVal) => { @@ -1987,10 +2075,75 @@ multiDialogRef.value?.init(val) }) } + + // 获取操作菜单项配置 + const getActionMenuItems = (row: any) => { + return [ + { + command: 'export', + label: '导出', + icon: Download, + visible: permissions.value.professional_teacherbase_export + }, + { + command: 'personnel-transfer', + label: '人员调动', + icon: Switch, + visible: permissions.value.professional_teacherbase_status_lock + }, + { + command: 'party-transfer', + label: '党员调动', + icon: Switch, + visible: permissions.value.professional_teacherbase_status_lock + }, + { + command: 'allow-inout', + label: '允许进出', + icon: CircleCheck, + visible: permissions.value.professional_teacherbase_inout && row.inoutFlag == '0' + }, + { + command: 'forbid-inout', + label: '禁止进出', + icon: CircleClose, + visible: permissions.value.professional_teacherbase_inout && row.inoutFlag == '1' + }, + { + command: 'reset-password', + label: '重置密码', + icon: Refresh, + visible: permissions.value.professional_teacherbase_resetpw + } + ] + } + + // 处理更多操作下拉菜单命令 + const handleMoreCommand = (command: string, row: any, index: number) => { + switch (command) { + case 'export': + handleDownLoadWord(row.teacherNo) + break + case 'personnel-transfer': + handleWaitExam(row, 5) + break + case 'party-transfer': + handleWaitExam(row, 6) + break + case 'allow-inout': + updateInoutFlag(row, 1) + break + case 'forbid-inout': + updateInoutFlag(row, 0) + break + case 'reset-password': + resetPassword(row) + break + } + } // 分页处理方法已由 useTable 提供,无需手动实现 - - const tableRef = ref() + // tableRef 已在上面定义(用于 useTableColumns) const exportExcel = (form: any, url: string) => { return axios({ @@ -2028,44 +2181,71 @@ const saveSubmit = () => { canSave.value = false if (activeName.value == "first") { - baseForm.value?.validate((valid: boolean) => { - if (valid) { + // 构建基本信息需要验证的字段列表(始终包含所有基本信息字段) + const baseInfoFields = Object.keys(rules).map(key => `baseInfo.${key}`) + + // 构建岗位信息需要验证的字段列表(始终包含所有岗位信息字段) + const stationFields = Object.keys(stationFormValidate).map(key => `professionalStationRelation.${key}`) + + // 根据当前子标签页决定先验证哪个 + const currentFields = subActiveName.value === 'subBaseInfo' + ? baseInfoFields + : stationFields + + const otherFields = subActiveName.value === 'subBaseInfo' + ? stationFields + : baseInfoFields + + // 先验证当前子标签页的字段 + baseForm.value?.validateField(currentFields, (isCurrentValid: boolean, currentInvalidFields: any) => { + if (!isCurrentValid) { + // 当前子标签页验证失败,提示用户 + console.log('当前子标签页验证失败的字段:', currentInvalidFields); + if (subActiveName.value === 'subBaseInfo') { + message.info("请完善基本信息"); + } else { + message.info("请完善岗位信息"); + } + setTimeout(() => { + canSave.value = true + }, 1000) + return + } + + // 当前子标签页验证通过,继续验证另一个子标签页 + baseForm.value?.validateField(otherFields, (isOtherValid: boolean, otherInvalidFields: any) => { + if (!isOtherValid) { + // 另一个子标签页验证失败,跳转到那个子标签页 + console.log('另一个子标签页验证失败的字段:', otherInvalidFields); + if (subActiveName.value === 'subBaseInfo') { + subActiveName.value = 'subStation' + message.info("基本信息已完善,请完善岗位信息"); + } else { + subActiveName.value = 'subBaseInfo' + message.info("岗位信息已完善,请完善基本信息"); + } + setTimeout(() => { + canSave.value = true + }, 1000) + return + } + + // 两个子标签页都验证通过,执行保存 addInformation(form).then((response: any) => { - const data = response.data.data; + const data = response.data; message.success('保存成功') form.baseInfo.id = data.baseId; form.professionalStationRelation.id = data.relationId; getDataList() - }).catch(()=>{ - }); - } else { - message.info("请完善基础信息"); - console.log('error submit!!'); - } + }).catch(() => { + // 错误处理 }); + setTimeout(() => { + canSave.value = true + }, 1000) + }) + }) } - - if (activeName.value == "seven") { - stationForm.value?.validate((valid: boolean) => { - if (valid) { - addInformation(form).then((response: any) => { - const data = response.data.data; - message.success('保存成功') - form.baseInfo.id = data.baseId; - form.professionalStationRelation.id = data.relationId; - getDataList() - }).catch(()=>{ - }); - } else { - message.info("请完善岗位信息"); - console.log('error submit!!'); - } - }); - } - - setTimeout(()=>{ - canSave.value = true - },1000) } // getNodeData 方法已移除,如需要请重新实现 // 政治面貌相关 @@ -2532,6 +2712,13 @@ getDicts(religiousBelief).then((response: any) => { religiousBeliefDic.value = response.data; }), + // 批量获取字典数据 + getDictsByTypes(['religious_belief', 'heath', 'teacher_cate']).then((response: any) => { + religiousBeliefDic.value = response.data.religious_belief; + healthList.value = response.data.heath; + teacherCateList.value = response.data.teacher_cate; + + }), // 民族 getNationalList().then((response: any) => { nationalList.value = response.data; @@ -2942,7 +3129,16 @@ const handleImportDialog = () => { importTeacherInfoRef.value?.init() - } + } + + // 身份证号输入限制:只允许数字和X/x,最大长度18 + const handleIdCardInput = (value: string) => { + // 只保留数字和X/x + const filtered = value.replace(/[^\dXx]/g, '') + if (filtered !== value) { + form.baseInfo.idCard = filtered + } + } + diff --git a/src/composables/useTableColumns.ts b/src/composables/useTableColumns.ts index b0db92a..ae959a5 100644 --- a/src/composables/useTableColumns.ts +++ b/src/composables/useTableColumns.ts @@ -76,23 +76,50 @@ export function useTableColumns( if (store.states && store.states.columns) { tableColumns = store.states.columns.value || [] + console.log('[useTableColumns] 从 store.states.columns 获取到列数:', tableColumns.length) + console.log('[useTableColumns] store.states.columns 内容:', tableColumns) } else if (store.columns) { tableColumns = Array.isArray(store.columns) ? store.columns : (store.columns.value || []) + console.log('[useTableColumns] 从 store.columns 获取到列数:', tableColumns.length) } else if ((table as any).columns) { tableColumns = Array.isArray((table as any).columns) ? (table as any).columns : ((table as any).columns.value || []) + console.log('[useTableColumns] 从 table.columns 获取到列数:', tableColumns.length) } if (tableColumns.length > 0) { - tableColumns.forEach((col: any) => { - // 跳过序号列和没有 label 的列 - if (!col.label || col.type === 'index') return + tableColumns.forEach((col: any, idx: number) => { + console.log(`[useTableColumns] store 列 ${idx}:`, { + type: col.type, + label: col.label, + property: col.property, + prop: col.prop, + fixed: col.fixed, + fullCol: col + }) + + // 跳过序号列 + if (col.type === 'index' || col.type === 'selection') { + console.log(`[useTableColumns] 列 ${idx} 被跳过(类型: ${col.type})`) + return + } + + // 尝试多种方式获取 label + const label = col.label || col.columnKey || col.property || col.prop || '' + + // 如果没有 label,尝试从其他属性推断 + if (!label) { + console.log(`[useTableColumns] 列 ${idx} 没有 label,尝试从其他属性推断`) + // 如果还是没有,跳过这一列 + return + } const config: ColumnConfig = { prop: col.property || col.prop || '', - label: col.label || '', + label: label, width: col.width, minWidth: col.minWidth, - fixed: col.fixed, + // Element Plus 中非固定列的 fixed 通常是 false,这里统一将 false 归一为 undefined + fixed: col.fixed ? col.fixed : undefined, } // 应用自定义映射 @@ -111,12 +138,16 @@ export function useTableColumns( } } + console.log(`[useTableColumns] 从 store 提取到列配置:`, config) extracted.push(config) }) + console.log('[useTableColumns] 从 store 总共提取到列数:', extracted.length) if (extracted.length > 0) { return extracted } + } else { + console.warn('[useTableColumns] store 中没有找到列数据') } } @@ -174,8 +205,45 @@ export function useTableColumns( } columnHeaders.forEach((th: HTMLElement, index: number) => { - const label = th.textContent?.trim() || '' - console.log(`[useTableColumns] 列 ${index}: label="${label}"`) + // 尝试多种方式获取 label 文本 + // 1. 从 .cell 元素 + const cell = th.querySelector('.cell') as HTMLElement | null + // 2. 从所有可能的文本节点 + let rawLabel = '' + + if (cell) { + rawLabel = cell.innerText || cell.textContent || '' + } else { + // 尝试从 th 的所有子元素中查找文本 + const textNodes: string[] = [] + const walker = document.createTreeWalker( + th, + NodeFilter.SHOW_TEXT, + null + ) + let node: Node | null + while ((node = walker.nextNode())) { + const text = node.textContent?.trim() + if (text) { + textNodes.push(text) + } + } + rawLabel = textNodes.join(' ') || th.innerText || th.textContent || '' + } + + const label = rawLabel.trim() + + // 调试:打印 th 的完整结构 + console.log(`[useTableColumns] DOM 列 ${index}:`, { + label, + rawLabel, + thHTML: th.innerHTML.substring(0, 100), + hasCell: !!cell, + cellText: cell?.innerText || cell?.textContent || '', + thInnerText: th.innerText, + thTextContent: th.textContent + }) + // 排除序号列和空列 if (!label || label === '序号') { console.log(`[useTableColumns] 列 ${index} 被跳过(序号列或空列)`) @@ -234,7 +302,8 @@ export function useTableColumns( prop: prop || `column_${index}`, label, width, - fixed, + // DOM 提取的 fixed 只有 left/right,这里也归一为 undefined 或 'left'/'right' + fixed: fixed ? fixed : undefined, } console.log(`[useTableColumns] 提取到列配置:`, columnConfig) extracted.push(columnConfig) @@ -304,12 +373,13 @@ export function useTableColumns( // 初始化默认可见列 const initDefaultVisibleColumns = () => { const defaultHidden = options?.defaultHidden || [] - // 默认显示所有列(除了默认隐藏的列) + // 默认显示所有列(除了默认隐藏的列和固定列/alwaysShow列) + // 注意:固定列和 alwaysShow 列不需要在 visibleColumns 中,因为它们始终显示 visibleColumns.value = columns.value .filter(col => { const key = col.prop || col.label return !col.alwaysShow && - col.fixed === undefined && + !col.fixed && !defaultHidden.includes(key) }) .map(col => col.prop || col.label) @@ -317,7 +387,7 @@ export function useTableColumns( // 如果所有列都被隐藏了,至少显示所有非固定列 if (visibleColumns.value.length === 0 && columns.value.length > 0) { visibleColumns.value = columns.value - .filter(col => !col.alwaysShow && col.fixed === undefined) + .filter(col => !col.alwaysShow && !col.fixed) .map(col => col.prop || col.label) } } @@ -339,7 +409,7 @@ export function useTableColumns( } // 固定列和始终显示的列始终显示 - if (column.fixed !== undefined || column.alwaysShow) { + if (column.fixed || column.alwaysShow) { return true } diff --git a/src/views/professional/stayschool/outercompany/index.vue b/src/views/professional/outercompany/index.vue similarity index 99% rename from src/views/professional/stayschool/outercompany/index.vue rename to src/views/professional/outercompany/index.vue index d1e215e..e8f6424 100755 --- a/src/views/professional/stayschool/outercompany/index.vue +++ b/src/views/professional/outercompany/index.vue @@ -153,7 +153,7 @@ import { useUserInfo } from '/@/stores/userInfo' import { BasicTableProps, useTable } from '/@/hooks/table' import { useMessage } from '/@/hooks/message' import { useMessageBox } from '/@/hooks/message' -import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/outercompany' +import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/stayschool/outercompany/indexSecond.vue b/src/views/professional/outercompany/indexSecond.vue similarity index 99% rename from src/views/professional/stayschool/outercompany/indexSecond.vue rename to src/views/professional/outercompany/indexSecond.vue index 49c0cfc..625ef2e 100755 --- a/src/views/professional/stayschool/outercompany/indexSecond.vue +++ b/src/views/professional/outercompany/indexSecond.vue @@ -153,7 +153,7 @@ import { useUserInfo } from '/@/stores/userInfo' import { BasicTableProps, useTable } from '/@/hooks/table' import { useMessage } from '/@/hooks/message' import { useMessageBox } from '/@/hooks/message' -import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/outercompany' +import { fetchList, addObj, putObj, delObj, getObj } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/stayschool/outercompany/indexTrain.vue b/src/views/professional/outercompany/indexTrain.vue similarity index 98% rename from src/views/professional/stayschool/outercompany/indexTrain.vue rename to src/views/professional/outercompany/indexTrain.vue index 3e3a6e5..e98ec87 100755 --- a/src/views/professional/stayschool/outercompany/indexTrain.vue +++ b/src/views/professional/outercompany/indexTrain.vue @@ -100,7 +100,7 @@ import { ref, reactive, computed } from 'vue' import { storeToRefs } from 'pinia' import { useUserInfo } from '/@/stores/userInfo' import { BasicTableProps, useTable } from '/@/hooks/table' -import { fetchList } from '/@/api/professional/outercompany' +import { fetchList } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/stayschool/outercompanyemployee/index.vue b/src/views/professional/outercompanyemployee/index.vue similarity index 99% rename from src/views/professional/stayschool/outercompanyemployee/index.vue rename to src/views/professional/outercompanyemployee/index.vue index 968727d..ec99152 100755 --- a/src/views/professional/stayschool/outercompanyemployee/index.vue +++ b/src/views/professional/outercompanyemployee/index.vue @@ -405,8 +405,8 @@ import { delObj, batchDel, resetPassWord -} from '/@/api/professional/outercompanyemployee' -import { getList as getCompanyList } from '/@/api/professional/outercompany' +} from '/@/api/professional/stayschool/outercompanyemployee' +import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/stayschool/outercompanyemployee/indexSecond.vue b/src/views/professional/outercompanyemployee/indexSecond.vue similarity index 99% rename from src/views/professional/stayschool/outercompanyemployee/indexSecond.vue rename to src/views/professional/outercompanyemployee/indexSecond.vue index 3fc8ffd..54d0374 100755 --- a/src/views/professional/stayschool/outercompanyemployee/indexSecond.vue +++ b/src/views/professional/outercompanyemployee/indexSecond.vue @@ -406,8 +406,8 @@ import { delObj, batchDel, resetPassWord -} from '/@/api/professional/outercompanyemployee' -import { getList as getCompanyList } from '/@/api/professional/outercompany' +} from '/@/api/professional/stayschool/outercompanyemployee' +import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/stayschool/outercompanyemployee/indexTrain.vue b/src/views/professional/outercompanyemployee/indexTrain.vue similarity index 99% rename from src/views/professional/stayschool/outercompanyemployee/indexTrain.vue rename to src/views/professional/outercompanyemployee/indexTrain.vue index 125ebf1..b26bc1e 100755 --- a/src/views/professional/stayschool/outercompanyemployee/indexTrain.vue +++ b/src/views/professional/outercompanyemployee/indexTrain.vue @@ -385,8 +385,8 @@ import { delObj, batchDel, resetPassWord -} from '/@/api/professional/outercompanyemployee' -import { getList as getCompanyList } from '/@/api/professional/outercompany' +} from '/@/api/professional/stayschool/outercompanyemployee' +import { getList as getCompanyList } from '/@/api/professional/stayschool/outercompany' // 使用 Pinia store const userInfoStore = useUserInfo() diff --git a/src/views/professional/professionalqualificationrelation/form.vue b/src/views/professional/professionalqualificationrelation/form.vue index 0ea73b5..8e054e8 100644 --- a/src/views/professional/professionalqualificationrelation/form.vue +++ b/src/views/professional/professionalqualificationrelation/form.vue @@ -96,8 +96,8 @@ import { ref, reactive, computed } from 'vue' import { Session } from '/@/utils/storage' import { useMessage } from '/@/hooks/message' -import { getMyTeacherNo, updateOtherInfo } from '/@/api/professional/professionaluser/teacherbase' -import { putObj } from '/@/api/professional/professionaluser/professionalqualificationrelation' +import { getMyTeacherNo } from '/@/api/professional/professionaluser/teacherbase' +import { addObj } from '/@/api/professional/professionaluser/professionalqualificationrelation' import { checkLocked } from '/@/api/professional/professionalstatuslock' import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig' import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype' @@ -284,34 +284,26 @@ const dialogSubmit = async () => { if (valid) { submitLoading.value = true try { + // 统一使用 addObj 接口(新增和编辑都使用同一个接口) + // 确保 evidenceA 或 materialA 有值 + if (!dataForm.evidenceA && dataForm.materialA) { + dataForm.evidenceA = dataForm.materialA + } + if (dataForm.id) { - // 编辑:使用 putObj 接口(管理员编辑) + // 编辑模式 dataForm.state = '0' - await putObj(dataForm) + await addObj(dataForm) message.success("修改成功") } else { - // 新增:使用 updateOtherInfo 接口(与 MultiDialog 保持一致) - const submitData: any = { - type: 3, // 职业资格类型 - teacherNo: dataForm.teacherNo, - worker: dataForm.worker, - qualificationConfigId: dataForm.qualificationConfigId, - certificateTime: dataForm.certificateTime, - certificateNumber: dataForm.certificateNumber, - mateA: dataForm.evidenceA || dataForm.materialA // 使用 mateA 字段(与 MultiDialog 一致) - } - - const res = await updateOtherInfo(submitData) - if (res.data == '-1') { - message.warning("当前不允许提交") - } else { - message.success("提交成功") - } + // 新增模式 + await addObj(dataForm) + message.success("提交成功") } dialogVisible.value = false emit('refreshData') } catch (error: any) { - message.error(error?.msg || '操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 } finally { submitLoading.value = false } diff --git a/src/views/professional/professionalteacheracademicrelation/form.vue b/src/views/professional/professionalteacheracademicrelation/form.vue index 56d9a82..f1b0421 100644 --- a/src/views/professional/professionalteacheracademicrelation/form.vue +++ b/src/views/professional/professionalteacheracademicrelation/form.vue @@ -147,8 +147,8 @@ import { ref, reactive, computed } from 'vue' import { Session } from '/@/utils/storage' import { useMessage } from '/@/hooks/message' -import { getMyTeacherNo, updateOtherInfo } from '/@/api/professional/professionaluser/teacherbase' -import { putObj } from '/@/api/professional/professionaluser/professionalteacheracademicrelation' +import { getMyTeacherNo } from '/@/api/professional/professionaluser/teacherbase' +import { addObj } from '/@/api/professional/professionaluser/professionalteacheracademicrelation' import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig' import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig' import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig' @@ -370,42 +370,30 @@ const dialogSubmit = async () => { if (valid) { submitLoading.value = true try { + // 统一使用 addObj 接口(新增和编辑都使用同一个接口) + // 确保 qualificationImg 或 materialA 有值 + if (!dataForm.qualificationImg && dataForm.materialA) { + dataForm.qualificationImg = dataForm.materialA + } + // 确保 degreeImg 或 materialB 有值 + if (!dataForm.degreeImg && dataForm.materialB) { + dataForm.degreeImg = dataForm.materialB + } + if (dataForm.id) { - // 编辑:使用 putObj 接口(管理员编辑) + // 编辑模式 dataForm.state = '0' - await putObj(dataForm) + await addObj(dataForm) message.success("修改成功") } else { - // 新增:使用 updateOtherInfo 接口(与 MultiDialog 保持一致) - // 注意:MultiDialog 中 type 字段在提交时会被设置为 val(1),但表单中也有 type 字段用于教育类型 - // 这里直接使用 dataForm 的所有字段,后端应该能够处理 - const submitData: any = { - type: 1, // 学历更新类型(固定值,会覆盖表单中的 type) - teacherNo: dataForm.teacherNo, - graduateTime: dataForm.graduateTime, - qualificationConfigId: dataForm.qualificationConfigId, - degreeConfigId: dataForm.degreeConfigId, - graduateSchool: dataForm.graduateSchool, - major: dataForm.major, - certificateNumber: dataForm.certificateNumber, - mateA: dataForm.qualificationImg || dataForm.materialA, // 学历证书 - mateB: dataForm.degreeImg || dataForm.materialB // 学位证书 - } - - // 注意:MultiDialog 中教育类型字段也是 type,但在提交时会被覆盖为 val(1) - // 如果后端需要教育类型,可能需要单独传递,这里先不传,保持与 MultiDialog 一致 - - const res = await updateOtherInfo(submitData) - if (res.data == '-1') { - message.warning("当前不允许提交") - } else { - message.success("提交成功") - } + // 新增模式 + await addObj(dataForm) + message.success("提交成功") } dialogVisible.value = false emit('refreshData') } catch (error: any) { - message.error(error?.msg || '操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 } finally { submitLoading.value = false } diff --git a/src/views/professional/professionalteachercertificaterelation/form.vue b/src/views/professional/professionalteachercertificaterelation/form.vue index f15932e..73c7f61 100644 --- a/src/views/professional/professionalteachercertificaterelation/form.vue +++ b/src/views/professional/professionalteachercertificaterelation/form.vue @@ -97,8 +97,8 @@ import { ref, reactive, computed } from 'vue' import { Session } from '/@/utils/storage' import { useMessage } from '/@/hooks/message' -import { getMyTeacherNo, updateOtherInfo } from '/@/api/professional/professionaluser/teacherbase' -import { putObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation' +import { getMyTeacherNo } from '/@/api/professional/professionaluser/teacherbase' +import { addObj } from '/@/api/professional/professionaluser/professionalteachercertificaterelation' import { getTeacherCertificateList } from '/@/api/professional/rsbase/professionalteachercertificateconf' import { checkLocked } from '/@/api/professional/professionalstatuslock' @@ -280,33 +280,30 @@ const dialogSubmit = async () => { if (valid) { submitLoading.value = true try { + // 统一使用 addObj 接口(新增和编辑都使用同一个接口) + // 确保 evidenceA 或 materialA 有值 + if (!dataForm.evidenceA && dataForm.materialA) { + dataForm.evidenceA = dataForm.materialA + } + // 确保 evidenceB 或 materialB 有值 + if (!dataForm.evidenceB && dataForm.materialB) { + dataForm.evidenceB = dataForm.materialB + } + if (dataForm.id) { - // 编辑:使用 putObj 接口(管理员编辑) + // 编辑模式 dataForm.state = '0' - await putObj(dataForm) + await addObj(dataForm) message.success("修改成功") } else { - // 新增:使用 updateOtherInfo 接口(与 MultiDialog 保持一致) - // 注意:MultiDialog 的 type=0 表单只有 certificateConfId 和 certificateNumber,没有 certificateTime - const submitData: any = { - type: 0, // 教师资格证类型 - teacherNo: dataForm.teacherNo, - certificateConfId: dataForm.certificateConfId, - certificateNumber: dataForm.certificateNumber, - mateA: dataForm.evidenceA || dataForm.materialA // 使用 mateA 字段(与 MultiDialog 一致) - } - - const res = await updateOtherInfo(submitData) - if (res.data == '-1') { - message.warning("当前不允许提交") - } else { - message.success("提交成功") - } + // 新增模式 + await addObj(dataForm) + message.success("提交成功") } dialogVisible.value = false emit('refreshData') } catch (error: any) { - message.error(error?.msg || '操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 } finally { submitLoading.value = false } diff --git a/src/views/professional/professionalteacherhonor/form.vue b/src/views/professional/professionalteacherhonor/form.vue index 5e0d6d0..09dd438 100644 --- a/src/views/professional/professionalteacherhonor/form.vue +++ b/src/views/professional/professionalteacherhonor/form.vue @@ -64,8 +64,8 @@ import { ref, reactive, computed } from 'vue' import { Session } from '/@/utils/storage' import { useMessage } from '/@/hooks/message' -import { getMyTeacherNo, updateOtherInfo } from '/@/api/professional/professionaluser/teacherbase' -import { putObj } from '/@/api/professional/professionaluser/professionalteacherhonor' +import { getMyTeacherNo } from '/@/api/professional/professionaluser/teacherbase' +import { addObj } from '/@/api/professional/professionaluser/professionalteacherhonor' import { checkLocked } from '/@/api/professional/professionalstatuslock' // Emits @@ -215,33 +215,26 @@ const dialogSubmit = async () => { if (valid) { submitLoading.value = true try { + // 统一使用 addObj 接口(新增和编辑都使用同一个接口) + // 确保 attachment 或 materialA 有值 + if (!dataForm.attachment && dataForm.materialA) { + dataForm.attachment = dataForm.materialA + } + if (dataForm.id) { - // 编辑:使用 putObj 接口(管理员编辑) + // 编辑模式 dataForm.state = '0' - await putObj(dataForm) + await addObj(dataForm) message.success("修改成功") } else { - // 新增:使用 updateOtherInfo 接口(与 MultiDialog 保持一致) - const submitData: any = { - type: 4, // 综合表彰类型 - teacherNo: dataForm.teacherNo, - honor: dataForm.honor, - honorCompany: dataForm.honorCompany, - year: dataForm.year, - mateA: dataForm.attachment || dataForm.materialA // 使用 mateA 字段(与 MultiDialog 一致) - } - - const res = await updateOtherInfo(submitData) - if (res.data == '-1') { - message.warning("当前不允许提交") - } else { - message.success("提交成功") - } + // 新增模式 + await addObj(dataForm) + message.success("提交成功") } dialogVisible.value = false emit('refreshData') } catch (error: any) { - message.error(error?.msg || '操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 } finally { submitLoading.value = false } diff --git a/src/views/professional/professionaltitlerelation/form.vue b/src/views/professional/professionaltitlerelation/form.vue index cd8ba39..19c1123 100644 --- a/src/views/professional/professionaltitlerelation/form.vue +++ b/src/views/professional/professionaltitlerelation/form.vue @@ -229,7 +229,7 @@ const openDialog = async (row?: any) => { return } } catch (error) { - message.error('操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 return } } @@ -306,7 +306,7 @@ const dialogSubmit = async () => { dialogVisible.value = false emit('refreshData') } catch (error: any) { - message.error(error?.msg || '操作失败') + // 错误处理已在数据请求层统一处理,此处不需要提示 } finally { submitLoading.value = false } diff --git a/src/views/professional/salaries/professionalyearbounds/index.vue b/src/views/professional/professionalyearbounds/index.vue similarity index 100% rename from src/views/professional/salaries/professionalyearbounds/index.vue rename to src/views/professional/professionalyearbounds/index.vue diff --git a/src/views/professional/salaries/salaryexportrecord/index.vue b/src/views/professional/salaryexportrecord/index.vue similarity index 100% rename from src/views/professional/salaries/salaryexportrecord/index.vue rename to src/views/professional/salaryexportrecord/index.vue diff --git a/src/views/professional/salaries/teacherawardtax/importAwardTax.vue b/src/views/professional/teacherawardtax/importAwardTax.vue similarity index 100% rename from src/views/professional/salaries/teacherawardtax/importAwardTax.vue rename to src/views/professional/teacherawardtax/importAwardTax.vue diff --git a/src/views/professional/salaries/teacherawardtax/index.vue b/src/views/professional/teacherawardtax/index.vue similarity index 100% rename from src/views/professional/salaries/teacherawardtax/index.vue rename to src/views/professional/teacherawardtax/index.vue diff --git a/src/views/professional/teacherbase/index.vue b/src/views/professional/teacherbase/index.vue index 473e554..217fb19 100644 --- a/src/views/professional/teacherbase/index.vue +++ b/src/views/professional/teacherbase/index.vue @@ -369,24 +369,26 @@
- -
-
- 照片 -
-
- - -
-
- - 基本信息 + +
+ +
+
+ 照片 +
- + + +
+
+ + 基本信息 +
+ +
@@ -886,19 +889,23 @@
-
+
政治面貌信息
- + 新增
- + + + - + + + - - -
@@ -1043,15 +1038,11 @@
-
+
职称信息
- - - 新增 -
@@ -1086,18 +1077,6 @@ {{ scope.row.createTime ? new Date(scope.row.createTime).toLocaleDateString() : '-' }} - - -
@@ -1105,15 +1084,11 @@
-
+
职业资格信息
- - - 新增 -
@@ -1143,18 +1118,6 @@ {{ scope.row.createTime ? new Date(scope.row.createTime).toLocaleDateString() : '-' }} - - -
@@ -1198,6 +1161,12 @@
+
+
+ + 岗位变更信息 +
+
@@ -1213,6 +1182,12 @@
+
+
+ + 党员调动信息 +
+
@@ -1236,6 +1211,12 @@
+
+
+ + 综合表彰信息 +
+
@@ -1251,6 +1232,12 @@
+
+
+ + 教师论文信息 +
+
@@ -1285,6 +1272,12 @@
+
+
+ + 教材列表信息 +
+
@@ -1312,6 +1305,12 @@
+
+
+ + 课题列表信息 +
+
@@ -1342,6 +1341,7 @@