This commit is contained in:
2026-01-22 13:38:10 +08:00
parent b350322626
commit 313fe64475
151 changed files with 13060 additions and 4411 deletions

View File

@@ -0,0 +1,224 @@
/**
* 批量添加 TableColumnControl 组件的脚本
* 用于为 stuwork 文件夹下的所有页面添加列显隐控制功能
*/
const fs = require('fs')
const path = require('path')
// 需要处理的目录
const targetDir = path.join(__dirname, '../src/views/stuwork')
// 获取所有 index.vue 文件
function getAllIndexFiles(dir) {
const files = []
const items = fs.readdirSync(dir, { withFileTypes: true })
for (const item of items) {
const fullPath = path.join(dir, item.name)
if (item.isDirectory()) {
const indexPath = path.join(fullPath, 'index.vue')
if (fs.existsSync(indexPath)) {
files.push(indexPath)
}
}
}
return files
}
// 检查文件是否已经包含 TableColumnControl
function hasTableColumnControl(content) {
return content.includes('TableColumnControl') || content.includes('table-column-control')
}
// 提取表格列配置
function extractTableColumns(content) {
const columns = []
const columnRegex = /<el-table-column\s+([^>]+)>/g
let match
while ((match = columnRegex.exec(content)) !== null) {
const attrs = match[1]
const propMatch = attrs.match(/prop=["']([^"']+)["']/)
const labelMatch = attrs.match(/label=["']([^"']+)["']/)
const typeMatch = attrs.match(/type=["']([^"']+)["']/)
const fixedMatch = attrs.match(/fixed=["']([^"']+)["']/)
if (typeMatch && typeMatch[1] === 'index') {
// 序号列,跳过
continue
}
if (labelMatch && labelMatch[1] === '操作') {
// 操作列,标记为 alwaysShow
columns.push({
prop: '操作',
label: '操作',
alwaysShow: true,
fixed: fixedMatch ? fixedMatch[1] : false
})
continue
}
if (propMatch && labelMatch) {
columns.push({
prop: propMatch[1],
label: labelMatch[1],
alwaysShow: false,
fixed: fixedMatch ? fixedMatch[1] : false
})
}
}
return columns
}
// 添加 TableColumnControl 导入
function addImport(content) {
if (content.includes("import TableColumnControl")) {
return content
}
// 查找 import 语句的位置
const importRegex = /import\s+.*from\s+['"][^'"]+['"]/g
const lastImportMatch = [...content.matchAll(importRegex)].pop()
if (lastImportMatch) {
const insertPos = lastImportMatch.index + lastImportMatch[0].length
const newImport = "\nimport TableColumnControl from '/@/components/TableColumnControl/index.vue'"
return content.slice(0, insertPos) + newImport + content.slice(insertPos)
}
return content
}
// 添加 useRoute 导入
function addUseRouteImport(content) {
if (content.includes("import.*useRoute")) {
return content
}
// 查找 vue-router 相关的导入
const routerImportRegex = /import\s+.*from\s+['"]vue-router['"]/
if (routerImportRegex.test(content)) {
// 如果已经有 vue-router 导入,添加 useRoute
return content.replace(
/import\s+([^}]+)\s+from\s+['"]vue-router['"]/,
(match, imports) => {
if (imports.includes('useRoute')) {
return match
}
return `import { ${imports.trim()}, useRoute } from 'vue-router'`
}
)
} else {
// 如果没有 vue-router 导入,添加新的导入
const importRegex = /import\s+.*from\s+['"]vue['"]/
const vueImportMatch = content.match(importRegex)
if (vueImportMatch) {
const insertPos = vueImportMatch.index + vueImportMatch[0].length
return content.slice(0, insertPos) + "\nimport { useRoute } from 'vue-router'" + content.slice(insertPos)
}
}
return content
}
// 添加 Menu 图标导入
function addMenuIconImport(content) {
if (content.includes('Menu') && content.includes('@element-plus/icons-vue')) {
return content
}
// 查找 @element-plus/icons-vue 导入
const iconImportRegex = /import\s+.*from\s+['"]@element-plus\/icons-vue['"]/
const iconImportMatch = content.match(iconImportRegex)
if (iconImportMatch) {
// 如果已经有图标导入,添加 Menu
return content.replace(
/import\s+{([^}]+)}\s+from\s+['"]@element-plus\/icons-vue['"]/,
(match, imports) => {
if (imports.includes('Menu')) {
return match
}
return `import { ${imports.trim()}, Menu } from '@element-plus/icons-vue'`
}
)
} else {
// 如果没有图标导入,添加新的导入
const importRegex = /import\s+.*from\s+['"][^'"]+['"]/
const lastImportMatch = [...content.matchAll(importRegex)].pop()
if (lastImportMatch) {
const insertPos = lastImportMatch.index + lastImportMatch[0].length
return content.slice(0, insertPos) + "\nimport { Menu } from '@element-plus/icons-vue'" + content.slice(insertPos)
}
}
return content
}
// 主处理函数
function processFile(filePath) {
console.log(`处理文件: ${filePath}`)
let content = fs.readFileSync(filePath, 'utf-8')
// 检查是否已经包含 TableColumnControl
if (hasTableColumnControl(content)) {
console.log(` 跳过: 已包含 TableColumnControl`)
return false
}
// 检查是否有 el-table
if (!content.includes('<el-table')) {
console.log(` 跳过: 没有 el-table`)
return false
}
// 提取表格列配置
const columns = extractTableColumns(content)
if (columns.length === 0) {
console.log(` 跳过: 无法提取表格列配置`)
return false
}
console.log(` 找到 ${columns.length} 个列`)
// 添加导入
content = addImport(content)
content = addUseRouteImport(content)
content = addMenuIconImport(content)
// TODO: 添加其他必要的代码修改
// 这里需要根据具体页面结构进行更详细的修改
// 保存文件
// fs.writeFileSync(filePath, content, 'utf-8')
console.log(` 完成: ${filePath}`)
return true
}
// 主函数
function main() {
const files = getAllIndexFiles(targetDir)
console.log(`找到 ${files.length} 个文件`)
let processed = 0
for (const file of files) {
if (processFile(file)) {
processed++
}
}
console.log(`\n处理完成: ${processed}/${files.length} 个文件`)
}
if (require.main === module) {
main()
}
module.exports = { processFile, extractTableColumns }

View File

@@ -1,6 +1,6 @@
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-row :gutter="0">
<!-- 搜索框控制 -->
<el-tooltip
class="item"
@@ -103,6 +103,25 @@ const isExport = () => {
</script>
<style lang="scss" scoped>
.top-right-btn {
display: flex;
align-items: center;
:deep(.el-row) {
display: flex;
align-items: center;
gap: 8px;
}
:deep(.el-button) {
margin-left: 0;
}
:deep(.el-tooltip) {
margin-left: 0;
}
}
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;

View File

@@ -0,0 +1,272 @@
# TableColumnControl 集成指南
## 概述
`TableColumnControl` 是一个通用的表格列显隐控制组件,可以为任何表格页面添加列显示/隐藏和排序功能。
## 集成步骤
### 1. 导入必要的依赖
```typescript
import { ref, computed, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { Menu } from '@element-plus/icons-vue'
```
### 2. 定义表格列配置
```typescript
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'title', label: '标题' },
{ prop: 'author', label: '作者' },
{ prop: '操作', label: '操作', alwaysShow: true, fixed: true }
]
// 列配置映射(用于图标,可选)
const columnConfigMap: Record<string, { icon: any }> = {
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
title: { icon: Document },
author: { icon: User }
}
```
### 3. 添加状态变量
```typescript
const route = useRoute()
const columnControlRef = ref<any>()
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
```
### 4. 添加配置加载和保存逻辑
```typescript
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
console.error('解析列配置失败:', e)
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载列排序配置
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
console.error('解析列排序失败:', e)
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 立即加载保存的配置
loadSavedConfig()
```
### 5. 添加排序后的表格列计算属性
```typescript
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return col.alwaysShow || col.fixed || visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
```
### 6. 添加列显示控制函数
```typescript
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
```
### 7. 添加事件处理函数
```typescript
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
```
### 8. 在模板中添加 TableColumnControl 组件
`right-toolbar` 组件内添加:
```vue
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
```
### 9. 修改表格列渲染方式
将原来的静态 `el-table-column` 替换为动态渲染:
```vue
<el-table-column type="index" label="序号" width="60" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip>
<template #header>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
<!-- 特殊列的模板可以在这里添加 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ scope.row.schoolTerm === 1 ? '上学期' : scope.row.schoolTerm === 2 ? '下学期' : scope.row.schoolTerm }}
</el-tag>
</template>
</el-table-column>
</template>
<el-table-column label="操作" align="center" fixed="right">
<!-- 操作列内容 -->
</el-table-column>
```
### 10. 在 onMounted 中确保配置已加载
```typescript
onMounted(() => {
// 其他初始化代码...
// 确保配置已同步
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
```
## 注意事项
1. **列配置中的 prop 必须与 el-table-column 的 prop 属性一致**
2. **操作列应该标记为 `alwaysShow: true` 和 `fixed: true`**
3. **序号列type="index")不需要添加到 tableColumns 中**
4. **localStorage 的 key 会根据路由自动生成,确保每个页面都有独立的配置**
## 示例
参考 `src/views/stuwork/weekPlan/index.vue``src/views/stuwork/classroomhygienemonthly/index.vue` 的完整实现。

View File

@@ -0,0 +1,293 @@
# TableColumnControl 快速开始指南
## 简介
`TableColumnControl` 是一个通用的表格列控制组件,支持:
- ✅ 列的显示/隐藏控制
- ✅ 列的拖拽排序
- ✅ 配置的自动保存和恢复(基于路由)
- ✅ 两种使用方式:自动提取或手动配置
## 快速集成3 步)
### 步骤 1: 在页面中引入组件
```vue
<script setup lang="ts">
import { ref } from 'vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import type { TableInstance } from 'element-plus'
const tableRef = ref<TableInstance>()
</script>
```
### 步骤 2: 添加列设置按钮
```vue
<template>
<div>
<!-- 列设置按钮放在表格上方 -->
<right-toolbar>
<TableColumnControl
:table-ref="tableRef"
trigger-circle
>
<template #trigger>
<el-tooltip content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
<!-- 表格 -->
<el-table ref="tableRef" :data="tableData">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
<el-table-column prop="email" label="邮箱" />
<el-table-column label="操作" fixed="right">
<template #default="scope">
<el-button @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
```
### 步骤 3: 完成!
就这么简单!组件会自动:
- 从表格中提取列配置
- 根据当前路由自动生成存储 key
- 保存和恢复用户的列设置
## 手动配置方式(可选)
如果你的表格列是动态生成的,或者需要自定义列配置,可以使用手动配置:
```vue
<template>
<div>
<right-toolbar>
<TableColumnControl
:columns="tableColumns"
v-model="visibleColumns"
trigger-circle
>
<template #trigger>
<el-tooltip content="列设置" placement="top">
<el-button circle>
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
<el-table :data="tableData">
<el-table-column
v-for="col in sortedTableColumns"
v-if="checkColumnVisible(col.prop || '')"
:key="col.prop"
:prop="col.prop"
:label="col.label"
/>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
const route = useRoute()
const visibleColumns = ref<string[]>([])
const columnOrder = ref<string[]>([])
const tableColumns = [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'email', label: '邮箱' },
{ prop: 'action', label: '操作', fixed: true, alwaysShow: true }
]
// 检查列是否可见
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) return true
return visibleColumns.value.includes(prop)
}
// 排序后的列
const sortedTableColumns = computed(() => {
if (columnOrder.value.length === 0) return tableColumns
const ordered: typeof tableColumns = []
const unordered: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = tableColumns.find(c => (c.prop || c.label) === key)
if (col) ordered.push(col)
})
tableColumns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unordered.push(col)
}
})
return [...ordered, ...unordered]
})
// 加载保存的配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
if (filteredSaved.length > 0) {
visibleColumns.value = filteredSaved
} else {
visibleColumns.value = validColumns
}
} catch (e) {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载排序
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
columnOrder.value = JSON.parse(savedOrder)
} catch (e) {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 立即加载配置
loadSavedConfig()
</script>
```
## 使用 Composable推荐
为了简化手动配置方式,可以使用 `useTableColumnControl` composable
```vue
<script setup lang="ts">
import { computed } from 'vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { useTableColumnControl } from '/@/composables/useTableColumnControl'
const tableColumns = [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'email', label: '邮箱' },
{ prop: 'action', label: '操作', fixed: true, alwaysShow: true }
]
const {
visibleColumns,
columnOrder,
checkColumnVisible,
saveColumnConfig,
saveColumnOrder
} = useTableColumnControl({
columns: tableColumns
})
const sortedTableColumns = computed(() => {
// ... 排序逻辑
})
</script>
```
## 常见问题
### Q: 如何自定义存储 key
```vue
<TableColumnControl
:table-ref="tableRef"
storage-key="my-custom-key"
/>
```
### Q: 如何设置始终显示的列?
```vue
<TableColumnControl
:table-ref="tableRef"
:auto-extract-options="{
alwaysShow: ['name', 'action']
}"
/>
```
### Q: 如何设置默认隐藏的列?
```vue
<TableColumnControl
:table-ref="tableRef"
:auto-extract-options="{
defaultHidden: ['remark', 'description']
}"
/>
```
### Q: 固定列会自动隐藏吗?
不会。使用 `fixed="left"``fixed="right"` 的列会自动标记为不可隐藏。
## 完整示例
查看 `src/views/stuwork/classroomhygienemonthly/index.vue` 了解完整的使用示例。

View File

@@ -1,16 +1,65 @@
# TableColumnControl 表格列显隐控制组件
# TableColumnControl 表格列控制组件
一个通用的表格列显示/隐藏控制组件,支持动态控制表格列的显示状态
一个**完全通用**的表格列控制组件,支持列的显示/隐藏控制和拖拽排序,配置会自动保存到 localStorage并在页面重新加载时自动恢复
## 功能特性
## 功能特性
-动态控制表格列的显示/隐藏
-支持全选/全不选
-支持重置为默认值
-支持 localStorage 持久化
-支持固定列(不可隐藏)
-支持始终显示的列
- ✅ 可自定义触发按钮样式
-**列的显示/隐藏控制**:用户可以自定义显示哪些列
-**列的拖拽排序**:用户可以自定义列的显示顺序
-**自动保存和恢复**:配置自动保存到 localStorage基于路由自动生成存储 key
-**两种使用方式**:支持自动提取或手动配置
-**完全通用**:可以在任何页面使用,无需额外配置
-**固定列保护**固定列fixed和始终显示的列alwaysShow自动不可隐藏
## 🚀 快速开始3 步集成)
### 步骤 1: 引入组件
```vue
<script setup lang="ts">
import { ref } from 'vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import type { TableInstance } from 'element-plus'
const tableRef = ref<TableInstance>()
</script>
```
### 步骤 2: 添加列设置按钮
```vue
<template>
<right-toolbar>
<TableColumnControl
:table-ref="tableRef"
trigger-circle
>
<template #trigger>
<el-tooltip content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
<el-table ref="tableRef" :data="tableData">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
<el-table-column label="操作" fixed="right" />
</el-table>
</template>
```
### 步骤 3: 完成!
就这么简单!组件会自动:
- ✅ 从表格中提取列配置
- ✅ 根据当前路由自动生成存储 key
- ✅ 保存和恢复用户的列设置
> 📖 更多使用方式请查看 [快速开始指南](./QUICK_START.md)
## 使用方法

View File

@@ -16,28 +16,30 @@
<el-dialog
v-model="visible"
title="列显隐设置"
:width="dialogWidth"
:width="props.dialogWidth"
append-to-body
:close-on-click-modal="false"
>
<div class="column-control-content">
<div class="column-control-body">
<el-checkbox-group v-model="checkedColumns" @change="handleColumnChange" class="column-checkbox-group">
<div class="column-switch-group" ref="sortableContainerRef">
<div
v-for="column in actualColumns"
v-for="column in sortedColumns"
:key="column.prop || column.label"
class="column-item"
:data-key="column.prop || column.label"
>
<el-checkbox
:label="column.prop || column.label"
:disabled="!!column.fixed || column.alwaysShow"
>
{{ column.label }}
</el-checkbox>
<!-- <el-tag v-if="column.fixed !== undefined" size="small" type="info">
{{ column.fixed === 'left' ? '固定左侧' : column.fixed === 'right' ? '固定右侧' : '固定' }}
</el-tag> -->
<div class="column-drag-handle">
<el-icon class="drag-icon"><Rank /></el-icon>
</div>
<span class="column-label">{{ column.label }}</span>
<el-switch
v-model="columnVisibleMap[column.prop || column.label]"
:disabled="!!column.fixed || column.alwaysShow"
@change="handleSwitchChange(column.prop || column.label, $event)"
/>
</div>
</div>
</el-checkbox-group>
</div>
</div>
</el-dialog>
@@ -46,11 +48,14 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick, useSlots, type Ref } from 'vue'
import { Menu } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { Menu, Rank } from '@element-plus/icons-vue'
import type { TableInstance } from 'element-plus'
import { useTableColumns, type ColumnConfig } from '/@/composables/useTableColumns'
import Sortable from 'sortablejs'
const slots = useSlots()
const route = useRoute()
interface Column {
prop?: string
@@ -85,20 +90,40 @@ const props = withDefaults(defineProps<Props>(), {
triggerCircle: false,
triggerText: '',
triggerLink: false,
dialogWidth: '800px'
dialogWidth: '500px'
})
const emit = defineEmits<{
'update:modelValue': [value: string[]]
'change': [value: string[]]
'order-change': [value: string[]]
}>()
const visible = ref(false)
const checkedColumns = ref<string[]>([])
const columnVisibleMap = ref<Record<string, boolean>>({})
const sortableContainerRef = ref<HTMLElement>()
const columnOrder = ref<string[]>([])
let sortableInstance: any = null
// 根据路由自动生成 storageKey如果没有提供
const getStorageKey = (): string => {
if (props.storageKey) {
return props.storageKey
}
// 使用路由路径生成 key移除开头的 / 并替换 / 为 -
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
return `table-columns-${routePath}`
}
// 获取排序配置的 storageKey
const getOrderStorageKey = (): string => {
return `${getStorageKey()}-order`
}
// 如果提供了 tableRef使用自动提取否则使用手动配置的 columns
const tableColumnsResult = props.tableRef
? useTableColumns(props.tableRef, props.storageKey, props.autoExtractOptions)
? useTableColumns(props.tableRef, getStorageKey(), props.autoExtractOptions)
: {
columns: computed(() => []),
visibleColumns: computed(() => []),
@@ -123,6 +148,41 @@ const actualColumns = computed(() => {
return result
})
// 排序后的列配置(排除序号和操作列)
const sortedColumns = computed(() => {
const columns = actualColumns.value.filter(col => {
const key = col.prop || col.label
// 排除序号列type === 'index')和操作列(固定右侧或 alwaysShow
return col.type !== 'index' && !(col.fixed === 'right' && col.alwaysShow) && key !== '操作'
})
// 如果有保存的排序顺序,按照顺序排序
if (columnOrder.value.length > 0) {
const orderedColumns: ColumnConfig[] = []
const unorderedColumns: ColumnConfig[] = []
// 先按照保存的顺序添加列
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
// 添加未在排序列表中的列(新增的列)
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 获取所有列(包括固定列和 alwaysShow 列)
const getAllColumns = (): string[] => {
return actualColumns.value.map(col => col.prop || col.label)
@@ -130,6 +190,37 @@ const getAllColumns = (): string[] => {
// 初始化选中的列
const initCheckedColumns = () => {
// 如果 actualColumns 为空,无法初始化
if (actualColumns.value.length === 0) {
return
}
// 优先从 localStorage 读取配置
const storageKey = getStorageKey()
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const allSelectableColumns = actualColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
// 使用保存的列配置(即使数量少于所有列,也使用保存的配置)
// 但确保包含固定列和 alwaysShow 列
const fixedAndAlwaysShow = actualColumns.value
.filter(col => col.alwaysShow || !!col.fixed)
.map(col => col.prop || col.label)
checkedColumns.value = [...new Set([...savedColumns, ...fixedAndAlwaysShow])]
// 同步到外部 modelValue
emit('update:modelValue', checkedColumns.value)
return // 已从 localStorage 加载,直接返回
} catch (e) {
// 如果解析失败,继续使用其他方式初始化
}
}
// 如果 localStorage 中没有保存的配置,使用其他方式初始化
if (props.modelValue && props.modelValue.length > 0) {
checkedColumns.value = [...props.modelValue]
} else if (props.tableRef && autoVisibleColumns.value.length > 0) {
@@ -151,60 +242,118 @@ const initCheckedColumns = () => {
.map(col => col.prop || col.label)
checkedColumns.value = [...new Set([...autoVisibleColumns.value, ...fixedAndAlwaysShow])]
}
} else if (props.storageKey) {
// 从 localStorage 读取
const saved = localStorage.getItem(props.storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const allSelectableColumns = actualColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
// 如果保存的列数量少于所有列,默认全部选中
if (savedColumns.length < allSelectableColumns.length) {
const fixedAndAlwaysShow = actualColumns.value
.filter(col => col.alwaysShow || !!col.fixed)
.map(col => col.prop || col.label)
checkedColumns.value = [...new Set([...allSelectableColumns, ...fixedAndAlwaysShow])]
} else {
// 使用保存的列,但确保包含固定列和 alwaysShow 列
const fixedAndAlwaysShow = actualColumns.value
.filter(col => col.alwaysShow || !!col.fixed)
.map(col => col.prop || col.label)
checkedColumns.value = [...new Set([...savedColumns, ...fixedAndAlwaysShow])]
}
} catch (e) {
// 如果解析失败,使用默认值(所有列)
checkedColumns.value = getAllColumns()
}
} else {
// 首次使用,默认全部选中
checkedColumns.value = getAllColumns()
}
} else {
// 没有 storageKey默认全部选中
checkedColumns.value = getAllColumns()
}
}
// 监听 actualColumns 变化,更新选中状态
watch(actualColumns, (newColumns) => {
if (newColumns.length > 0 && checkedColumns.value.length === 0) {
// 如果列数据已加载但选中列表为空,初始化选中所有列
initCheckedColumns()
} else if (newColumns.length > 0) {
// 确保固定列和 alwaysShow 列始终在选中列表中
const fixedAndAlwaysShow = newColumns
.filter(col => col.alwaysShow || !!col.fixed)
.map(col => col.prop || col.label)
const currentChecked = checkedColumns.value
const missingFixed = fixedAndAlwaysShow.filter(col => !currentChecked.includes(col))
if (missingFixed.length > 0) {
checkedColumns.value = [...currentChecked, ...missingFixed]
// 初始化列可见性映射
const initColumnVisibleMap = () => {
const map: Record<string, boolean> = {}
actualColumns.value.forEach(col => {
const key = col.prop || col.label
// 固定列和 alwaysShow 列始终为 true
if (col.fixed || col.alwaysShow) {
map[key] = true
} else {
// 其他列根据 checkedColumns 判断
map[key] = checkedColumns.value.includes(key)
}
})
columnVisibleMap.value = map
}
// 初始化列排序顺序
const initColumnOrder = () => {
const storageKey = getOrderStorageKey()
// 先获取所有可排序的列(不依赖 sortedColumns computed
const allSortableColumns = actualColumns.value.filter(col => {
const key = col.prop || col.label
return col.type !== 'index' && !(col.fixed === 'right' && col.alwaysShow) && key !== '操作'
})
const validColumns = allSortableColumns.map(col => col.prop || col.label)
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedOrder = JSON.parse(saved)
// 验证保存的列是否仍然存在
columnOrder.value = savedOrder.filter((key: string) => validColumns.includes(key))
// 添加新列到排序列表末尾
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
// 如果解析失败,使用默认顺序
columnOrder.value = [...validColumns]
}
} else {
// 首次使用,使用默认顺序
columnOrder.value = [...validColumns]
}
}
}, { deep: true })
// 保存列排序顺序
const saveColumnOrder = () => {
const storageKey = getOrderStorageKey()
if (columnOrder.value.length > 0) {
localStorage.setItem(storageKey, JSON.stringify(columnOrder.value))
}
}
// 初始化拖拽排序
const initSortable = () => {
if (!sortableContainerRef.value) {
return
}
// 销毁旧的实例
if (sortableInstance) {
sortableInstance.destroy()
sortableInstance = null
}
// 等待 DOM 更新完成
nextTick(() => {
if (!sortableContainerRef.value) return
// 创建新的拖拽实例(整行可拖拽)
sortableInstance = Sortable.create(sortableContainerRef.value, {
animation: 150,
ghostClass: 'column-item-ghost',
dragClass: 'column-item-drag',
forceFallback: false,
filter: '.el-switch', // 排除开关元素,避免拖拽时触发开关
preventOnFilter: false,
onEnd: (evt) => {
const { newIndex, oldIndex } = evt
if (newIndex !== undefined && oldIndex !== undefined && newIndex !== oldIndex) {
// 获取当前排序后的列
const currentOrder = sortedColumns.value.map(col => col.prop || col.label)
// 更新列顺序
const movedKey = currentOrder[oldIndex]
currentOrder.splice(oldIndex, 1)
currentOrder.splice(newIndex, 0, movedKey)
// 更新 columnOrder
columnOrder.value = currentOrder
// 保存排序顺序
saveColumnOrder()
// 触发 change 事件,通知父组件列顺序已改变
emit('order-change', [...columnOrder.value])
}
}
})
})
}
// 监听弹窗打开,触发列配置重新提取
watch(visible, (newVal) => {
@@ -225,6 +374,22 @@ watch(visible, (newVal) => {
checkedColumns.value = [...currentChecked, ...missingFixed]
}
}
// 更新列可见性映射
initColumnVisibleMap()
// 初始化列排序顺序(只在 columnOrder 为空时初始化,避免覆盖已保存的配置)
if (columnOrder.value.length === 0) {
initColumnOrder()
}
// 初始化拖拽排序
nextTick(() => {
initSortable()
})
}
} else {
// 弹窗关闭时销毁拖拽实例
if (sortableInstance) {
sortableInstance.destroy()
sortableInstance = null
}
}
if (newVal && props.tableRef) {
@@ -345,7 +510,51 @@ defineExpose({
refreshColumns: refreshAutoColumns
})
// 变化处理(实时生效)
// 开关变化处理
const handleSwitchChange = (columnKey: string, value: boolean) => {
// 更新 columnVisibleMap
columnVisibleMap.value[columnKey] = value
// 更新 checkedColumns
if (value) {
// 开启:添加到选中列表
if (!checkedColumns.value.includes(columnKey)) {
checkedColumns.value.push(columnKey)
}
} else {
// 关闭:从选中列表移除
checkedColumns.value = checkedColumns.value.filter(col => col !== columnKey)
}
// 确保固定列和 alwaysShow 列始终在选中列表中
const fixedAndAlwaysShow = actualColumns.value
.filter(col => col.alwaysShow || !!col.fixed)
.map(col => col.prop || col.label)
const finalValue = [...new Set([...checkedColumns.value, ...fixedAndAlwaysShow])]
// 立即应用更改
emit('update:modelValue', finalValue)
emit('change', finalValue)
// 如果使用自动提取,同步更新(只更新可选择的列)
if (props.tableRef) {
const selectableValue = finalValue.filter(col => {
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
updateAutoVisibleColumns(selectableValue)
} else {
// 保存到 localStorage只保存可选择的列
const storageKey = getStorageKey()
const selectableValue = finalValue.filter(col => {
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableValue))
}
}
// 列变化处理(实时生效)- 保留用于兼容性
const handleColumnChange = (value: string[]) => {
// 确保固定列和 alwaysShow 列始终在选中列表中
const fixedAndAlwaysShow = actualColumns.value
@@ -366,26 +575,64 @@ const handleColumnChange = (value: string[]) => {
updateAutoVisibleColumns(selectableValue)
} else {
// 保存到 localStorage只保存可选择的列
if (props.storageKey) {
const storageKey = getStorageKey()
const selectableValue = finalValue.filter(col => {
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(props.storageKey, JSON.stringify(selectableValue))
}
localStorage.setItem(storageKey, JSON.stringify(selectableValue))
}
}
// 监听外部 modelValue 变化
watch(() => props.modelValue, (newVal) => {
// 只有在外部传入的值与当前值不同时才更新,避免覆盖已加载的配置
if (newVal && newVal.length > 0) {
const currentSorted = [...checkedColumns.value].sort()
const newSorted = [...newVal].sort()
if (JSON.stringify(currentSorted) !== JSON.stringify(newSorted)) {
// 检查是否是从 localStorage 加载的配置
const storageKey = getStorageKey()
const saved = localStorage.getItem(storageKey)
// 如果 checkedColumns 为空,或者没有保存的配置,才使用外部传入的值
if (checkedColumns.value.length === 0 || !saved) {
checkedColumns.value = [...newVal]
initColumnVisibleMap()
}
}, { immediate: true })
}
}
}, { immediate: false })
// 初始化
onMounted(() => {
// 等待 actualColumns 准备好
nextTick(() => {
// 优先从 localStorage 加载配置
if (actualColumns.value.length > 0) {
initCheckedColumns()
initColumnVisibleMap()
initColumnOrder()
// 如果外部传入了 modelValue且 checkedColumns 为空,才使用外部传入的值
if (props.modelValue && props.modelValue.length > 0 && checkedColumns.value.length === 0) {
checkedColumns.value = [...props.modelValue]
initColumnVisibleMap()
}
} else {
// 如果 actualColumns 为空,等待它准备好
// 使用 nextTick 确保所有函数都已定义
nextTick(() => {
const unwatch = watch(actualColumns, (newColumns) => {
if (newColumns.length > 0) {
initCheckedColumns()
initColumnVisibleMap()
initColumnOrder()
unwatch() // 停止监听
}
}, { immediate: true })
})
}
})
})
</script>
@@ -396,31 +643,84 @@ onMounted(() => {
.column-control-content {
.column-control-body {
max-height: 400px;
overflow-y: auto;
margin-bottom: 12px;
.column-checkbox-group {
.column-switch-group {
display: flex;
flex-wrap: wrap;
gap: 12px 16px;
flex-direction: column;
gap: 12px;
width: 100%;
}
.column-item {
display: flex;
align-items: center;
gap: 6px;
min-width: 120px;
gap: 12px;
padding: 10px 12px;
border-radius: 4px;
transition: all 0.2s;
cursor: move;
width: 100%;
box-sizing: border-box;
user-select: none;
:deep(.el-checkbox) {
margin-right: 0;
&:hover {
background-color: #f5f7fa;
}
.el-checkbox__label {
&:active {
cursor: grabbing;
}
&.sortable-ghost {
opacity: 0.5;
background-color: #e6f7ff;
}
&.sortable-drag {
opacity: 0.8;
}
.column-drag-handle {
display: flex;
align-items: center;
justify-content: center;
color: #909399;
flex-shrink: 0;
width: 20px;
height: 20px;
pointer-events: none; // 让图标不阻止拖拽
.drag-icon {
font-size: 18px;
}
}
.column-label {
font-size: 14px;
color: #606266;
padding-left: 8px;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: none; // 让文本不阻止拖拽
}
// 开关元素可以正常交互
.el-switch {
pointer-events: auto;
cursor: pointer;
}
}
.column-item-ghost {
opacity: 0.5;
background-color: #e6f7ff;
}
.column-item-drag {
opacity: 0.8;
}
}

View File

@@ -0,0 +1,160 @@
import { ref, computed, onMounted, nextTick, type Ref } from 'vue'
import { useRoute } from 'vue-router'
import type { TableInstance } from 'element-plus'
export interface ColumnConfig {
prop?: string
label: string
fixed?: boolean | 'left' | 'right'
alwaysShow?: boolean
[key: string]: any
}
/**
* 表格列控制通用 Composable
* 简化 TableColumnControl 组件的使用
*
* @param options 配置选项
* @returns 返回列控制相关的响应式数据和方法
*/
export function useTableColumnControl(options?: {
/** 表格 ref用于自动提取列配置 */
tableRef?: Ref<TableInstance | undefined>
/** 手动配置的列(如果提供了 tableRef 则忽略) */
columns?: ColumnConfig[]
/** localStorage 存储的 key可选默认根据路由自动生成 */
storageKey?: string
/** 默认显示的列prop 或 label 数组) */
defaultVisible?: string[]
/** 始终显示的列prop 或 label 数组) */
alwaysShow?: string[]
}) {
const route = useRoute()
// 根据路由自动生成 storageKey
const getStorageKey = (suffix: string = ''): string => {
if (options?.storageKey) {
return options.storageKey + (suffix ? `-${suffix}` : '')
}
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
return `table-columns-${routePath}${suffix ? `-${suffix}` : ''}`
}
// 可见列
const visibleColumns = ref<string[]>([])
// 列排序
const columnOrder = ref<string[]>([])
// 列配置(手动配置或从表格提取)
const tableColumns = computed(() => {
return options?.columns || []
})
// 加载保存的配置
const loadSavedConfig = () => {
const storageKey = getStorageKey()
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
if (filteredSaved.length > 0) {
visibleColumns.value = filteredSaved
} else if (options?.defaultVisible && options.defaultVisible.length > 0) {
visibleColumns.value = options.defaultVisible
} else {
visibleColumns.value = validColumns
}
} catch (e) {
console.error('解析列配置失败:', e)
visibleColumns.value = options?.defaultVisible || tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = options?.defaultVisible || tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载列排序配置
const orderKey = getStorageKey('order')
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
// 添加新列到排序列表末尾
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
console.error('解析列排序失败:', e)
columnOrder.value = tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.value
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 保存列配置
const saveColumnConfig = (columns: string[]) => {
visibleColumns.value = columns
const storageKey = getStorageKey()
const selectableColumns = columns.filter(col => {
const column = tableColumns.value.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 保存列排序
const saveColumnOrder = (order: string[]) => {
columnOrder.value = order
const orderKey = getStorageKey('order')
localStorage.setItem(orderKey, JSON.stringify(order))
}
// 检查列是否可见
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 初始化
onMounted(() => {
nextTick(() => {
loadSavedConfig()
})
})
return {
visibleColumns,
columnOrder,
tableColumns,
checkColumnVisible,
saveColumnConfig,
saveColumnOrder,
loadSavedConfig,
getStorageKey
}
}

View File

@@ -0,0 +1,157 @@
/**
* TableColumnControl 集成辅助函数
* 提供通用的列配置加载和保存逻辑
*/
import { ref, Ref, computed, ComputedRef } from 'vue'
import { useRoute } from 'vue-router'
export interface ColumnConfig {
prop?: string
label: string
alwaysShow?: boolean
fixed?: boolean | 'left' | 'right'
minWidth?: number | string
width?: number | string
[key: string]: any
}
/**
* 创建列显隐控制相关的响应式数据和方法
* @param tableColumns 表格列配置数组
* @returns 返回 visibleColumns、columnOrder、loadSavedConfig、handleColumnChange、handleColumnOrderChange 等
*/
export function useTableColumnControl(tableColumns: ColumnConfig[]) {
const route = useRoute()
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 根据路由生成 storageKey
const getStorageKey = (suffix: string = '') => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
return `table-columns-${routePath}${suffix ? `-${suffix}` : ''}`
}
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const storageKey = getStorageKey()
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
console.error('解析列配置失败:', e)
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载列排序配置
const orderKey = getStorageKey('order')
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
console.error('解析列排序失败:', e)
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return col.alwaysShow || col.fixed || visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: ColumnConfig[] = []
const unorderedColumns: ColumnConfig[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const storageKey = getStorageKey()
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const storageKey = getStorageKey('order')
localStorage.setItem(storageKey, JSON.stringify(order))
}
return {
visibleColumns,
columnOrder,
sortedTableColumns,
checkColumnVisible,
loadSavedConfig,
handleColumnChange,
handleColumnOrderChange
}
}

View File

@@ -111,6 +111,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -131,72 +148,32 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="classProName" label="班级规范名称" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">班级规范名称</span>
</template>
</el-table-column>
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="teacherTel" label="班主任电话号码" show-overflow-tooltip>
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">班主任电话</span>
</template>
</el-table-column>
<el-table-column label="班级人数/原始人数" show-overflow-tooltip>
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">班级人数/原始人数</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'stuNum'">
<el-tag size="small" type="primary" effect="plain">
{{ scope.row.stuNum || 0 }}/{{ scope.row.preStuNum || 0 }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="ruleName" label="门禁规则" show-overflow-tooltip>
<template #header>
<el-icon><Lock /></el-icon>
<span style="margin-left: 4px">门禁规则</span>
</template>
</el-table-column>
<el-table-column prop="classStatus" label="班级状态" show-overflow-tooltip>
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">班级状态</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'classStatus'">
<StatusTag
:value="scope.row.classStatus"
:options="[{ label: '正常', value: '0' }, { label: '离校', value: '1' }]"
:type-map="{ '0': { type: 'success', effect: 'light' }, '1': { type: 'warning', effect: 'light' } }"
/>
</template>
</el-table-column>
<el-table-column prop="stuLoseRate" label="流失率" show-overflow-tooltip>
<template #header>
<el-icon><TrendCharts /></el-icon>
<span style="margin-left: 4px">流失率</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'stuLoseRate'">
<el-tag size="small" type="danger" effect="plain">
{{ scope.row.stuLoseRate ? `${scope.row.stuLoseRate}%` : '0%' }}
</el-tag>
@@ -278,12 +255,14 @@
<script setup lang="ts" name="BasicClass">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, putObjs, classExportData, getDeptList, getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { fetchList as getRuleList } from "/@/api/stuwork/entrancerule";
import { downBlobFile, adaptationUrl } from "/@/utils/other";
import { List, OfficeBuilding, Grid, Document, UserFilled, Phone, User, Lock, CircleCheck, TrendCharts, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, Grid, Document, UserFilled, Phone, User, Lock, CircleCheck, TrendCharts, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent as defineStatusTag } from 'vue'
const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue'))
@@ -292,9 +271,11 @@ const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const DetailDialog = defineAsyncComponent(() => import('./detail.vue'));
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
@@ -304,6 +285,113 @@ const linkRuleDialogVisible = ref(false)
const linkRuleLoading = ref(false)
const selectedClassCodes = ref<string[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'classProName', label: '班级规范名称', icon: Document },
{ prop: 'teacherRealName', label: '班主任', icon: UserFilled },
{ prop: 'teacherTel', label: '班主任电话号码', icon: Phone },
{ prop: 'stuNum', label: '班级人数/原始人数', icon: User },
{ prop: 'ruleName', label: '门禁规则', icon: Lock },
{ prop: 'classStatus', label: '班级状态', icon: CircleCheck },
{ prop: 'stuLoseRate', label: '流失率', icon: TrendCharts }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -499,6 +587,7 @@ const getRuleListData = async () => {
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -201,6 +201,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -221,102 +238,32 @@
<span style="margin-left: 4px;">序号</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px;">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="majorName" label="专业" show-overflow-tooltip>
<template #header>
<el-icon><Briefcase /></el-icon>
<span style="margin-left: 4px;">专业</span>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px;">班级</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px;">学号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px;">姓名</span>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="80" align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px;">性别</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'gender'">
<GenderTag :gender="scope.row.gender" />
</template>
</el-table-column>
<el-table-column prop="idCard" label="身份证号" show-overflow-tooltip>
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px;">身份证号</span>
</template>
</el-table-column>
<el-table-column prop="teacherNo" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px;">班主任</span>
</template>
</el-table-column>
<el-table-column prop="isDorm" label="住宿" width="80" align="center">
<template #header>
<el-icon><HomeFilled /></el-icon>
<span style="margin-left: 4px;">住宿</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isDorm'">
<el-tag v-if="scope.row.isDorm === 1 || scope.row.isDorm === '1'" size="small" type="success" effect="plain"></el-tag>
<el-tag v-else-if="scope.row.isDorm === 0 || scope.row.isDorm === '0'" size="small" type="info" effect="plain"></el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="education" label="文化程度" show-overflow-tooltip>
<template #header>
<el-icon><School /></el-icon>
<span style="margin-left: 4px;">文化程度</span>
</template>
</el-table-column>
<el-table-column prop="enrollStatus" label="学籍状态" show-overflow-tooltip>
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px;">学籍状态</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'enrollStatus'">
<el-tag v-if="scope.row.enrollStatus" size="small" type="info" effect="plain">{{ scope.row.enrollStatus }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="个人电话" show-overflow-tooltip>
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px;">个人电话</span>
</template>
</el-table-column>
<el-table-column prop="householdAddress" label="户籍所在地" show-overflow-tooltip>
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px;">户籍所在地</span>
</template>
</el-table-column>
<el-table-column prop="stuStatus" label="学生状态" show-overflow-tooltip>
<template #header>
<el-icon><Tickets /></el-icon>
<span style="margin-left: 4px;">学生状态</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'stuStatus'">
<el-tag
v-if="getStuStatusLabel(scope.row.stuStatus)"
size="small"
@@ -326,35 +273,17 @@
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="isClassLeader" label="是否班干部" width="100" align="center">
<template #header>
<el-icon><Medal /></el-icon>
<span style="margin-left: 4px;">是否班干部</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isClassLeader'">
<el-tag v-if="scope.row.isClassLeader == 1 || scope.row.isClassLeader === '1'" size="small" type="success" effect="plain"></el-tag>
<el-tag v-else-if="scope.row.isClassLeader == 0 || scope.row.isClassLeader === '0'" size="small" type="info" effect="plain"></el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="isInout" label="是否允许进出" width="120" align="center">
<template #header>
<el-icon><Lock /></el-icon>
<span style="margin-left: 4px;">是否允许进出</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isInout'">
<el-tag v-if="scope.row.isInout === 1 || scope.row.isInout === '1'" size="small" type="success" effect="plain"></el-tag>
<el-tag v-else-if="scope.row.isInout === 0 || scope.row.isInout === '0'" size="small" type="danger" effect="plain"></el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="completeRate" label="资料完成度" width="120" align="center">
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px;">资料完成度</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'completeRate'">
<el-tag v-if="scope.row.completeRate !== undefined && scope.row.completeRate !== null" size="small" type="primary" effect="plain">
{{ scope.row.completeRate }}%
</el-tag>
@@ -472,7 +401,8 @@
<script setup lang="ts" name="BasicStudent">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { List, OfficeBuilding, Briefcase, Grid, Document, UserFilled, User, CreditCard, HomeFilled, School, CircleCheck, Phone, Location, Tickets, Medal, Lock, DataAnalysis, Setting } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { List, OfficeBuilding, Briefcase, Grid, Document, UserFilled, User, CreditCard, HomeFilled, School, CircleCheck, Phone, Location, Tickets, Medal, Lock, DataAnalysis, Setting, Menu } from '@element-plus/icons-vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/basic/basicstudentinfo";
import {
@@ -496,6 +426,7 @@ import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { downBlobFile, adaptationUrl } from "/@/utils/other";
import { Session } from "/@/utils/storage";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import GenderTag from '/@/components/GenderTag/index.vue'
// 引入组件
@@ -504,11 +435,13 @@ const DetailDialog = defineAsyncComponent(() => import('./detail.vue'));
const SimpleEditDialog = defineAsyncComponent(() => import('./components/SimpleEdit.vue'));
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const simpleEditDialogRef = ref()
const searchFormRef = ref()
const uploadRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
@@ -519,6 +452,121 @@ const importCertificateDialogVisible = ref(false)
const uploadLoading = ref(false)
const fileList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'majorName', label: '专业', icon: Briefcase },
{ prop: 'className', label: '班级', icon: Grid },
{ prop: 'stuNo', label: '学号', icon: Document },
{ prop: 'realName', label: '姓名', icon: UserFilled },
{ prop: 'gender', label: '性别', icon: User, width: 80 },
{ prop: 'idCard', label: '身份证号', icon: CreditCard },
{ prop: 'teacherNo', label: '班主任', icon: UserFilled },
{ prop: 'isDorm', label: '住宿', icon: HomeFilled, width: 80 },
{ prop: 'education', label: '文化程度', icon: School },
{ prop: 'enrollStatus', label: '学籍状态', icon: CircleCheck },
{ prop: 'phone', label: '个人电话', icon: Phone },
{ prop: 'householdAddress', label: '户籍所在地', icon: Location },
{ prop: 'stuStatus', label: '学生状态', icon: Tickets },
{ prop: 'isClassLeader', label: '是否班干部', icon: Medal, width: 100 },
{ prop: 'isInout', label: '是否允许进出', icon: Lock, width: 120 },
{ prop: 'completeRate', label: '资料完成度', icon: DataAnalysis, width: 120 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -776,6 +824,7 @@ const getStuStatusType = (value: any) => {
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -34,6 +34,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -52,26 +69,20 @@
<span style="margin-left: 4px;">序号</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px;">学号</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px;">姓名</span>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px;">班级</span>
</template>
</el-table-column>
<el-table-column prop="headImg" label="头像" width="120" align="center">
<template #default="scope">
<template #default="scope" v-if="col.prop === 'headImg'">
<el-image
v-if="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
:src="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
@@ -100,17 +111,123 @@
</template>
<script setup lang="ts" name="BasicStudentAvatar">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/basic/basicstudentavatar";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { Picture, List, Document, UserFilled, Grid } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { Picture, List, Document, UserFilled, Grid, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'stuNo', label: '学号', icon: Document },
{ prop: 'realName', label: '姓名', icon: UserFilled },
{ prop: 'className', label: '班级', icon: Grid },
{ prop: 'headImg', label: '头像', icon: Picture, width: 120 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
classCode: ''
@@ -196,6 +313,7 @@ const getClassListData = async () => {
}
// 初始化
loadSavedConfig()
onMounted(() => {
getClassListData()
})

View File

@@ -148,7 +148,6 @@ const handleStuNoBlur = async () => {
form.realName = student.realName || ''
}
} catch (err) {
console.error('查询学生信息失败', err)
// 不显示错误,允许手动输入姓名
}
}
@@ -163,7 +162,6 @@ const getActivityInfoListData = async () => {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
@@ -207,7 +205,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})

View File

@@ -22,6 +22,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -38,51 +55,29 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="userName" label="学号" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">学号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100">
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="awards" label="获奖信息" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Medal /></el-icon>
<span style="margin-left: 4px">获奖信息</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'awards'">
<el-tag v-if="scope.row.awards" size="small" type="warning" effect="light">
{{ scope.row.awards }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="awardTime" label="获奖时间" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">获奖时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'awardTime'">
<span>{{ parseTime(scope.row.awardTime || scope.row.month, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -160,22 +155,130 @@
</template>
<script setup lang="ts" name="ActivityAwards">
import { reactive, ref } from 'vue'
import { reactive, ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/activityawards";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { UploadFilled, List, Trophy, CreditCard, Avatar, Medal, Calendar, EditPen, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, Trophy, CreditCard, Avatar, Medal, Calendar, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const uploadRef = ref()
const columnControlRef = ref()
const showSearch = ref(false)
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格列配置
const tableColumns = [
{ prop: 'activityTheme', label: '活动主题', icon: Trophy, minWidth: 200 },
{ prop: 'userName', label: '学号', icon: CreditCard, width: 120 },
{ prop: 'realName', label: '姓名', icon: Avatar, width: 100 },
{ prop: 'awards', label: '获奖信息', icon: Medal, minWidth: 200 },
{ prop: 'awardTime', label: '获奖时间', icon: Calendar, width: 120 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -183,6 +286,7 @@ const tableStyle = {
}
// 配置 useTable
loadSavedConfig()
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
@@ -249,7 +353,6 @@ const handleDownloadTemplate = async () => {
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}

View File

@@ -114,7 +114,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.maxSub = res.data.maxSub !== undefined && res.data.maxSub !== null ? res.data.maxSub : undefined
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})

View File

@@ -15,6 +15,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -31,45 +48,29 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="活动说明" show-overflow-tooltip align="center" min-width="300">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">活动说明</span>
</template>
</el-table-column>
<el-table-column prop="maxSub" label="活动兼报数" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">活动兼报数</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'maxSub'">
<el-tag v-if="scope.row.maxSub" size="small" type="success" effect="plain">
{{ scope.row.maxSub }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">开始时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'startTime'">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">结束时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'endTime'">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
@@ -162,25 +163,132 @@
</template>
<script setup lang="ts" name="ActivityInfo">
import { reactive, ref } from 'vue'
import { reactive, ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importSub } from "/@/api/stuwork/activityinfo";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { UploadFilled, List, Trophy, Document, UserFilled, Calendar, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, Trophy, Document, UserFilled, Calendar, Setting, Menu } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const uploadRef = ref()
const columnControlRef = ref()
const showSearch = ref(false)
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
const currentActivity = ref<any>(null)
// 表格列配置
const tableColumns = [
{ prop: 'activityTheme', label: '活动主题', icon: Trophy, minWidth: 200 },
{ prop: 'remarks', label: '活动说明', icon: Document, minWidth: 300 },
{ prop: 'maxSub', label: '活动兼报数', icon: UserFilled, width: 120 },
{ prop: 'startTime', label: '开始时间', icon: Calendar, width: 120 },
{ prop: 'endTime', label: '结束时间', icon: Calendar, width: 120 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },

View File

@@ -34,6 +34,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -50,48 +67,26 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="subTitle" label="子项目名称" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">子项目名称</span>
</template>
</el-table-column>
<el-table-column prop="projectDescription" label="项目描述" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Files /></el-icon>
<span style="margin-left: 4px">项目描述</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">开始时间</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'startTime'">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">结束时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'endTime'">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="maxNum" label="报名人数限制" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">报名人数限制</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'maxNum'">
<el-tag v-if="scope.row.maxNum !== undefined && scope.row.maxNum !== null" size="small" type="success" effect="plain">
{{ scope.row.maxNum }}
</el-tag>
@@ -131,18 +126,126 @@
</template>
<script setup lang="ts" name="ActivityInfoSub">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, getActivityInfoList } from "/@/api/stuwork/activityinfosub";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { List, Trophy, Document, Files, Calendar, UserFilled, EditPen, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, Trophy, Document, Files, Calendar, UserFilled, EditPen, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const activityInfoList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'activityTheme', label: '活动主题', icon: Trophy, minWidth: 200 },
{ prop: 'subTitle', label: '子项目名称', icon: Document, minWidth: 150 },
{ prop: 'projectDescription', label: '项目描述', icon: Files, minWidth: 200 },
{ prop: 'startTime', label: '开始时间', icon: Calendar, width: 180 },
{ prop: 'endTime', label: '结束时间', icon: Calendar, width: 180 },
{ prop: 'maxNum', label: '报名人数限制', icon: UserFilled, width: 120 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -202,12 +305,12 @@ const getActivityInfoListData = async () => {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getActivityInfoListData()
})

View File

@@ -65,6 +65,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -81,58 +98,18 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
</template>
</el-table-column>
<el-table-column prop="subTitle" label="子项目" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">子项目</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="项目描述" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Files /></el-icon>
<span style="margin-left: 4px">项目描述</span>
</template>
</el-table-column>
<el-table-column prop="userName" label="学号/工号" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">学号/工号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100">
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院名称" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院名称</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="classMasterName" label="班主任" show-overflow-tooltip align="center" width="100">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">联系电话</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
@@ -163,18 +140,129 @@
<script setup lang="ts" name="ActivityInfoSubSignup">
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel, getActivityInfoList, getActivityInfoSubList } from "/@/api/stuwork/activityinfosubsignup";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Trophy, Document, Files, CreditCard, Avatar, OfficeBuilding, Grid, UserFilled, Phone, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, Trophy, Document, Files, CreditCard, Avatar, OfficeBuilding, Grid, UserFilled, Phone, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const exportLoading = ref(false)
const activityInfoList = ref<any[]>([])
const subList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'activityTheme', label: '活动主题', icon: Trophy, minWidth: 200 },
{ prop: 'subTitle', label: '子项目', icon: Document, minWidth: 150 },
{ prop: 'remarks', label: '项目描述', icon: Files, minWidth: 200 },
{ prop: 'userName', label: '学号/工号', icon: CreditCard, width: 120 },
{ prop: 'realName', label: '姓名', icon: Avatar, width: 100 },
{ prop: 'deptName', label: '学院名称', icon: OfficeBuilding, minWidth: 150 },
{ prop: 'classNo', label: '班号', icon: Grid, width: 120 },
{ prop: 'classMasterName', label: '班主任', icon: UserFilled, width: 100 },
{ prop: 'phone', label: '联系电话', icon: Phone, width: 120 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -282,7 +370,6 @@ const getActivityInfoListData = async () => {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
@@ -297,12 +384,12 @@ const getSubListData = async (activityInfoId?: string) => {
subList.value = []
}
} catch (err) {
console.error('获取子项目列表失败', err)
subList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getActivityInfoListData()
getSubListData()

View File

@@ -111,7 +111,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.type = res.data.type || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})

View File

@@ -39,6 +39,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -55,29 +72,25 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="category" label="考核项名称" show-overflow-tooltip align="center" min-width="200">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">考核项名称</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="考核类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">考核类型</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'type'">
<el-tag size="small" type="info" effect="plain">
{{ formatType(scope.row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -115,18 +128,123 @@
</template>
<script setup lang="ts" name="AssessmentCategory">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/assessmentcategory";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, Document, Collection, EditPen, Setting } from '@element-plus/icons-vue'
import { List, Document, Collection, EditPen, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
// 表格列配置
const tableColumns = [
{ prop: 'category', label: '考核项名称', icon: Document, minWidth: 200 },
{ prop: 'type', label: '考核类型', icon: Collection },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 考核类型列表
const typeList = ref([
{ label: '常规考核', value: '0' },
@@ -140,6 +258,7 @@ const tableStyle = {
}
// 配置 useTable
loadSavedConfig()
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
type: ''

View File

@@ -138,7 +138,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -193,7 +192,6 @@ const getCategoryList = async () => {
categoryList.value = []
}
} catch (err) {
console.error('获取考核项列表失败', err)
categoryList.value = []
}
}

View File

@@ -32,6 +32,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -48,42 +65,26 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="categoryName" label="考核项" show-overflow-tooltip align="center" min-width="150">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">考核项</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="pointName" label="指标名称" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">指标名称</span>
</template>
</el-table-column>
<el-table-column prop="standard" label="评分标准" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Reading /></el-icon>
<span style="margin-left: 4px">评分标准</span>
</template>
</el-table-column>
<el-table-column prop="score" label="默认扣分值" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Minus /></el-icon>
<span style="margin-left: 4px">默认扣分值</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'score'">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" type="danger" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -121,18 +122,125 @@
</template>
<script setup lang="ts" name="AssessmentPoint">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/assessmentpoint";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, Document, Trophy, Reading, Minus, EditPen, Setting } from '@element-plus/icons-vue'
import { List, Document, Trophy, Reading, Minus, EditPen, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
// 表格列配置
const tableColumns = [
{ prop: 'categoryName', label: '考核项', icon: Document, minWidth: 150 },
{ prop: 'pointName', label: '指标名称', icon: Trophy, minWidth: 200 },
{ prop: 'standard', label: '评分标准', icon: Reading, minWidth: 200 },
{ prop: 'score', label: '默认扣分值', icon: Minus, width: 120 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -140,6 +248,7 @@ const tableStyle = {
}
// 配置 useTable
loadSavedConfig()
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
pointName: ''

View File

@@ -271,7 +271,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
@@ -288,7 +287,6 @@ const getSchoolYearList = async () => {
currentSchoolYear.value = CURRENT_SCHOOL_YEAR
}
} catch (err) {
console.error('获取学年列表失败', err)
currentSchoolYear.value = CURRENT_SCHOOL_YEAR
}
}
@@ -309,7 +307,6 @@ const getSchoolTermDict = async () => {
currentSchoolTerm.value = CURRENT_SCHOOL_TERM
}
} catch (err) {
console.error('获取学期字典失败', err)
currentSchoolTerm.value = CURRENT_SCHOOL_TERM
}
}

View File

@@ -70,6 +70,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -86,70 +103,35 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'"
:fixed="col.fixed">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="themeName" label="活动主题" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
</template>
</el-table-column>
<el-table-column prop="author" label="主持人" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">主持人</span>
</template>
</el-table-column>
<el-table-column prop="activityTime" label="活动时间" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">活动时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'activityTime'">
<span>{{ scope.row.activityTime || scope.row.recordDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="address" label="活动地点" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px">活动地点</span>
</template>
</el-table-column>
<el-table-column prop="attendNum" label="参加人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">参加人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attendNum'">
<el-tag v-if="scope.row.attendNum" size="small" type="success" effect="plain">
{{ scope.row.attendNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="活动图片" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attachment'">
<el-button
v-if="scope.row.attachment"
icon="Picture"
@@ -161,9 +143,7 @@
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment2" label="活动图片2" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attachment2'">
<el-button
v-if="scope.row.attachment2"
icon="Picture"
@@ -213,24 +193,141 @@
</template>
<script setup lang="ts" name="ClassActivity">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classactivity";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, Calendar, Clock, Grid, Trophy, User, Location, UserFilled, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, Grid, Trophy, User, Location, UserFilled, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'themeName', label: '活动主题', icon: Trophy, minWidth: 150 },
{ prop: 'author', label: '主持人', icon: User },
{ prop: 'activityTime', label: '活动时间', icon: Calendar, width: 120 },
{ prop: 'address', label: '活动地点', icon: Location, minWidth: 150 },
{ prop: 'attendNum', label: '参加人数', icon: UserFilled },
{ prop: 'attachment', label: '活动图片', width: 100 },
{ prop: 'attachment2', label: '活动图片2', width: 100 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 检查列是否可见
const checkColumnVisible = (prop: string) => {
return visibleColumns.value.includes(prop)
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -320,7 +417,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -338,7 +434,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -353,12 +448,12 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -200,7 +200,6 @@ const getClassAssessmentSettleData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -219,7 +218,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -232,7 +230,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}

View File

@@ -79,6 +79,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -96,29 +113,25 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="virtualClassNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -150,26 +163,131 @@
<script setup lang="ts" name="ClassAssessmentSettle">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/classassessmentsettle";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, Calendar, Clock, Grid, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, Grid, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'virtualClassNo', label: '班号', icon: Grid }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
schoolYear: '',
@@ -256,7 +374,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -269,7 +386,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -282,12 +398,12 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -48,6 +48,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -64,83 +81,37 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">学号</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center">
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">联系电话</span>
</template>
</el-table-column>
<el-table-column prop="parentPhoneA" label="家长联系电话1" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">家长联系电话1</span>
</template>
</el-table-column>
<el-table-column prop="parentPhoneB" label="家长联系电话2" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">家长联系电话2</span>
</template>
</el-table-column>
<el-table-column prop="stuStatus" label="学生状态" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">学生状态</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'stuStatus'">
<el-tag size="small" type="info" effect="plain">
{{ formatStudentStatus(scope.row.stuStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="attendanceType" label="考勤类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">考勤类型</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attendanceType'">
<el-tag size="small" type="warning" effect="plain">
{{ formatAttendanceType(scope.row.attendanceType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isRoom" label="是否住宿" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">是否住宿</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isRoom'">
<StatusTag
:value="scope.row.isRoom"
:options="[{ label: '是', value: '1' }, { label: '否', value: '0' }]"
:type-map="{ '1': { type: 'success', effect: 'light' }, '0': { type: 'info', effect: 'light' } }"
/>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">宿舍号</span>
</template>
</el-table-column>
<el-table-column prop="isDeviceIn" label="是否扫脸" show-overflow-tooltip align="center">
<template #header>
<el-icon><Camera /></el-icon>
<span style="margin-left: 4px">是否扫脸</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isDeviceIn'">
<el-tag :type="scope.row.isDeviceIn === '1' ? 'success' : 'danger'">
{{ scope.row.isDeviceIn === '1' ? '是' : '否' }}
</el-tag>
@@ -167,16 +138,20 @@
</template>
<script setup lang="ts" name="ClassAttendance">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { fetchList, queryMyClassList } from '/@/api/stuwork/classattendance'
import { getDicts } from '/@/api/admin/dict'
import { useMessage } from '/@/hooks/message'
import { List, CreditCard, Avatar, Phone, CircleCheck, Collection, House, Camera, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, CreditCard, Avatar, Phone, CircleCheck, Collection, House, Camera, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent } from 'vue'
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const dataList = ref<any[]>([])
@@ -185,6 +160,114 @@ const orderTypeList = ref<any[]>([])
const studentStatusList = ref<any[]>([])
const attendanceTypeList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'stuNo', label: '学号', icon: CreditCard },
{ prop: 'realName', label: '姓名', icon: Avatar },
{ prop: 'phone', label: '联系电话', icon: Phone },
{ prop: 'parentPhoneA', label: '家长联系电话1', icon: Phone },
{ prop: 'parentPhoneB', label: '家长联系电话2', icon: Phone },
{ prop: 'stuStatus', label: '学生状态', icon: CircleCheck },
{ prop: 'attendanceType', label: '考勤类型', icon: Collection },
{ prop: 'isRoom', label: '是否住宿', icon: House },
{ prop: 'roomNo', label: '宿舍号', icon: House },
{ prop: 'isDeviceIn', label: '是否扫脸', icon: Camera }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
classCode: '',
@@ -256,7 +339,6 @@ const getDataList = async () => {
dataList.value = []
}
} catch (err: any) {
console.error('获取数据列表失败', err)
useMessage().error(err.msg || '获取数据列表失败')
dataList.value = []
} finally {
@@ -274,7 +356,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -291,7 +372,6 @@ const getOrderTypeDict = async () => {
{ label: '晚自习', value: '4' }
]
} catch (err) {
console.error('获取点名类型失败', err)
orderTypeList.value = []
}
}
@@ -309,7 +389,6 @@ const getStudentStatusDict = async () => {
studentStatusList.value = []
}
} catch (err) {
console.error('获取学生状态字典失败', err)
studentStatusList.value = []
}
}
@@ -327,12 +406,12 @@ const getAttendanceTypeDict = async () => {
attendanceTypeList.value = []
}
} catch (err) {
console.error('获取考勤类型字典失败', err)
attendanceTypeList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getClassListData()
getOrderTypeDict()

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -242,7 +242,6 @@ const getClassCheckDailyData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -261,7 +260,6 @@ const getStudentList = async (classCode: string) => {
studentList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学生列表失败', err)
studentList.value = []
}
}
@@ -274,7 +272,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}

View File

@@ -100,6 +100,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -117,54 +134,26 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="学生" show-overflow-tooltip>
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">学生</span>
</template>
</el-table-column>
<el-table-column prop="recordTime" label="记录时间" show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">记录时间</span>
</template>
</el-table-column>
<el-table-column prop="score" label="分数" show-overflow-tooltip>
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">分数</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'score'">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" :type="scope.row.score >= 0 ? 'success' : 'danger'" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="note" label="检查记录" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">检查记录</span>
</template>
</el-table-column>
<el-table-column prop="handleResult" label="处理结果" show-overflow-tooltip>
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">处理结果</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -248,6 +237,7 @@
<script setup lang="ts" name="ClassCheckDaily">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, exportData, getRank } from "/@/api/stuwork/classcheckdaily";
import { useMessage, useMessageBox } from "/@/hooks/message";
@@ -255,20 +245,128 @@ import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import request from "/@/utils/request";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, OfficeBuilding, Grid, Avatar, Calendar, DataAnalysis, Document, CircleCheck, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, Grid, Avatar, Calendar, DataAnalysis, Document, CircleCheck, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'realName', label: '学生', icon: Avatar },
{ prop: 'recordTime', label: '记录时间', icon: Calendar },
{ prop: 'score', label: '分数', icon: DataAnalysis },
{ prop: 'note', label: '检查记录', icon: Document },
{ prop: 'handleResult', label: '处理结果', icon: CircleCheck }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
schoolYear: '',
@@ -420,7 +518,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -433,7 +530,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -446,12 +542,12 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -91,7 +91,6 @@ const openDialog = async (row: any) => {
}
}
} catch (err: any) {
console.error('获取附件列表失败', err)
useMessage().error(err.msg || '获取附件列表失败')
fileList.value = []
} finally {

View File

@@ -71,6 +71,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -87,39 +104,23 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">标题</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'createTime'">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -167,25 +168,132 @@
</template>
<script setup lang="ts" name="ClassConstruction">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, initObj } from "/@/api/stuwork/classconstruction";
import { getDeptList } from "/@/api/basic/basicclass";
import { list as getClassList } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime"
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import FileListDialog from './fileList.vue'
import { List, OfficeBuilding, Grid, Document, Calendar, EditPen, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, Grid, Document, Calendar, EditPen, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const fileListDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'title', label: '标题', icon: Document, minWidth: 200 },
{ prop: 'createTime', label: '创建时间', icon: Calendar, width: 180 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -229,7 +337,6 @@ const getClassListByDept = async (deptCode: string) => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -294,12 +401,12 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
})

View File

@@ -300,7 +300,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
@@ -315,7 +314,6 @@ const getSchoolTermDict = async () => {
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
@@ -327,7 +325,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
@@ -342,7 +339,6 @@ const getTypeDict = async () => {
}))
}
} catch (err) {
console.error('获取类型字典失败', err)
}
}

View File

@@ -107,6 +107,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -123,85 +140,39 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="operatTime" label="发生时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">发生时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'operatTime'">
<span>{{ scope.row.operatTime || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">类型</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'type'">
<el-tag size="small" type="info" effect="plain">
{{ formatType(scope.row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="money" label="金额" show-overflow-tooltip align="center">
<template #header>
<el-icon><Money /></el-icon>
<span style="margin-left: 4px">金额</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'money'">
<el-tag v-if="scope.row.money !== null && scope.row.money !== undefined" size="small" type="success" effect="plain">
¥{{ scope.row.money.toFixed(2) }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="operator" label="经办人" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">经办人</span>
</template>
</el-table-column>
<el-table-column prop="purpose" label="用途" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">用途</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">附件</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attachment'">
<el-button
v-if="scope.row.attachment"
icon="Document"
@@ -251,7 +222,8 @@
</template>
<script setup lang="ts" name="ClassFeeLog">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel } from "/@/api/stuwork/classfeelog";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -259,12 +231,15 @@ import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Collection, Money, User, Document, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Collection, Money, User, Document, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -272,6 +247,114 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const typeList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'operatTime', label: '发生时间', icon: Calendar, width: 180 },
{ prop: 'type', label: '类型', icon: Collection },
{ prop: 'money', label: '金额', icon: Money },
{ prop: 'operator', label: '经办人', icon: User },
{ prop: 'purpose', label: '用途', icon: Document, minWidth: 150 },
{ prop: 'attachment', label: '附件', width: 100 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -370,7 +453,6 @@ const handleExport = async () => {
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
}
}
@@ -405,7 +487,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -423,7 +504,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -438,7 +518,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -453,7 +532,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -471,12 +549,12 @@ const getTypeDict = async () => {
typeList.value = []
}
} catch (err) {
console.error('获取类型字典失败', err)
typeList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -112,7 +112,6 @@ const openDialog = async (row: any) => {
form.belong = res.data.belong || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -156,7 +155,6 @@ const getBelongDict = async () => {
}))
}
} catch (err) {
console.error('获取归档级别字典失败', err)
}
}

View File

@@ -186,7 +186,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}

View File

@@ -83,9 +83,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -102,73 +119,37 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">标题</span>
</template>
</el-table-column>
<el-table-column prop="author" label="作者" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">作者</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">更新时间</span>
</template>
<template #default="scope">
<!-- 更新时间列特殊模板 -->
<template v-else-if="col.prop === 'updateTime'" #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="belong" label="归档级别" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">归档级别</span>
</template>
<template #default="scope">
<!-- 归档级别列特殊模板 -->
<template v-else-if="col.prop === 'belong'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatBelong(scope.row.belong) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">附件</span>
</template>
<template #default="scope">
<!-- 附件列特殊模板 -->
<template v-else-if="col.prop === 'attachment'" #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
@@ -181,6 +162,7 @@
<span v-else>-</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -228,7 +210,8 @@
</template>
<script setup lang="ts" name="ClassHonor">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classhonor";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -237,12 +220,15 @@ import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import BelongDialog from './belong.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Document, User, Collection, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Document, User, Collection, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const belongDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
@@ -252,6 +238,136 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const belongList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'title', label: '标题', minWidth: 200 },
{ prop: 'author', label: '作者' },
{ prop: 'updateTime', label: '更新时间', width: 180 },
{ prop: 'belong', label: '归档级别' },
{ prop: 'attachment', label: '附件', width: 100 }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
title: { icon: Document },
author: { icon: User },
updateTime: { icon: Clock },
belong: { icon: Collection },
attachment: { icon: Document }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -354,7 +470,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -372,7 +487,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -387,7 +501,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -402,7 +515,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -420,7 +532,6 @@ const getBelongDict = async () => {
belongList.value = []
}
} catch (err) {
console.error('获取归档级别字典失败', err)
belongList.value = []
}
}
@@ -432,6 +543,11 @@ onMounted(() => {
getDeptListData()
getClassListData()
getBelongDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -183,7 +183,6 @@ const getClassHygieneDailyData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -202,7 +201,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}

View File

@@ -75,6 +75,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -92,42 +109,26 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="时间" show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">时间</span>
</template>
</el-table-column>
<el-table-column prop="score" label="扣分" show-overflow-tooltip>
<template #header>
<el-icon><Minus /></el-icon>
<span style="margin-left: 4px">扣分</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'score'">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" type="danger" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="note" label="检查记录" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">检查记录</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -166,24 +167,131 @@
<script setup lang="ts" name="ClassHygieneDaily">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/classhygienedaily";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { List, OfficeBuilding, Grid, Calendar, Minus, Document, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, Grid, Calendar, Minus, Document, Setting, Menu } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'recordDate', label: '时间', icon: Calendar },
{ prop: 'score', label: '扣分', icon: Minus },
{ prop: 'note', label: '检查记录', icon: Document }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -269,7 +377,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -282,12 +389,12 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -257,7 +257,6 @@ const getClassMasterEvaluationData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -276,7 +275,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -289,7 +287,6 @@ const getAssessmentCategoryListData = async () => {
assessmentCategoryList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核项目列表失败', err)
assessmentCategoryList.value = []
}
}
@@ -302,7 +299,6 @@ const getAssessmentPointListData = async () => {
assessmentPointList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核指标列表失败', err)
assessmentPointList.value = []
}
}

View File

@@ -56,9 +56,26 @@
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -76,37 +93,34 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="totalScore" label="总分" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作' && !col.prop?.startsWith('day')"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">总分</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
<template #default="scope">
<!-- 总分列特殊模板 -->
<template v-if="col.prop === 'totalScore'" #default="scope">
<el-tag v-if="scope.row.totalScore !== undefined && scope.row.totalScore !== null" size="small" type="success" effect="plain">
{{ scope.row.totalScore }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="rank" label="排名" show-overflow-tooltip align="center">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">排名</span>
</template>
<template #default="scope">
<!-- 排名列特殊模板 -->
<template v-else-if="col.prop === 'rank'" #default="scope">
<el-tag v-if="scope.row.rank" size="small" :type="scope.row.rank <= 3 ? 'warning' : 'info'" effect="plain">
{{ scope.row.rank }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<!-- 动态日期列 -->
<!-- 动态日期列不受列控制影响始终显示 -->
<el-table-column
v-for="day in dayColumns"
:key="day"
@@ -202,16 +216,20 @@
</template>
<script setup lang="ts" name="ClassHygieneDailyAnalysis">
import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/classhygienedailyanalysis";
import { useMessage } from "/@/hooks/message";
import { getBuildingList } from '/@/api/stuwork/dormbuilding'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { List, DataAnalysis, Trophy, Grid, Calendar, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, DataAnalysis, Trophy, Grid, Calendar, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const scoreFormRef = ref()
// 搜索变量
const showSearch = ref(true)
@@ -221,6 +239,124 @@ const scoreDialogVisible = ref(false)
const scoreDialogTitle = ref('加分')
const isAddScore = ref(true) // true: 加分, false: 减分
// 表格列配置(只包含固定列,动态日期列不包含在内)
const tableColumns = [
{ prop: 'totalScore', label: '总分' },
{ prop: 'rank', label: '排名' },
{ prop: 'classNo', label: '班级' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
totalScore: { icon: DataAnalysis },
rank: { icon: Trophy },
classNo: { icon: Grid }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 加分/减分表单
const scoreForm = reactive({
classCode: '',
@@ -422,7 +558,6 @@ const getBuildingListData = async () => {
}
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -443,7 +578,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -452,5 +586,10 @@ const getClassListData = async () => {
onMounted(() => {
getBuildingListData()
getClassListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -133,7 +133,6 @@ const openDialog = async (id: string) => {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
@@ -150,7 +149,6 @@ const getSchoolTermDict = async () => {
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -166,7 +164,6 @@ const getLeaveTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
@@ -182,7 +179,6 @@ const getYesNoDict = async () => {
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
@@ -198,7 +194,6 @@ const getAuditTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取审核类型字典失败', err)
auditTypeList.value = []
}
}

View File

@@ -384,7 +384,6 @@ const getSchoolYearListData = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -400,7 +399,6 @@ const getSchoolTermDict = async () => {
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -413,7 +411,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -429,7 +426,6 @@ const getLeaveTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
@@ -445,7 +441,6 @@ const getYesNoDict = async () => {
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}

View File

@@ -120,6 +120,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -136,150 +153,68 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="num" label="人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'num'">
<el-tag v-if="scope.row.num" size="small" type="success" effect="plain">
{{ scope.row.num }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="请假开始时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">请假开始时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'startTime'">
<span>{{ scope.row.startTime ? formatDateTime(scope.row.startTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="请假结束时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">请假结束时间</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'endTime'">
<span>{{ scope.row.endTime ? formatDateTime(scope.row.endTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="leaveType" label="请假类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">请假类型</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'leaveType'">
<el-tag size="small" type="info" effect="plain">
{{ formatLeaveType(scope.row.leaveType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reason" label="请假事由" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">请假事由</span>
</template>
</el-table-column>
<el-table-column prop="stayDorm" label="是否住宿" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">是否住宿</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'stayDorm'">
<StatusTag
:value="scope.row.stayDorm"
:options="[{ label: '是', value: '1' }, { label: '否', value: '0' }]"
:type-map="{ '1': { type: 'success', effect: 'light' }, '0': { type: 'info', effect: 'light' } }"
/>
</template>
</el-table-column>
<el-table-column prop="isSegment" label="时段请假" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">时段请假</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'isSegment'">
<StatusTag
:value="scope.row.isSegment"
:options="[{ label: '是', value: '1' }, { label: '否', value: '0' }]"
:type-map="{ '1': { type: 'success', effect: 'light' }, '0': { type: 'info', effect: 'light' } }"
/>
</template>
</el-table-column>
<el-table-column prop="segmentString" label="请假时段" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">请假时段</span>
</template>
</el-table-column>
<el-table-column prop="schoolDoor" label="请假校门" show-overflow-tooltip align="center">
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px">请假校门</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'schoolDoor'">
<el-tag size="small" type="warning" effect="plain">
{{ formatSchoolDoor(scope.row.schoolDoor) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptAudit" label="基础部审核" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">基础部审核</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'deptAudit'">
<StatusTag
:value="scope.row.deptAudit"
:options="[{ label: '待审核', value: '0' }, { label: '通过', value: '1' }, { label: '驳回', value: '2' }]"
:type-map="{ '0': { type: 'warning', effect: 'light' }, '1': { type: 'success', effect: 'light' }, '2': { type: 'danger', effect: 'light' } }"
/>
</template>
</el-table-column>
<el-table-column prop="schoolAudit" label="学工处审批" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">学工处审批</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'schoolAudit'">
<StatusTag
:value="scope.row.schoolAudit"
:options="[{ label: '待审核', value: '0' }, { label: '通过', value: '1' }, { label: '驳回', value: '2' }]"
@@ -287,12 +222,6 @@
/>
</template>
</el-table-column>
<el-table-column prop="rejectReason" label="驳回原因" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">驳回原因</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -333,7 +262,8 @@
</template>
<script setup lang="ts" name="ClassLeaveApply">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, deleteByClass } from "/@/api/stuwork/classleaveapply";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
@@ -341,14 +271,17 @@ import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Collection, Document, House, Location, CircleCheck, Warning, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Collection, Document, House, Location, CircleCheck, Warning, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent } from 'vue'
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -361,6 +294,121 @@ const schoolDoorList = ref<any[]>([])
const formDialogRef = ref()
const detailDialogRef = ref()
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'teacherRealName', label: '班主任', icon: UserFilled },
{ prop: 'num', label: '人数', icon: UserFilled },
{ prop: 'startTime', label: '请假开始时间', icon: Calendar, width: 180 },
{ prop: 'endTime', label: '请假结束时间', icon: Calendar, width: 180 },
{ prop: 'leaveType', label: '请假类型', icon: Collection },
{ prop: 'reason', label: '请假事由', icon: Document, minWidth: 150 },
{ prop: 'stayDorm', label: '是否住宿', icon: House },
{ prop: 'isSegment', label: '时段请假', icon: Clock },
{ prop: 'segmentString', label: '请假时段', icon: Clock, minWidth: 150 },
{ prop: 'schoolDoor', label: '请假校门', icon: Location },
{ prop: 'deptAudit', label: '基础部审核', icon: CircleCheck },
{ prop: 'schoolAudit', label: '学工处审批', icon: CircleCheck },
{ prop: 'rejectReason', label: '驳回原因', icon: Warning, minWidth: 150 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 校门列表
const schoolDoorListData = [
{ label: '校门东', value: '0' },
@@ -506,7 +554,6 @@ const getSchoolYearListData = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -522,7 +569,6 @@ const getSchoolTermDict = async () => {
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -535,7 +581,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
@@ -548,7 +593,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -564,7 +608,6 @@ const getLeaveTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
@@ -580,7 +623,6 @@ const getYesNoDict = async () => {
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
@@ -596,12 +638,12 @@ const getAuditTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取审核类型字典失败', err)
auditTypeList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
schoolDoorList.value = schoolDoorListData
getSchoolYearListData()

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -257,7 +257,6 @@ const getClassMasterEvaluationData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -276,7 +275,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -289,7 +287,6 @@ const getAssessmentCategoryListData = async () => {
assessmentCategoryList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核项目列表失败', err)
assessmentCategoryList.value = []
}
}
@@ -302,7 +299,6 @@ const getAssessmentPointListData = async () => {
assessmentPointList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核指标列表失败', err)
assessmentPointList.value = []
}
}

View File

@@ -126,9 +126,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -146,77 +163,39 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="virtualClassNo" label="班号" show-overflow-tooltip>
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="assessmentCategory" label="考核项目" show-overflow-tooltip>
<template #header>
<el-icon><Tickets /></el-icon>
<span style="margin-left: 4px">考核项目</span>
</template>
<template #default="scope">
<!-- 考核项目列特殊模板 -->
<template v-if="col.prop === 'assessmentCategory'" #default="scope">
<span>{{ getAssessmentCategoryName(scope.row.assessmentCategory) || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="assessmentPoint" label="考核指标" show-overflow-tooltip>
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">考核指标</span>
</template>
<template #default="scope">
<!-- 考核指标列特殊模板 -->
<template v-else-if="col.prop === 'assessmentPoint'" #default="scope">
<span>{{ getAssessmentPointName(scope.row.assessmentPoint) || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" show-overflow-tooltip>
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">类型</span>
</template>
<template #default="scope">
<!-- 类型列特殊模板 -->
<template v-else-if="col.prop === 'type'" #default="scope">
<el-tag size="small" :type="scope.row.type === '1' ? 'success' : scope.row.type === '2' ? 'danger' : 'info'" effect="plain">
{{ scope.row.type === '1' ? '加分' : scope.row.type === '2' ? '减分' : '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="score" label="分数" show-overflow-tooltip>
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">分数</span>
</template>
<template #default="scope">
<!-- 分数列特殊模板 -->
<template v-else-if="col.prop === 'score'" #default="scope">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" :type="scope.row.score >= 0 ? 'success' : 'danger'" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="考核日期" show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">考核日期</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="考核人" show-overflow-tooltip>
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">考核人</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="情况说明" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">情况说明</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #header>
<el-icon><Setting /></el-icon>
@@ -309,7 +288,8 @@
</template>
<script setup lang="ts" name="ClassMasterEvaluation">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, computed, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, exportData } from "/@/api/stuwork/classmasterevaluation";
import { useMessage, useMessageBox } from "/@/hooks/message";
@@ -317,13 +297,16 @@ import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { getList as getAssessmentCategoryList } from '/@/api/stuwork/assessmentcategory'
import { getList as getAssessmentPointList } from '/@/api/stuwork/assessmentpoint'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, Grid, UserFilled, Tickets, DataAnalysis, Collection, Calendar, User, Document, Setting } from '@element-plus/icons-vue'
import { List, Grid, UserFilled, Tickets, DataAnalysis, Collection, Calendar, User, Document, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const searchFormRef = ref()
const uploadExcelRef = ref()
// 搜索变量
@@ -348,6 +331,136 @@ const appealForm = reactive({
appealReason: ''
})
// 表格列配置
const tableColumns = [
{ prop: 'virtualClassNo', label: '班号' },
{ prop: 'realName', label: '班主任' },
{ prop: 'assessmentCategory', label: '考核项目' },
{ prop: 'assessmentPoint', label: '考核指标' },
{ prop: 'type', label: '类型' },
{ prop: 'score', label: '分数' },
{ prop: 'recordDate', label: '考核日期' },
{ prop: 'createBy', label: '考核人' },
{ prop: 'remarks', label: '情况说明' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
virtualClassNo: { icon: Grid },
realName: { icon: UserFilled },
assessmentCategory: { icon: Tickets },
assessmentPoint: { icon: DataAnalysis },
type: { icon: Collection },
score: { icon: DataAnalysis },
recordDate: { icon: Calendar },
createBy: { icon: User },
remarks: { icon: Document }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -498,7 +611,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -511,7 +623,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -524,7 +635,6 @@ const getAssessmentCategoryListData = async () => {
assessmentCategoryList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核项目列表失败', err)
assessmentCategoryList.value = []
}
}
@@ -537,7 +647,6 @@ const getAssessmentPointListData = async () => {
assessmentPointList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取考核指标列表失败', err)
assessmentPointList.value = []
}
}
@@ -562,5 +671,10 @@ onMounted(() => {
getClassListData()
getAssessmentCategoryListData()
getAssessmentPointListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -65,6 +65,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -82,66 +99,20 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="考核日期" width="120" align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">考核日期</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="申诉日期" width="120" align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">申诉日期</span>
</template>
</el-table-column>
<el-table-column prop="assessmentCategory" label="考核项目" show-overflow-tooltip>
<template #header>
<el-icon><Tickets /></el-icon>
<span style="margin-left: 4px">考核项目</span>
</template>
</el-table-column>
<el-table-column prop="assessmentPoint" label="考核指标" show-overflow-tooltip>
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">考核指标</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="情况说明" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">情况说明</span>
</template>
</el-table-column>
<el-table-column prop="appealReason" label="申诉原因" show-overflow-tooltip>
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">申诉原因</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="考核人" show-overflow-tooltip>
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">考核人</span>
</template>
</el-table-column>
<el-table-column prop="appealStatus" label="申诉结果" width="100" align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">申诉结果</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'appealStatus'">
<StatusTag
:value="scope.row.appealStatus"
:options="[{ label: '待审核', value: '0' }, { label: '通过', value: '1' }, { label: '驳回', value: '2' }]"
@@ -149,12 +120,6 @@
/>
</template>
</el-table-column>
<el-table-column prop="appealReply" label="反馈意见" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">反馈意见</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -247,18 +212,22 @@
<script setup lang="ts" name="ClassMasterEvaluationAppeal">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, auditAppeal } from "/@/api/stuwork/classmasterevaluationappeal";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDeptList, getClassListByRole } from '/@/api/basic/basicclass'
import { getTypeValue } from '/@/api/admin/dict'
import { List, Grid, UserFilled, Calendar, Tickets, DataAnalysis, Document, Warning, User, CircleCheck, EditPen, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, Grid, UserFilled, Calendar, Tickets, DataAnalysis, Document, Warning, User, CircleCheck, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent } from 'vue'
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const auditFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
@@ -267,6 +236,115 @@ const appealStatusList = ref<any[]>([])
const auditDialogVisible = ref(false)
const auditLoading = ref(false)
// 表格列配置
const tableColumns = [
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'realName', label: '班主任', icon: UserFilled },
{ prop: 'recordDate', label: '考核日期', icon: Calendar, width: 120 },
{ prop: 'createTime', label: '申诉日期', icon: Calendar, width: 120 },
{ prop: 'assessmentCategory', label: '考核项目', icon: Tickets },
{ prop: 'assessmentPoint', label: '考核指标', icon: DataAnalysis },
{ prop: 'remarks', label: '情况说明', icon: Document },
{ prop: 'appealReason', label: '申诉原因', icon: Warning },
{ prop: 'createBy', label: '考核人', icon: User },
{ prop: 'appealStatus', label: '申诉结果', icon: CircleCheck, width: 100 },
{ prop: 'appealReply', label: '反馈意见', icon: EditPen }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 审核表单
const auditForm = reactive({
id: '',
@@ -404,7 +482,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -417,7 +494,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -433,12 +509,12 @@ const getAppealStatusListData = async () => {
})) : []
}
} catch (err) {
console.error('获取申诉结果字典失败', err)
appealStatusList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -164,7 +164,6 @@ const getTeacherList = async () => {
})
teacherList.value = res.data || []
} catch (err) {
console.error('获取教师列表失败', err)
}
}
@@ -177,7 +176,6 @@ const getClassList = async () => {
})
classList.value = res.data || []
} catch (err) {
console.error('获取班级列表失败', err)
}
}
@@ -285,7 +283,6 @@ const getClassMasterResumeData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false

View File

@@ -50,6 +50,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -67,22 +84,18 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="teacherNoVal" label="教师" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">教师</span>
</template>
</el-table-column>
<el-table-column prop="teacherNo" label="工号" show-overflow-tooltip>
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">工号</span>
</template>
</el-table-column>
<el-table-column prop="telPhone" label="联系方式" show-overflow-tooltip>
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">联系方式</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
@@ -118,26 +131,131 @@
</template>
<script setup lang="ts" name="ClassMasterResume">
import { ref, reactive, defineAsyncComponent, onMounted, nextTick } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/classmasterresume";
import request from "/@/utils/request";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 尝试直接导入看看是否能解决问题
import DetailDialog from './detail.vue';
import { List, User, CreditCard, Phone, Setting } from '@element-plus/icons-vue'
import { List, User, CreditCard, Phone, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const detailDialogRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
// 教师列表
const teacherList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'teacherNoVal', label: '教师', icon: User },
{ prop: 'teacherNo', label: '工号', icon: CreditCard },
{ prop: 'telPhone', label: '联系方式', icon: Phone }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
teacherNo: '',
@@ -173,7 +291,6 @@ const getTeacherList = async () => {
})
teacherList.value = res.data || []
} catch (err) {
console.error('获取教师列表失败', err)
}
}
@@ -201,35 +318,26 @@ const exportExcel = () => {
// 查看履历详情
const handleViewDetail = async (row: any) => {
console.log('handleViewDetail 被调用row:', row)
if (!row.teacherNo) {
console.error('缺少教师工号', row)
return
}
// 确保组件已挂载
await nextTick()
console.log('nextTick 后detailDialogRef.value:', detailDialogRef.value)
console.log('detailDialogRef.value?.openDialog:', detailDialogRef.value?.openDialog)
if (detailDialogRef.value && typeof detailDialogRef.value.openDialog === 'function') {
detailDialogRef.value.openDialog(row.teacherNo)
} else {
console.error('详情对话框组件未找到或 openDialog 方法不存在', {
detailDialogRef: detailDialogRef.value,
hasOpenDialog: detailDialogRef.value?.openDialog
})
}
};
// 初始化
loadSavedConfig()
onMounted(() => {
getTeacherList()
// 调试:检查组件是否已挂载
nextTick(() => {
console.log('组件挂载后detailDialogRef:', detailDialogRef.value)
})
})
</script>

View File

@@ -109,7 +109,6 @@ const openDialog = async (id: string) => {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
@@ -126,7 +125,6 @@ const getSchoolTermDict = async () => {
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}

View File

@@ -245,7 +245,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}

View File

@@ -103,9 +103,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -122,67 +139,37 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">标题</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">类型</span>
</template>
<template #default="scope">
<!-- 类型列特殊模板 -->
<template v-else-if="col.prop === 'type'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatType(scope.row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="journal" label="发表刊物" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="page" label="发表页码" show-overflow-tooltip align="center" />
<el-table-column prop="isAddScore" label="加分" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 加分列特殊模板 -->
<template v-else-if="col.prop === 'isAddScore'" #default="scope">
<span>{{ scope.row.isAddScore === '1' ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<!-- 附件列特殊模板 -->
<template v-else-if="col.prop === 'attachment'" #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
@@ -195,6 +182,7 @@
<span v-else>-</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
@@ -246,7 +234,8 @@
</template>
<script setup lang="ts" name="ClassPaper">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, addScore } from "/@/api/stuwork/classpaper";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -254,12 +243,15 @@ import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
import { List, Document, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Collection, Setting } from '@element-plus/icons-vue'
import { List, Document, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Collection, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const detailDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
@@ -272,6 +264,140 @@ const typeList = ref<any[]>([
{ label: '教案', value: '1' }
])
// 表格列配置
const tableColumns = [
{ prop: 'title', label: '标题', minWidth: 200 },
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'teacherRealName', label: '班主任' },
{ prop: 'type', label: '类型' },
{ prop: 'journal', label: '发表刊物', minWidth: 150 },
{ prop: 'page', label: '发表页码' },
{ prop: 'isAddScore', label: '加分' },
{ prop: 'attachment', label: '附件', width: 100 }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
title: { icon: Document },
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
teacherRealName: { icon: UserFilled },
type: { icon: Collection },
journal: { icon: Document },
page: { icon: Document },
isAddScore: { icon: Document },
attachment: { icon: Document }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -391,7 +517,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -409,7 +534,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -424,7 +548,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -439,7 +562,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -450,6 +572,11 @@ onMounted(() => {
getSchoolTermDict()
getDeptListData()
getClassListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -96,9 +96,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -115,67 +132,37 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">状态</span>
</template>
<template #default="scope">
<!-- 状态列特殊模板 -->
<template v-if="col.prop === 'status'" #default="scope">
<el-tag size="small" :type="scope.row.status === '1' ? 'success' : 'warning'" effect="plain">
{{ formatStatus(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-else-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">标题</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">更新时间</span>
</template>
<template #default="scope">
<!-- 更新时间列特殊模板 -->
<template v-else-if="col.prop === 'updateTime'" #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="填报人" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">填报人</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -213,7 +200,8 @@
</template>
<script setup lang="ts" name="ClassPlan">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classplan";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -222,11 +210,14 @@ import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, OfficeBuilding, Calendar, CircleCheck, Clock, Grid, Document, User, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, Calendar, CircleCheck, Clock, Grid, Document, User, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
@@ -235,6 +226,134 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const statusList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院' },
{ prop: 'schoolYear', label: '学年' },
{ prop: 'status', label: '状态' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'classNo', label: '班号' },
{ prop: 'title', label: '标题', minWidth: 200 },
{ prop: 'updateTime', label: '更新时间', width: 180 },
{ prop: 'createBy', label: '填报人' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
schoolYear: { icon: Calendar },
status: { icon: CircleCheck },
schoolTerm: { icon: Clock },
classNo: { icon: Grid },
title: { icon: Document },
updateTime: { icon: Clock },
createBy: { icon: User }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -317,7 +436,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -335,7 +453,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -350,7 +467,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -365,7 +481,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -383,7 +498,6 @@ const getStatusDict = async () => {
statusList.value = []
}
} catch (err) {
console.error('获取状态字典失败', err)
statusList.value = []
}
}
@@ -395,6 +509,11 @@ onMounted(() => {
getDeptListData()
getClassListData()
getStatusDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -112,7 +112,6 @@ const openDialog = async (row: any) => {
form.belong = res.data.belong || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -156,7 +155,6 @@ const getBelongDict = async () => {
}))
}
} catch (err) {
console.error('获取归档级别字典失败', err)
}
}

View File

@@ -193,7 +193,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}

View File

@@ -83,9 +83,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -102,73 +119,37 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">标题</span>
</template>
</el-table-column>
<el-table-column prop="author" label="作者" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">作者</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">更新时间</span>
</template>
<template #default="scope">
<!-- 更新时间列特殊模板 -->
<template v-else-if="col.prop === 'updateTime'" #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="belong" label="归档级别" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">归档级别</span>
</template>
<template #default="scope">
<!-- 归档级别列特殊模板 -->
<template v-else-if="col.prop === 'belong'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatBelong(scope.row.belong) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isAddScore" label="加分" show-overflow-tooltip align="center">
<template #header>
<el-icon><Plus /></el-icon>
<span style="margin-left: 4px">加分</span>
</template>
<template #default="scope">
<!-- 加分列特殊模板 -->
<template v-else-if="col.prop === 'isAddScore'" #default="scope">
<StatusTag
:value="scope.row.isAddScore"
:options="[{ label: '是', value: '1' }, { label: '否', value: '0' }]"
@@ -176,6 +157,7 @@
/>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="300" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -238,7 +220,8 @@
</template>
<script setup lang="ts" name="ClassPublicity">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, addScore } from "/@/api/stuwork/classpublicity";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -247,14 +230,17 @@ import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import BelongDialog from './belong.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Document, User, Collection, Plus, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Document, User, Collection, Plus, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent } from 'vue'
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const belongDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
@@ -264,6 +250,136 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const belongList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'title', label: '标题', minWidth: 200 },
{ prop: 'author', label: '作者' },
{ prop: 'updateTime', label: '更新时间', width: 180 },
{ prop: 'belong', label: '归档级别' },
{ prop: 'isAddScore', label: '加分' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
title: { icon: Document },
author: { icon: User },
updateTime: { icon: Clock },
belong: { icon: Collection },
isAddScore: { icon: Plus }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -381,7 +497,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -399,7 +514,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -414,7 +528,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -429,7 +542,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -447,7 +559,6 @@ const getBelongDict = async () => {
belongList.value = []
}
} catch (err) {
console.error('获取归档级别字典失败', err)
belongList.value = []
}
}
@@ -459,6 +570,11 @@ onMounted(() => {
getDeptListData()
getClassListData()
getBelongDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -175,7 +175,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -188,7 +187,6 @@ const getClassroomListData = async () => {
classroomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取教室列表失败', err)
classroomList.value = []
}
}
@@ -201,7 +199,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}

View File

@@ -81,9 +81,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -101,89 +118,50 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">楼号</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classStatus" label="班级状态" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">班级状态</span>
</template>
<template #default="scope">
<!-- 班级状态列特殊模板 -->
<template v-if="col.prop === 'classStatus'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatClassStatus(scope.row.classStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="position" label="教室位置" show-overflow-tooltip align="center">
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px">教室位置</span>
</template>
</el-table-column>
<el-table-column prop="stuNum" label="人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">人数</span>
</template>
<template #default="scope">
<!-- 人数列特殊模板 -->
<template v-else-if="col.prop === 'stuNum'" #default="scope">
<el-tag v-if="scope.row.stuNum" size="small" type="success" effect="plain">
{{ scope.row.stuNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="platformType" label="讲台类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">讲台类型</span>
</template>
<template #default="scope">
<!-- 讲台类型列特殊模板 -->
<template v-else-if="col.prop === 'platformType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.platformType, platformTypeList) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="tyType" label="投影类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">投影类型</span>
</template>
<template #default="scope">
<!-- 投影类型列特殊模板 -->
<template v-else-if="col.prop === 'tyType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.tyType, tyTypeList) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="tvType" label="电视机" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">电视机</span>
</template>
<template #default="scope">
<!-- 电视机列特殊模板 -->
<template v-else-if="col.prop === 'tvType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.tvType, tvTypeList) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="chairCnt" label="方凳数量" show-overflow-tooltip align="center" />
<el-table-column prop="tableCnt" label="课桌数量" show-overflow-tooltip align="center" />
<el-table-column prop="password" label="锁密码" show-overflow-tooltip align="center" />
</template>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="scope">
<el-button
@@ -210,18 +188,22 @@
</template>
<script setup lang="ts" name="ClassroomBase">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, exportData, syncClassroomArrangement } from "/@/api/stuwork/classroombase";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { queryAllClass } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import ArrangeDialog from './arrange.vue'
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
@@ -231,6 +213,142 @@ const tyTypeList = ref<any[]>([])
const tvTypeList = ref<any[]>([])
const arrangeDialogRef = ref()
// 表格列配置
const tableColumns = [
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classStatus', label: '班级状态' },
{ prop: 'position', label: '教室位置' },
{ prop: 'stuNum', label: '人数' },
{ prop: 'teacherRealName', label: '班主任' },
{ prop: 'platformType', label: '讲台类型' },
{ prop: 'tyType', label: '投影类型' },
{ prop: 'tvType', label: '电视机' },
{ prop: 'chairCnt', label: '方凳数量' },
{ prop: 'tableCnt', label: '课桌数量' },
{ prop: 'password', label: '锁密码' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
buildingNo: { icon: OfficeBuilding },
deptName: { icon: OfficeBuilding },
classStatus: { icon: CircleCheck },
position: { icon: Location },
stuNum: { icon: UserFilled },
teacherRealName: { icon: UserFilled },
platformType: { icon: Collection },
tyType: { icon: Collection },
tvType: { icon: Collection },
chairCnt: { icon: Calendar },
tableCnt: { icon: Calendar },
password: { icon: Calendar }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 班级状态列表
const classStatusListData = [
{ label: '在校', value: '0' },
@@ -296,7 +414,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
}
} catch (err: any) {
// 确保即使出错也返回正确的数据结构
console.error('获取数据失败', err)
throw err
}
},
@@ -391,7 +508,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
@@ -404,7 +520,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -420,7 +535,6 @@ const getPlatformTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取讲台类型字典失败', err)
platformTypeList.value = []
}
}
@@ -436,7 +550,6 @@ const getTyTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取投影类型字典失败', err)
tyTypeList.value = []
}
}
@@ -452,7 +565,6 @@ const getTvTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取电视机类型字典失败', err)
tvTypeList.value = []
}
}
@@ -465,6 +577,11 @@ onMounted(() => {
getPlatformTypeDict()
getTyTypeDict()
getTvTypeDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -88,7 +88,6 @@ const getDetailList = () => {
detailList.value = []
}
}).catch((err: any) => {
console.error('获取履历详情失败', err)
detailList.value = []
}).finally(() => {
loading.value = false

View File

@@ -220,7 +220,6 @@ const getClassRoomHygieneDailyData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -235,7 +234,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -248,7 +246,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}

View File

@@ -75,6 +75,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -92,42 +109,26 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="时间" show-overflow-tooltip width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">时间</span>
</template>
</el-table-column>
<el-table-column prop="score" label="扣分" show-overflow-tooltip width="80">
<template #header>
<el-icon><Minus /></el-icon>
<span style="margin-left: 4px">扣分</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'score'">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" type="danger" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="note" label="检查记录" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">检查记录</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -165,25 +166,132 @@
</template>
<script setup lang="ts" name="ClassRoomHygieneDaily">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/classroomhygienedaily";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { List, OfficeBuilding, Grid, Calendar, Minus, Document, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, Grid, Calendar, Minus, Document, Setting, Menu } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'recordDate', label: '时间', icon: Calendar, width: 120 },
{ prop: 'score', label: '扣分', icon: Minus, width: 80 },
{ prop: 'note', label: '检查记录', icon: Document }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -235,7 +343,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -248,12 +355,12 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getDeptListData()
getClassListData()

View File

@@ -56,9 +56,26 @@
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -76,37 +93,34 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="totalScore" label="总分" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作' && !col.prop?.startsWith('day')"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">总分</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
<template #default="scope">
<!-- 总分列特殊模板 -->
<template v-if="col.prop === 'totalScore'" #default="scope">
<el-tag v-if="scope.row.totalScore !== undefined && scope.row.totalScore !== null" size="small" type="success" effect="plain">
{{ scope.row.totalScore }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="rank" label="排名" show-overflow-tooltip align="center">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">排名</span>
</template>
<template #default="scope">
<!-- 排名列特殊模板 -->
<template v-else-if="col.prop === 'rank'" #default="scope">
<el-tag v-if="scope.row.rank" size="small" :type="scope.row.rank <= 3 ? 'warning' : 'info'" effect="plain">
{{ scope.row.rank }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<!-- 动态日期列 -->
<!-- 动态日期列不受列控制影响始终显示 -->
<el-table-column
v-for="day in dayColumns"
:key="day"
@@ -202,16 +216,20 @@
</template>
<script setup lang="ts" name="ClassRoomHygieneDailyAnalysis">
import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/classroomhygienedailyanalysis";
import { useMessage } from "/@/hooks/message";
import { getBuildingList } from '/@/api/stuwork/teachbuilding'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { List, DataAnalysis, Trophy, Grid, Calendar, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, DataAnalysis, Trophy, Grid, Calendar, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const scoreFormRef = ref()
// 搜索变量
const showSearch = ref(true)
@@ -221,6 +239,124 @@ const scoreDialogVisible = ref(false)
const scoreDialogTitle = ref('加分')
const isAddScore = ref(true) // true: 加分, false: 减分
// 表格列配置(只包含固定列,动态日期列不包含在内)
const tableColumns = [
{ prop: 'totalScore', label: '总分' },
{ prop: 'rank', label: '排名' },
{ prop: 'classNo', label: '班级' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
totalScore: { icon: DataAnalysis },
rank: { icon: Trophy },
classNo: { icon: Grid }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 加分/减分表单
const scoreForm = reactive({
classCode: '',
@@ -422,7 +558,6 @@ const getBuildingListData = async () => {
}
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -443,7 +578,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -452,6 +586,11 @@ const getClassListData = async () => {
onMounted(() => {
getBuildingListData()
getClassListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -81,7 +81,6 @@ const getWeekPlanDetail = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false

View File

@@ -180,7 +180,6 @@ const getWeekPlanData = (id: string) => {
}
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
@@ -195,7 +194,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}

View File

@@ -92,15 +92,33 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
@@ -112,83 +130,38 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip>
<template v-for="col in sortedTableColumns" :key="col.prop">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="classMasterName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="教学楼号" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">教学楼号</span>
</template>
</el-table-column>
<el-table-column prop="position" label="教室号" show-overflow-tooltip>
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px">教室号</span>
</template>
</el-table-column>
<el-table-column prop="score" label="评分" show-overflow-tooltip>
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">评分</span>
</template>
<template #default="scope">
<!-- 评分列特殊模板 -->
<template v-else-if="col.prop === 'score'" #default="scope">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" :type="scope.row.score >= 80 ? 'success' : scope.row.score >= 60 ? 'warning' : 'danger'" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="note" label="检查记录" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">检查记录</span>
</template>
</el-table-column>
<el-table-column prop="month" label="月份" show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">月份</span>
</template>
<template #default="scope">
<!-- 月份列特殊模板 -->
<template v-else-if="col.prop === 'month'" #default="scope">
<el-tag v-if="scope.row.month" size="small" type="info" effect="plain">
{{ scope.row.month }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -276,30 +249,272 @@
<script setup lang="ts" name="ClassRoomHygieneMonthly">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, checkClassRoomHygieneMonthly } from "/@/api/stuwork/classroomhygienemonthly";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { getDicts } from '/@/api/admin/dict'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import type { TableInstance } from 'element-plus'
// 引入组件
const UploadExcel = defineAsyncComponent(() => import('/@/components/Upload/Excel.vue'));
import { List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Location, DataAnalysis, Document, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Location, DataAnalysis, Document, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const uploadExcelRef = ref()
const checkFormRef = ref()
const tableRef = ref<TableInstance>()
const columnControlRef = ref<any>()
// 搜索变量
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const checkDialogVisible = ref(false)
// 模板文件URL
const templateUrl = ref('assets/file/教室月卫生导入模板.xlsx')
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'classMasterName', label: '班主任' },
{ prop: 'buildingNo', label: '教学楼号' },
{ prop: 'position', label: '教室号' },
{ prop: 'score', label: '评分' },
{ prop: 'note', label: '检查记录' },
{ prop: 'month', label: '月份' },
{ prop: '操作', label: '操作', alwaysShow: true, fixed: 'right' as const }
]
// 当前显示的列(从 localStorage 读取或默认全部显示)
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置(在组件创建时)
const loadSavedConfig = () => {
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
// 使用保存的列配置(即使数量少于所有列,也使用保存的配置)
if (filteredSaved.length > 0) {
visibleColumns.value = filteredSaved
} else {
// 如果保存的配置为空或无效,使用所有列
visibleColumns.value = validColumns
}
} catch (e) {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载列排序配置
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
// 添加新列到排序列表末尾
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 立即加载保存的配置
loadSavedConfig()
// 初始化可见列(已废弃,使用 loadSavedConfig 代替)
const initVisibleColumns = () => {
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 这里只做兼容性处理,不重复加载
}
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
// 如果 visibleColumns 还没初始化,默认显示所有列
if (visibleColumns.value.length === 0) {
return true
}
// 检查 prop 是否在可见列列表中
const isVisible = visibleColumns.value.includes(prop)
return isVisible
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
// 只保存可选择的列(排除固定列和 alwaysShow 列)
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
// 保存到 localStorage
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
// 保存排序顺序到 localStorage
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 初始化列排序顺序(已废弃,使用 loadSavedConfig 代替)
const initColumnOrder = () => {
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 这里只做兼容性处理,不重复加载
}
// 获取排序后的列配置
const sortedTableColumns = computed(() => {
// 获取所有可排序的列
const allSortableColumns = tableColumns.filter(col => !col.alwaysShow && !col.fixed)
if (columnOrder.value.length === 0) {
return allSortableColumns
}
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
// 先按照保存的顺序添加列
columnOrder.value.forEach(key => {
const col = tableColumns.find(c => {
const colKey = c.prop || c.label
return colKey === key && !c.alwaysShow && !c.fixed
})
if (col) {
orderedColumns.push(col)
}
})
// 添加未在排序列表中的列(新增的列)
allSortableColumns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
})
// 列配置映射,包含每个列的渲染信息
const columnConfigMap: Record<string, any> = {
schoolYear: {
prop: 'schoolYear',
label: '学年',
icon: Calendar,
template: null
},
schoolTerm: {
prop: 'schoolTerm',
label: '学期',
icon: Clock,
template: 'schoolTerm'
},
deptName: {
prop: 'deptName',
label: '学院',
icon: OfficeBuilding,
template: null
},
classNo: {
prop: 'classNo',
label: '班号',
icon: Grid,
template: null
},
classMasterName: {
prop: 'classMasterName',
label: '班主任',
icon: UserFilled,
template: null
},
buildingNo: {
prop: 'buildingNo',
label: '教学楼号',
icon: OfficeBuilding,
template: null
},
position: {
prop: 'position',
label: '教室号',
icon: Location,
template: null
},
score: {
prop: 'score',
label: '评分',
icon: DataAnalysis,
template: 'score'
},
note: {
prop: 'note',
label: '检查记录',
icon: Document,
template: null
},
month: {
prop: 'month',
label: '月份',
icon: Calendar,
template: 'month'
}
}
// 初始化函数会在 onMounted 中调用,确保 DOM 已准备好
// 注意visibleColumns 和 columnOrder 需要在组件挂载前初始化,以便传递给 TableColumnControl
// 考核表单
const checkForm = reactive({
schoolYear: '',
@@ -386,14 +601,7 @@ const confirmCheck = async () => {
try {
await useMessageBox().confirm(
`是否确认对${monthDisplay}月进行考核?\n1如果当前指定年月份已经有考核数据则会做覆盖处理\n2考核对象为所有有评分的班级`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: false
}
`是否确认对${monthDisplay}月进行考核?\n1如果当前指定年月份已经有考核数据则会做覆盖处理\n2考核对象为所有有评分的班级`
)
} catch {
return
@@ -416,11 +624,7 @@ const confirmCheck = async () => {
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await useMessageBox().confirm('此操作将永久删除')
} catch {
return
}
@@ -442,7 +646,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -455,7 +658,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -468,15 +670,49 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
schoolTermList.value = []
}
}
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 初始化
onMounted(() => {
getSchoolYearList()
getDeptListData()
getClassListData()
getSchoolTermDict()
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 确保配置已同步到 TableColumnControl 组件
nextTick(() => {
// 确保 visibleColumns 已正确加载
if (visibleColumns.value.length === 0) {
// 如果 visibleColumns 为空,重新加载配置
loadSavedConfig()
}
// visibleColumns 已经通过 v-model 绑定到 TableColumnControl应该会自动同步
})
})
</script>

View File

@@ -122,7 +122,6 @@ const openDialog = async (row: any) => {
useMessage().error('获取详情失败')
}
} catch (err: any) {
console.error('获取详情失败', err)
useMessage().error(err.msg || '获取详情失败')
} finally {
loading.value = false
@@ -140,7 +139,6 @@ const getSchoolTermDict = async () => {
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}

View File

@@ -267,7 +267,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}

View File

@@ -86,6 +86,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -102,67 +119,31 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="themeName" label="活动主题" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">活动主题</span>
</template>
</el-table-column>
<el-table-column prop="author" label="主持人" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">主持人</span>
</template>
</el-table-column>
<el-table-column prop="address" label="活动地点" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Location /></el-icon>
<span style="margin-left: 4px">活动地点</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="活动时间" show-overflow-tooltip align="center" width="120">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">活动时间</span>
</template>
</el-table-column>
<el-table-column prop="attendNum" label="参加人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">参加人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attendNum'">
<el-tag v-if="scope.row.attendNum" size="small" type="success" effect="plain">
{{ scope.row.attendNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="活动照片" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attachment'">
<el-button
v-if="scope.row.attachment"
icon="Picture"
@@ -174,9 +155,7 @@
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment2" label="活动照片2" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'attachment2'">
<el-button
v-if="scope.row.attachment2"
icon="Picture"
@@ -236,26 +215,138 @@
</template>
<script setup lang="ts" name="ClassSafeEdu">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel } from "/@/api/stuwork/classsafeedu";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
import { List, Calendar, Clock, Grid, Trophy, User, Location, UserFilled, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, Grid, Trophy, User, Location, UserFilled, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'themeName', label: '活动主题', icon: Trophy, minWidth: 150 },
{ prop: 'author', label: '主持人', icon: User },
{ prop: 'address', label: '活动地点', icon: Location, minWidth: 150 },
{ prop: 'recordDate', label: '活动时间', icon: Calendar, width: 120 },
{ prop: 'attendNum', label: '参加人数', icon: UserFilled },
{ prop: 'attachment', label: '活动照片', width: 100 },
{ prop: 'attachment2', label: '活动照片2', width: 100 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -346,7 +437,6 @@ const handleExport = async () => {
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
}
}
@@ -386,7 +476,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -404,7 +493,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -419,12 +507,12 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -91,7 +91,6 @@ const openDialog = async (id: string) => {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
@@ -108,7 +107,6 @@ const getSchoolTermDict = async () => {
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
@@ -123,7 +121,6 @@ const getStatusDict = async () => {
}))
}
} catch (err) {
console.error('获取状态字典失败', err)
}
}

View File

@@ -332,7 +332,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}

View File

@@ -99,6 +99,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -115,167 +132,91 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="totalCnt" label="总人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">总人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'totalCnt'">
<el-tag v-if="scope.row.totalCnt" size="small" type="success" effect="plain">
{{ scope.row.totalCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="boyCnt" label="男生人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Male /></el-icon>
<span style="margin-left: 4px">男生人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'boyCnt'">
<el-tag v-if="scope.row.boyCnt" size="small" type="primary" effect="plain">
{{ scope.row.boyCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="girlCnt" label="女生人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Female /></el-icon>
<span style="margin-left: 4px">女生人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'girlCnt'">
<el-tag v-if="scope.row.girlCnt" size="small" type="danger" effect="plain">
{{ scope.row.girlCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="boyDormCnt" label="男(住宿)" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">(住宿)</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'boyDormCnt'">
<el-tag v-if="scope.row.boyDormCnt" size="small" type="info" effect="plain">
{{ scope.row.boyDormCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="girlDormCnt" label="女(住宿)" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">(住宿)</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'girlDormCnt'">
<el-tag v-if="scope.row.girlDormCnt" size="small" type="info" effect="plain">
{{ scope.row.girlDormCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="keepSchoolCnt" label="留校察看人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">留校察看人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'keepSchoolCnt'">
<el-tag v-if="scope.row.keepSchoolCnt" size="small" type="warning" effect="plain">
{{ scope.row.keepSchoolCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="gigCnt" label="记过人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">记过人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'gigCnt'">
<el-tag v-if="scope.row.gigCnt" size="small" type="warning" effect="plain">
{{ scope.row.gigCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="seriousWarningCnt" label="严重警告人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">严重警告人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'seriousWarningCnt'">
<el-tag v-if="scope.row.seriousWarningCnt" size="small" type="warning" effect="plain">
{{ scope.row.seriousWarningCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="warningCnt" label="警告人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">警告人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'warningCnt'">
<el-tag v-if="scope.row.warningCnt" size="small" type="warning" effect="plain">
{{ scope.row.warningCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="revokeCnt" label="撤销处分人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">撤销处分人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'revokeCnt'">
<el-tag v-if="scope.row.revokeCnt" size="small" type="success" effect="plain">
{{ scope.row.revokeCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="dropCnt" label="退学人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">退学人数</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'dropCnt'">
<el-tag v-if="scope.row.dropCnt" size="small" type="danger" effect="plain">
{{ scope.row.dropCnt }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">状态</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'status'">
<el-tag size="small" :type="scope.row.status === '1' ? 'success' : 'warning'" effect="plain">
{{ formatStatus(scope.row.status) }}
</el-tag>
@@ -328,7 +269,8 @@
</template>
<script setup lang="ts" name="ClassSummary">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classsummary";
import { getTeachDept } from "/@/api/basic/basicdept";
@@ -336,14 +278,17 @@ import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
import { List, Calendar, Clock, Grid, UserFilled, Male, Female, House, Warning, CircleCheck, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, Grid, UserFilled, Male, Female, House, Warning, CircleCheck, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -351,6 +296,119 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const statusList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'totalCnt', label: '总人数', icon: UserFilled },
{ prop: 'boyCnt', label: '男生人数', icon: Male },
{ prop: 'girlCnt', label: '女生人数', icon: Female },
{ prop: 'boyDormCnt', label: '男(住宿)', icon: House },
{ prop: 'girlDormCnt', label: '女(住宿)', icon: House },
{ prop: 'keepSchoolCnt', label: '留校察看人数', icon: Warning },
{ prop: 'gigCnt', label: '记过人数', icon: Warning },
{ prop: 'seriousWarningCnt', label: '严重警告人数', icon: Warning },
{ prop: 'warningCnt', label: '警告人数', icon: Warning },
{ prop: 'revokeCnt', label: '撤销处分人数', icon: CircleCheck },
{ prop: 'dropCnt', label: '退学人数', icon: UserFilled },
{ prop: 'status', label: '状态', icon: CircleCheck }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -438,7 +496,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -456,7 +513,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -471,7 +527,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取开课部门列表失败', err)
deptList.value = []
}
}
@@ -486,7 +541,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
@@ -504,12 +558,12 @@ const getStatusDict = async () => {
statusList.value = []
}
} catch (err) {
console.error('获取状态字典失败', err)
statusList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -96,9 +96,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -115,53 +132,34 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="count" label="上传次数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Upload /></el-icon>
<span style="margin-left: 4px">上传次数</span>
</template>
<template #default="scope">
<!-- 上传次数列特殊模板 -->
<template v-else-if="col.prop === 'count'" #default="scope">
<el-tag v-if="scope.row.count" size="small" type="success" effect="plain">
{{ scope.row.count }}
</el-tag>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -202,7 +200,8 @@
</template>
<script setup lang="ts" name="ClassTheme">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classtheme";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -210,12 +209,15 @@ import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import InitDialog from './init.vue'
import RecordDialog from './record.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Upload, EditPen, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Upload, EditPen, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const initDialogRef = ref()
const columnControlRef = ref<any>()
const recordDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
@@ -225,6 +227,130 @@ const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const yesNoList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班级' },
{ prop: 'count', label: '上传次数' },
{ prop: 'remarks', label: '备注', minWidth: 200 }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
count: { icon: Upload },
remarks: { icon: EditPen }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
@@ -305,7 +431,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -323,7 +448,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -338,7 +462,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -353,7 +476,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -371,7 +493,6 @@ const getYesNoDict = async () => {
yesNoList.value = []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
@@ -383,6 +504,11 @@ onMounted(() => {
getDeptListData()
getClassListData()
getYesNoDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -175,7 +175,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
@@ -190,7 +189,6 @@ const getSchoolTermDict = async () => {
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
@@ -202,7 +200,6 @@ const getDeptListData = async () => {
deptList.value = res.data
}
} catch (err) {
console.error('获取学院列表失败', err)
}
}
@@ -214,7 +211,6 @@ const getClassListData = async () => {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}

View File

@@ -196,7 +196,6 @@ const getRecordList = async () => {
pagination.total = 0
}
} catch (err) {
console.error('获取上传记录失败', err)
recordList.value = []
pagination.total = 0
} finally {

View File

@@ -153,7 +153,6 @@ const loadData = async () => {
isPage.value = false
}
} catch (err: any) {
console.error('加载空宿舍列表失败', err)
useMessage().error(err.msg || '加载失败')
tableData.value = []
} finally {

View File

@@ -13,9 +13,26 @@
</el-button>
<right-toolbar
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -35,62 +52,47 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">楼号</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="layers" label="层数" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">层数</span>
</template>
<template #default="scope">
<!-- 层数列特殊模板 -->
<template v-if="col.prop === 'layers'" #default="scope">
<el-tag v-if="scope.row.layers" size="small" type="info" effect="plain">
{{ scope.row.layers }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="allAlreadyNum" label="已住人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">已住人数</span>
</template>
<template #default="scope">
<!-- 已住人数列特殊模板 -->
<template v-else-if="col.prop === 'allAlreadyNum'" #default="scope">
<el-tag v-if="scope.row.allAlreadyNum !== undefined && scope.row.allAlreadyNum !== null" size="small" type="success" effect="plain">
{{ scope.row.allAlreadyNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="nowNum" label="现住人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">现住人数</span>
</template>
<template #default="scope">
<!-- 现住人数列特殊模板 -->
<template v-else-if="col.prop === 'nowNum'" #default="scope">
<el-tag v-if="scope.row.nowNum !== undefined && scope.row.nowNum !== null" size="small" type="primary" effect="plain">
{{ scope.row.nowNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="surplusNum" label="剩余可住人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">剩余可住人数</span>
</template>
<template #default="scope">
<!-- 剩余可住人数列特殊模板 -->
<template v-else-if="col.prop === 'surplusNum'" #default="scope">
<el-tag v-if="scope.row.surplusNum !== undefined && scope.row.surplusNum !== null" size="small" type="warning" effect="plain">
{{ scope.row.surplusNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum" label="空宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum > 0"
type="primary"
@@ -100,9 +102,8 @@
</el-link>
<span v-else>{{ scope.row.roomEmptyNum || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum5" label="空5人宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空5人宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum5'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum5 > 0"
type="primary"
@@ -112,9 +113,8 @@
</el-link>
<span v-else>{{ scope.row.roomEmptyNum5 || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum4" label="空4人宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空4人宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum4'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum4 > 0"
type="primary"
@@ -124,9 +124,8 @@
</el-link>
<span v-else>{{ scope.row.roomEmptyNum4 || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum3" label="空3人宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空3人宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum3'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum3 > 0"
type="primary"
@@ -136,9 +135,8 @@
</el-link>
<span v-else>{{ scope.row.roomEmptyNum3 || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum2" label="空2人宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空2人宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum2'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum2 > 0"
type="primary"
@@ -148,9 +146,8 @@
</el-link>
<span v-else>{{ scope.row.roomEmptyNum2 || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="roomEmptyNum1" label="空1人宿舍数" show-overflow-tooltip align="center">
<template #default="scope">
<!-- 空1人宿舍数列特殊模板 -->
<template v-else-if="col.prop === 'roomEmptyNum1'" #default="scope">
<el-link
v-if="scope.row.roomEmptyNum1 > 0"
type="primary"
@@ -161,7 +158,7 @@
<span v-else>{{ scope.row.roomEmptyNum1 || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip align="center" />
</template>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -202,19 +199,159 @@
</template>
<script setup lang="ts" name="DormBuilding">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { ref, reactive, defineAsyncComponent, computed, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/dormbuilding";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const EmptyRoomDialog = defineAsyncComponent(() => import('./emptyRoomDialog.vue'));
import { List, OfficeBuilding, Grid, UserFilled, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, Grid, UserFilled, Setting, Menu, Calendar } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const emptyRoomDialogRef = ref()
const columnControlRef = ref<any>()
// 表格列配置
const tableColumns = [
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'layers', label: '层数' },
{ prop: 'allAlreadyNum', label: '已住人数' },
{ prop: 'nowNum', label: '现住人数' },
{ prop: 'surplusNum', label: '剩余可住人数' },
{ prop: 'roomEmptyNum', label: '空宿舍数' },
{ prop: 'roomEmptyNum5', label: '空5人宿舍数' },
{ prop: 'roomEmptyNum4', label: '空4人宿舍数' },
{ prop: 'roomEmptyNum3', label: '空3人宿舍数' },
{ prop: 'roomEmptyNum2', label: '空2人宿舍数' },
{ prop: 'roomEmptyNum1', label: '空1人宿舍数' },
{ prop: 'phone', label: '电话' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
buildingNo: { icon: OfficeBuilding },
layers: { icon: Grid },
allAlreadyNum: { icon: UserFilled },
nowNum: { icon: UserFilled },
surplusNum: { icon: UserFilled },
roomEmptyNum: { icon: UserFilled },
roomEmptyNum5: { icon: UserFilled },
roomEmptyNum4: { icon: UserFilled },
roomEmptyNum3: { icon: UserFilled },
roomEmptyNum2: { icon: UserFilled },
roomEmptyNum1: { icon: UserFilled },
phone: { icon: Calendar }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
@@ -306,4 +443,13 @@ const handleViewEmptyRoom = (row: any, roomType: string) => {
emptyRoomDialogRef.value.openDialog(row.buildingNo, roomType)
}
}
// 初始化
onMounted(() => {
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -1,6 +1,34 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<right-toolbar
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
@@ -14,24 +42,19 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">楼号</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="username" label="管理员工号" show-overflow-tooltip align="center">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">管理员工号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="管理员姓名" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">管理员姓名</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -59,11 +82,135 @@
</template>
<script setup lang="ts" name="DormBuildingManger">
import { reactive } from 'vue'
import { reactive, ref, computed, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/dormbuildingmanger";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, OfficeBuilding, CreditCard, UserFilled, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, CreditCard, UserFilled, Setting, Menu, Calendar } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const columnControlRef = ref<any>()
// 表格列配置
const tableColumns = [
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'username', label: '管理员工号' },
{ prop: 'realName', label: '管理员姓名' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
buildingNo: { icon: OfficeBuilding },
username: { icon: CreditCard },
realName: { icon: UserFilled }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
@@ -98,5 +245,14 @@ const handleDelete = async (row: any) => {
}
}
}
// 初始化
onMounted(() => {
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -155,7 +155,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}

View File

@@ -84,9 +84,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -104,50 +121,29 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<!-- 学期列特殊模板 -->
<template v-if="col.prop === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ scope.row.schoolTerm === '1' ? '第一学期' : scope.row.schoolTerm === '2' ? '第二学期' : scope.row.schoolTerm }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">楼号</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">宿舍号</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="记录日期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">记录日期</span>
</template>
<template #default="scope">
<!-- 记录日期列特殊模板 -->
<template v-else-if="col.prop === 'recordDate'" #default="scope">
<span>{{ scope.row.recordDate ? scope.row.recordDate.split(' ')[0] : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="note" label="情况记录" show-overflow-tooltip align="center">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">情况记录</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -185,7 +181,8 @@
</template>
<script setup lang="ts" name="DormHygieneDaily">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/dormhygienedaily";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
@@ -193,13 +190,16 @@ import { getDormRoomDataByBuildingNo } from "/@/api/stuwork/dormroom";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDicts } from "/@/api/admin/dict";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, Calendar, Clock, OfficeBuilding, House, Document, Setting } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, House, Document, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
@@ -207,6 +207,130 @@ const schoolTermList = ref<any[]>([])
const buildingList = ref<any[]>([])
const roomList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'roomNo', label: '宿舍号' },
{ prop: 'recordDate', label: '记录日期' },
{ prop: 'note', label: '情况记录' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
schoolYear: { icon: Calendar },
schoolTerm: { icon: Clock },
buildingNo: { icon: OfficeBuilding },
roomNo: { icon: House },
recordDate: { icon: Calendar },
note: { icon: Document }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
schoolYear: '',
@@ -299,7 +423,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -315,7 +438,6 @@ const getSchoolTermDict = async () => {
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -328,7 +450,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -345,7 +466,6 @@ const getRoomListData = async (buildingNo: string) => {
roomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取房间列表失败', err)
roomList.value = []
}
}
@@ -355,5 +475,10 @@ onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getBuildingListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -123,7 +123,6 @@ const getDetailData = async (id: string) => {
}
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
@@ -181,7 +180,6 @@ const getLiveTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取留宿类型字典失败', err)
liveTypeList.value = []
}
}

View File

@@ -112,6 +112,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -128,45 +145,28 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center" />
<el-table-column prop="liveType" label="留宿类型" show-overflow-tooltip align="center">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'liveType'">
<span>{{ formatLiveType(scope.row.liveType) }}</span>
</template>
</el-table-column>
<el-table-column prop="liveDate" label="留宿日期" show-overflow-tooltip align="center" />
<el-table-column prop="auditStatus" label="审核状态" show-overflow-tooltip align="center">
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'auditStatus'">
<span>{{ formatAuditStatus(scope.row.auditStatus) }}</span>
</template>
</el-table-column>
@@ -203,7 +203,8 @@
</template>
<script setup lang="ts" name="DormLiveApply">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportData } from "/@/api/stuwork/dormliveapply";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
@@ -211,11 +212,14 @@ import { getDicts } from "/@/api/admin/dict";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import { List, Calendar, Clock, OfficeBuilding, Grid } from '@element-plus/icons-vue'
import { List, Calendar, Clock, OfficeBuilding, Grid, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -225,6 +229,113 @@ const liveTypeList = ref<any[]>([])
const auditStatusList = ref<any[]>([])
const formDialogRef = ref()
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'realName', label: '姓名' },
{ prop: 'roomNo', label: '宿舍号' },
{ prop: 'liveType', label: '留宿类型' },
{ prop: 'liveDate', label: '留宿日期' },
{ prop: 'auditStatus', label: '审核状态' }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 搜索表单
const searchForm = reactive({
schoolYear: '',
@@ -341,7 +452,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -357,7 +467,6 @@ const getSchoolTermDict = async () => {
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -370,7 +479,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -383,7 +491,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -399,7 +506,6 @@ const getLiveTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取留宿类型字典失败', err)
liveTypeList.value = []
}
}
@@ -415,12 +521,12 @@ const getAuditStatusDict = async () => {
})) : []
}
} catch (err) {
console.error('获取审核状态字典失败', err)
auditStatusList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -71,9 +71,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -91,45 +108,23 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNos" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="房间号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">房间号</span>
</template>
</el-table-column>
<el-table-column prop="reformDate" label="整改时间" show-overflow-tooltip align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">整改时间</span>
</template>
<template #default="scope">
<!-- 整改时间列特殊模板 -->
<template v-if="col.prop === 'reformDate'" #default="scope">
<span>{{ scope.row.reformDate ? scope.row.reformDate.split(' ')[0] : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="reformContent" label="整改内容" show-overflow-tooltip align="center">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">整改内容</span>
</template>
</el-table-column>
<el-table-column prop="reformStatus" label="整改结果" show-overflow-tooltip align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">整改结果</span>
</template>
<template #default="scope">
<!-- 整改结果列特殊模板 -->
<template v-else-if="col.prop === 'reformStatus'" #default="scope">
<StatusTag
:value="scope.row.reformStatus"
:options="[{ label: '合格', value: '合格' }, { label: '不合格', value: '不合格' }, { label: '未整改', value: '未整改' }]"
@@ -137,12 +132,7 @@
/>
</template>
</el-table-column>
<el-table-column prop="remarks" label="关联扣分明细" show-overflow-tooltip align="center">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">关联扣分明细</span>
</template>
</el-table-column>
<el-table-column label="操作" width="350" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -201,7 +191,8 @@
</template>
<script setup lang="ts" name="DormHygieneMonthly">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, computed, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, putObj, delObjs } from "/@/api/stuwork/dormreform";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
@@ -209,21 +200,150 @@ import { getDormRoomDataByBuildingNo } from "/@/api/stuwork/dormroom";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDicts } from "/@/api/admin/dict";
import { downBlobFile, adaptationUrl } from "/@/utils/other";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, OfficeBuilding, Grid, House, Calendar, Document, CircleCheck, EditPen, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, Grid, House, Calendar, Document, CircleCheck, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent as defineStatusTag } from 'vue'
const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const searchFormRef = ref()
const showSearch = ref(true)
const buildingList = ref<any[]>([])
const roomList = ref<any[]>([])
const reformStatusDict = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院' },
{ prop: 'classNos', label: '班级' },
{ prop: 'roomNo', label: '房间号' },
{ prop: 'reformDate', label: '整改时间' },
{ prop: 'reformContent', label: '整改内容' },
{ prop: 'reformStatus', label: '整改结果' },
{ prop: 'remarks', label: '关联扣分明细' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
classNos: { icon: Grid },
roomNo: { icon: House },
reformDate: { icon: Calendar },
reformContent: { icon: Document },
reformStatus: { icon: CircleCheck },
remarks: { icon: EditPen }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
buildingNo: '',
@@ -359,7 +479,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -376,7 +495,6 @@ const getRoomListData = async (buildingNo: string) => {
roomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取房间列表失败', err)
roomList.value = []
}
}
@@ -392,7 +510,6 @@ const getReformStatusDict = async () => {
})) : []
}
} catch (err) {
console.error('获取整改结果字典失败', err)
reformStatusDict.value = []
}
}
@@ -401,6 +518,11 @@ const getReformStatusDict = async () => {
onMounted(() => {
getBuildingListData()
getReformStatusDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -188,7 +188,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -201,7 +200,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -217,7 +215,6 @@ const getBedNumDict = async () => {
})) : []
}
} catch (err) {
console.error('获取几人间字典失败', err)
bedNumList.value = []
}
}

View File

@@ -95,9 +95,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -117,54 +134,33 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">楼号</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="房间号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">房间号</span>
</template>
</el-table-column>
<el-table-column prop="bedNum" label="几人间" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">几人间</span>
</template>
<template #default="scope">
<!-- 几人间列特殊模板 -->
<template v-if="col.prop === 'bedNum'" #default="scope">
<el-tag v-if="scope.row.bedNum" size="small" type="info" effect="plain">
{{ scope.row.bedNum }}人间
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="livedNum" label="已住人数" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">已住人数</span>
</template>
<template #default="scope">
<!-- 已住人数列特殊模板 -->
<template v-else-if="col.prop === 'livedNum'" #default="scope">
<el-tag v-if="scope.row.livedNum !== undefined && scope.row.livedNum !== null" size="small" type="success" effect="plain">
{{ scope.row.livedNum }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -232,7 +228,8 @@
</template>
<script setup lang="ts" name="DormRoom">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, editDept } from "/@/api/stuwork/dormroom";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -240,13 +237,140 @@ import { getBuildingList } from "/@/api/stuwork/dormbuilding";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { downBlobFile, adaptationUrl } from "/@/utils/other";
import { getDicts } from "/@/api/admin/dict";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, OfficeBuilding, House, UserFilled, EditPen, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, House, UserFilled, EditPen, Setting, Menu, Calendar } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院' },
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'roomNo', label: '房间号' },
{ prop: 'bedNum', label: '几人间' },
{ prop: 'livedNum', label: '已住人数' },
{ prop: 'remarks', label: '备注' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
buildingNo: { icon: OfficeBuilding },
roomNo: { icon: House },
bedNum: { icon: UserFilled },
livedNum: { icon: UserFilled },
remarks: { icon: EditPen }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
@@ -387,7 +511,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -400,7 +523,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -416,7 +538,6 @@ const getBedNumDict = async () => {
})) : []
}
} catch (err) {
console.error('获取几人间字典失败', err)
bedNumList.value = []
}
}
@@ -426,5 +547,10 @@ onMounted(() => {
getDeptListData()
getBuildingListData()
getBedNumDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -81,7 +81,6 @@ const {
// 查看详情
const handleView = (row: any) => {
// TODO: 实现查看详情功能
console.log('查看详情', row)
}
// 初始化

View File

@@ -192,7 +192,6 @@ const handleClassChange = async (classCode: string) => {
form.stuNo = ''
form.realName = ''
} catch (err) {
console.error('获取学生列表失败', err)
studentList.value = []
form.stuNo = ''
form.realName = ''
@@ -237,7 +236,6 @@ const handleRoomChange = async (roomNo: string) => {
}
form.bedNo = ''
} catch (err) {
console.error('获取床位号列表失败', err)
bedNoList.value = []
form.bedNo = ''
}
@@ -295,7 +293,6 @@ const getClassListData = async () => {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -308,7 +305,6 @@ const getRoomListData = async () => {
roomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取房间号列表失败', err)
roomList.value = []
}
}

View File

@@ -144,9 +144,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -164,66 +181,28 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || OfficeBuilding" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="房间号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">房间号</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="teacherPhone" label="班主任电话" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">班主任电话</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">学号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center">
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="bedNo" label="床位号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">床位号</span>
</template>
<template #default="scope">
<!-- 床位号列特殊模板 -->
<template v-if="col.prop === 'bedNo'" #default="scope">
<el-tag v-if="scope.row.bedNo" size="small" type="info" effect="plain">
{{ scope.row.bedNo }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="isLeader" label="是否舍长" show-overflow-tooltip align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">是否舍长</span>
</template>
<template #default="scope">
<!-- 是否舍长列特殊模板 -->
<template v-else-if="col.prop === 'isLeader'" #default="scope">
<StatusTag
:value="scope.row.isLeader"
:options="[{ label: '是', value: '1' }, { label: '否', value: '0' }]"
@@ -231,18 +210,7 @@
/>
</template>
</el-table-column>
<el-table-column prop="phone" label="学生电话" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">学生电话</span>
</template>
</el-table-column>
<el-table-column prop="tel" label="家长电话" show-overflow-tooltip align="center">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">家长电话</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -283,22 +251,26 @@
</template>
<script setup lang="ts" name="DormRoomStudent">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/stuwork/dormroomstudent";
import { getDeptList } from "/@/api/basic/basicclass";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
import { fetchDormRoomTreeList } from "/@/api/stuwork/dormroom";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue';
import TransferDialog from './transfer.vue';
import TreeSelect from '/@/components/TreeSelect/index.vue';
import { List, OfficeBuilding, House, Grid, UserFilled, Phone, CreditCard, Avatar, User, Setting } from '@element-plus/icons-vue'
import { List, OfficeBuilding, House, Grid, UserFilled, Phone, CreditCard, Avatar, User, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent } from 'vue'
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const transferDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
@@ -306,6 +278,140 @@ const deptList = ref<any[]>([])
const buildingList = ref<any[]>([])
const dormRoomTreeList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院' },
{ prop: 'roomNo', label: '房间号' },
{ prop: 'classNo', label: '班号' },
{ prop: 'teacherRealName', label: '班主任' },
{ prop: 'teacherPhone', label: '班主任电话' },
{ prop: 'stuNo', label: '学号' },
{ prop: 'realName', label: '姓名' },
{ prop: 'bedNo', label: '床位号' },
{ prop: 'isLeader', label: '是否舍长' },
{ prop: 'phone', label: '学生电话' },
{ prop: 'tel', label: '家长电话' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
roomNo: { icon: House },
classNo: { icon: Grid },
teacherRealName: { icon: UserFilled },
teacherPhone: { icon: Phone },
stuNo: { icon: CreditCard },
realName: { icon: Avatar },
bedNo: { icon: House },
isLeader: { icon: User },
phone: { icon: Phone },
tel: { icon: Phone }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 树形选择器配置
const treeProps = {
value: 'id', // 值字段
@@ -432,7 +538,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -445,7 +550,6 @@ const getBuildingListData = async () => {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
@@ -470,7 +574,6 @@ const getDormRoomTreeListData = async (dormdataType?: string) => {
}
}
} catch (err) {
console.error('获取宿舍树状列表失败', err)
dormRoomTreeList.value = []
}
}
@@ -480,6 +583,11 @@ onMounted(() => {
getDeptListData()
getBuildingListData()
getDormRoomTreeListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -147,7 +147,6 @@ const handleRoomChange = async (roomNo: string) => {
}
form.bedNo = ''
} catch (err) {
console.error('获取床位号列表失败', err)
bedNoList.value = []
form.bedNo = ''
}
@@ -209,7 +208,6 @@ const getRoomListData = async () => {
roomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取房间号列表失败', err)
roomList.value = []
}
}

View File

@@ -52,9 +52,26 @@
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -72,86 +89,43 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="createTime" label="变动时间" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">变动时间</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
<template #default="scope">
<!-- 变动时间列特殊模板 -->
<template v-if="col.prop === 'createTime'" #default="scope">
<span>{{ scope.row.createTime ? scope.row.createTime.split(' ')[0] + ' ' + scope.row.createTime.split(' ')[1] : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班号</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">学号</span>
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center">
<template #header>
<el-icon><Avatar /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="changeType" label="异动类型" show-overflow-tooltip align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">异动类型</span>
</template>
<template #default="scope">
<!-- 异动类型列特殊模板 -->
<template v-else-if="col.prop === 'changeType'" #default="scope">
<el-tag size="small" type="warning" effect="plain">
{{ formatChangeType(scope.row.changeType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="原房间号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">原房间号</span>
</template>
</el-table-column>
<el-table-column prop="bedNo" label="原床位号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">原床位号</span>
</template>
<template #default="scope">
<!-- 原床位号列特殊模板 -->
<template v-else-if="col.prop === 'bedNo'" #default="scope">
<el-tag v-if="scope.row.bedNo" size="small" type="info" effect="plain">
{{ scope.row.bedNo }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="newRoomNo" label="新房间号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">新房间号</span>
</template>
</el-table-column>
<el-table-column prop="newBedNo" label="新床位号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">新床位号</span>
</template>
<template #default="scope">
<!-- 新床位号列特殊模板 -->
<template v-else-if="col.prop === 'newBedNo'" #default="scope">
<el-tag v-if="scope.row.newBedNo" size="small" type="success" effect="plain">
{{ scope.row.newBedNo }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -179,20 +153,156 @@
</template>
<script setup lang="ts" name="DormRoomStudentChange">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/dormroomstudentchange";
import { getDeptList } from "/@/api/basic/basicclass";
import { useMessage } from "/@/hooks/message";
import { getDicts } from "/@/api/admin/dict";
import { List, Calendar, OfficeBuilding, Grid, CreditCard, Avatar, Collection, House, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, Calendar, OfficeBuilding, Grid, CreditCard, Avatar, Collection, House, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const changeTypeList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'createTime', label: '变动时间' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'stuNo', label: '学号' },
{ prop: 'realName', label: '姓名' },
{ prop: 'changeType', label: '异动类型' },
{ prop: 'roomNo', label: '原房间号' },
{ prop: 'bedNo', label: '原床位号' },
{ prop: 'newRoomNo', label: '新房间号' },
{ prop: 'newBedNo', label: '新床位号' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
createTime: { icon: Calendar },
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
stuNo: { icon: CreditCard },
realName: { icon: Avatar },
changeType: { icon: Collection },
roomNo: { icon: House },
bedNo: { icon: House },
newRoomNo: { icon: House },
newBedNo: { icon: House }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -237,7 +347,6 @@ const handleReset = () => {
// 查看详情
const handleView = (row: any) => {
// TODO: 实现查看详情功能
console.log('查看详情', row)
}
// 格式化异动类型
@@ -257,7 +366,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -273,7 +381,6 @@ const getChangeTypeDict = async () => {
})) : []
}
} catch (err) {
console.error('获取异动类型字典失败', err)
changeTypeList.value = []
}
}
@@ -282,6 +389,11 @@ const getChangeTypeDict = async () => {
onMounted(() => {
getDeptListData()
getChangeTypeDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -13,9 +13,26 @@
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -33,24 +50,18 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="ruleName" label="规则名称" show-overflow-tooltip>
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">规则名称</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Document" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNames" label="绑定班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">绑定班级</span>
</template>
</el-table-column>
<el-table-column prop="isHoliday" label="假期模式" show-overflow-tooltip>
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">假期模式</span>
</template>
<template #default="scope">
<!-- 假期模式列特殊模板 -->
<template v-if="col.prop === 'isHoliday'" #default="scope">
<StatusTag
:value="scope.row.isHoliday"
:options="[{ label: '开启', value: '1' }, { label: '关闭', value: '0' }]"
@@ -58,6 +69,7 @@
/>
</template>
</el-table-column>
</template>
<el-table-column label="操作" align="center" fixed="right" width="280">
<template #header>
<el-icon><Setting /></el-icon>
@@ -133,24 +145,146 @@
</template>
<script setup lang="ts" name="EntranceRule">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
import { ref, reactive, defineAsyncComponent, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, getObj, openHoliday } from "/@/api/stuwork/entrancerule";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
import { List, Document, Grid, CircleCheck, Setting } from '@element-plus/icons-vue'
import { List, Document, Grid, CircleCheck, Setting, Menu } from '@element-plus/icons-vue'
import { defineAsyncComponent as defineStatusTag } from 'vue'
const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue'))
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
// 搜索变量
const showSearch = ref(false) // 没有搜索条件,隐藏搜索区域
const detailDialogVisible = ref(false)
const detailData = ref<any>(null)
// 表格列配置
const tableColumns = [
{ prop: 'ruleName', label: '规则名称' },
{ prop: 'classNames', label: '绑定班级' },
{ prop: 'isHoliday', label: '假期模式' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
ruleName: { icon: Document },
classNames: { icon: Grid },
isHoliday: { icon: CircleCheck }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
@@ -224,5 +358,10 @@ const handleViewDetail = async (row: any) => {
// 初始化
onMounted(() => {
getDataList()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -47,9 +47,26 @@
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
class="ml10"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -66,52 +83,34 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center"
:min-width="col.minWidth"
:width="col.width">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="pendingWork" label="待办事项" show-overflow-tooltip align="center">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">待办事项</span>
</template>
</el-table-column>
<el-table-column prop="nums" label="数量" show-overflow-tooltip align="center">
<template #header>
<el-icon><DataAnalysis /></el-icon>
<span style="margin-left: 4px">数量</span>
</template>
<template #default="scope">
<!-- 数量列特殊模板 -->
<template v-if="col.prop === 'nums'" #default="scope">
<el-tag type="warning" v-if="scope.row.nums > 0">{{ scope.row.nums }}</el-tag>
<span v-else>{{ scope.row.nums || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
<template #default="scope">
<!-- 创建时间列特殊模板 -->
<template v-else-if="col.prop === 'createTime'" #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">更新时间</span>
</template>
<template #default="scope">
<!-- 更新时间列特殊模板 -->
<template v-else-if="col.prop === 'updateTime'" #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -139,21 +138,149 @@
</template>
<script setup lang="ts" name="PendingWork">
import { reactive, ref, onMounted, computed } from 'vue'
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/pendingwork";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { List, OfficeBuilding, Grid, Document, DataAnalysis, Calendar, Clock, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, Grid, Document, DataAnalysis, Calendar, Clock, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班级' },
{ prop: 'pendingWork', label: '待办事项' },
{ prop: 'nums', label: '数量' },
{ prop: 'createTime', label: '创建时间', width: 180 },
{ prop: 'updateTime', label: '更新时间', width: 180 }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
pendingWork: { icon: Document },
nums: { icon: DataAnalysis },
createTime: { icon: Calendar },
updateTime: { icon: Clock }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 搜索表单
const searchForm = reactive({
deptCode: '',
@@ -200,7 +327,6 @@ const handleDeptChange = () => {
// 查看详情
const handleView = (row: any) => {
useMessage().warning('查看详情功能待实现')
console.log('查看详情', row)
}
// 获取学院列表
@@ -211,7 +337,6 @@ const getDeptListData = async () => {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -226,7 +351,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
@@ -243,6 +367,11 @@ const filteredClassList = computed(() => {
onMounted(() => {
getDeptListData()
getClassListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>

View File

@@ -129,7 +129,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -189,7 +188,6 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}

View File

@@ -92,6 +92,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -108,59 +125,31 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center">
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="classMasterName" label="班主任" show-overflow-tooltip align="center">
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="ruleName" label="奖项名称" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">奖项名称</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'ruleName'">
<el-tag v-if="scope.row.ruleName" size="small" type="warning" effect="light">
{{ scope.row.ruleName }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -238,7 +227,8 @@
</template>
<script setup lang="ts" name="RewardClass">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/rewardclass";
import { getDeptList } from "/@/api/basic/basicclass";
@@ -246,13 +236,16 @@ import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { UploadFilled, List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Trophy, EditPen, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Trophy, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -262,6 +255,111 @@ const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'classMasterName', label: '班主任', icon: UserFilled },
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 150 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -344,7 +442,6 @@ const handleDownloadTemplate = async () => {
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
@@ -406,7 +503,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -424,7 +520,6 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
@@ -439,7 +534,6 @@ const getDeptListData = async () => {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
@@ -454,12 +548,12 @@ const getClassListData = async () => {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -128,7 +128,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -183,7 +182,6 @@ const getRoomListData = async () => {
roomList.value = []
}
} catch (err) {
console.error('获取宿舍列表失败', err)
roomList.value = []
}
}

View File

@@ -75,6 +75,23 @@
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</el-row>
@@ -91,47 +108,31 @@
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center">
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">学年</span>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">学期</span>
</template>
<template #default="scope">
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center">
<template #header>
<el-icon><House /></el-icon>
<span style="margin-left: 4px">宿舍号</span>
</template>
</el-table-column>
<el-table-column prop="ruleName" label="奖项名称" show-overflow-tooltip align="center" min-width="150">
<template #header>
<el-icon><Trophy /></el-icon>
<span style="margin-left: 4px">奖项名称</span>
</template>
<template #default="scope">
<template #default="scope" v-else-if="col.prop === 'ruleName'">
<el-tag v-if="scope.row.ruleName" size="small" type="warning" effect="light">
{{ scope.row.ruleName }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200">
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
@@ -209,19 +210,23 @@
</template>
<script setup lang="ts" name="RewardDorm">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/rewarddorm";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { UploadFilled, List, Calendar, Clock, House, Trophy, EditPen, Setting } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, Calendar, Clock, House, Trophy, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
@@ -229,6 +234,109 @@ const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'roomNo', label: '宿舍号', icon: House },
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 150 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
@@ -306,7 +414,6 @@ const handleDownloadTemplate = async () => {
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
@@ -368,7 +475,6 @@ const getSchoolYearList = async () => {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
@@ -386,12 +492,12 @@ const getSchoolTermDict = async () => {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()

View File

@@ -123,7 +123,6 @@ const openDialog = async (type: string = 'add', row?: any) => {
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
@@ -180,7 +179,6 @@ const getRuleTypeDict = async () => {
ruleTypeList.value = []
}
} catch (err) {
console.error('获取奖项类型字典失败', err)
ruleTypeList.value = []
}
}

Some files were not shown because too many files have changed in this diff Show More