解决API文档和路由配置合并冲突
This commit is contained in:
commit
79a96a7dfd
662 files changed
+127608
-205195
No files matched your search
@@ -0,0 +1,748 @@
|
||||
<template>
|
||||
<div class="performance-indicators">
|
||||
<el-alert
|
||||
title="绩效指标设置"
|
||||
type="info"
|
||||
show-icon
|
||||
description="请按需要填写各类绩效指标,未使用的指标可以不填写。分值占比总和必须等于100%。"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- 产出指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
<span>产出指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 数量指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">数量指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.quantity.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.quantity.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.quantity.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.quantity.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.quantity.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 质量指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">质量指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.quality.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.quality.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.quality.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.quality.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.quality.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 时效指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">时效指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.timeliness.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.timeliness.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 成本指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">成本指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.cost.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.cost.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.cost.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.cost.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.cost.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 效益指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>效益指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 经济效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">经济效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.economic.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.economic.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.economic.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.economic.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.economic.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 社会效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">社会效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.social.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.social.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.social.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.social.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.social.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 生态效益指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">生态效益指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.ecological.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.ecological.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.ecological.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.ecological.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.ecological.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 满意度指标 -->
|
||||
<el-card class="indicator-category" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
<el-icon><Star /></el-icon>
|
||||
<span>满意度指标</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 服务对象满意度指标 -->
|
||||
<div class="indicator-item">
|
||||
<h4 class="indicator-title">服务对象满意度指标</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="绩效目标名称">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.goalName"
|
||||
placeholder="请输入绩效目标名称"
|
||||
maxlength="200"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="目标值">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.targetValue"
|
||||
placeholder="目标值"
|
||||
maxlength="100"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="单位">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.unit"
|
||||
placeholder="单位"
|
||||
maxlength="20"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-form-item label="分值占比(%)">
|
||||
<el-input-number
|
||||
v-model="indicators.service_satisfaction.weightPercentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="1"
|
||||
placeholder="分值占比"
|
||||
style="width: 100%"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="详细说明">
|
||||
<el-input
|
||||
v-model="indicators.service_satisfaction.description"
|
||||
placeholder="详细说明"
|
||||
maxlength="500"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 分值占比统计 -->
|
||||
<el-alert
|
||||
:title="`当前分值占比总和:${totalWeight}%`"
|
||||
:type="totalWeight === 100 ? 'success' : totalWeight === 0 ? 'info' : 'warning'"
|
||||
show-icon
|
||||
:description="totalWeight === 100 ? '分值占比设置正确' : totalWeight === 0 ? '请填写绩效指标的分值占比' : '分值占比总和必须等于100%'"
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from 'vue'
|
||||
import { TrendCharts, Money, Star } from '@element-plus/icons-vue'
|
||||
|
||||
interface PerformanceIndicator {
|
||||
indicatorType: string
|
||||
indicatorTypeName: string
|
||||
goalName: string
|
||||
targetValue: string
|
||||
unit: string
|
||||
weightPercentage: number
|
||||
description: string
|
||||
measurementMethod: string
|
||||
dataSource: string
|
||||
isQuantitative: boolean
|
||||
sortOrder: number
|
||||
status: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PerformanceIndicators',
|
||||
components: {
|
||||
TrendCharts,
|
||||
Money,
|
||||
Star
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
// 创建默认指标对象
|
||||
const createIndicator = (type: string, typeName: string): PerformanceIndicator => ({
|
||||
indicatorType: type,
|
||||
indicatorTypeName: typeName,
|
||||
goalName: '',
|
||||
targetValue: '',
|
||||
unit: '',
|
||||
weightPercentage: 0,
|
||||
description: '',
|
||||
measurementMethod: '',
|
||||
dataSource: '',
|
||||
isQuantitative: true,
|
||||
sortOrder: 0,
|
||||
status: '1'
|
||||
})
|
||||
|
||||
// 8种固定的指标类型
|
||||
const indicators = ref({
|
||||
quantity: createIndicator('quantity', '数量指标'),
|
||||
quality: createIndicator('quality', '质量指标'),
|
||||
timeliness: createIndicator('timeliness', '时效指标'),
|
||||
cost: createIndicator('cost', '成本指标'),
|
||||
economic: createIndicator('economic', '经济效益指标'),
|
||||
social: createIndicator('social', '社会效益指标'),
|
||||
ecological: createIndicator('ecological', '生态效益指标'),
|
||||
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
|
||||
})
|
||||
|
||||
// 计算总分值占比
|
||||
const totalWeight = computed(() => {
|
||||
let total = 0
|
||||
Object.values(indicators.value).forEach(indicator => {
|
||||
total += indicator.weightPercentage || 0
|
||||
})
|
||||
return Math.round(total * 10) / 10
|
||||
})
|
||||
|
||||
// 收集有效的指标数据
|
||||
const collectValidIndicators = () => {
|
||||
const validIndicators: PerformanceIndicator[] = []
|
||||
let sortOrder = 0
|
||||
|
||||
Object.values(indicators.value).forEach(indicator => {
|
||||
// 只有填写了目标名称的指标才被认为是有效的
|
||||
if (indicator.goalName?.trim()) {
|
||||
validIndicators.push({
|
||||
...indicator,
|
||||
sortOrder: sortOrder++
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return validIndicators
|
||||
}
|
||||
|
||||
// 发出变化事件
|
||||
const emitChange = () => {
|
||||
const validIndicators = collectValidIndicators()
|
||||
emit('update:modelValue', validIndicators)
|
||||
emit('change', validIndicators)
|
||||
}
|
||||
|
||||
// 从传入的数据初始化指标
|
||||
const initializeFromData = (data: any[]) => {
|
||||
if (!data || data.length === 0) return
|
||||
|
||||
// 重置所有指标
|
||||
indicators.value = {
|
||||
quantity: createIndicator('quantity', '数量指标'),
|
||||
quality: createIndicator('quality', '质量指标'),
|
||||
timeliness: createIndicator('timeliness', '时效指标'),
|
||||
cost: createIndicator('cost', '成本指标'),
|
||||
economic: createIndicator('economic', '经济效益指标'),
|
||||
social: createIndicator('social', '社会效益指标'),
|
||||
ecological: createIndicator('ecological', '生态效益指标'),
|
||||
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
|
||||
}
|
||||
|
||||
// 将数据填入对应的指标类型
|
||||
data.forEach(item => {
|
||||
if (indicators.value[item.indicatorType]) {
|
||||
indicators.value[item.indicatorType] = { ...item }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听外部数据变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
initializeFromData(newValue)
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
// 校验方法
|
||||
const validate = () => {
|
||||
const validIndicators = collectValidIndicators()
|
||||
const errors = []
|
||||
|
||||
if (validIndicators.length === 0) {
|
||||
errors.push('请至少填写一个绩效指标')
|
||||
}
|
||||
|
||||
validIndicators.forEach((indicator, index) => {
|
||||
if (!indicator.goalName?.trim()) {
|
||||
errors.push(`第${index + 1}个指标未填写绩效目标名称`)
|
||||
}
|
||||
if (!indicator.targetValue?.trim()) {
|
||||
errors.push(`第${index + 1}个指标未填写目标值`)
|
||||
}
|
||||
if (!indicator.weightPercentage || indicator.weightPercentage <= 0) {
|
||||
errors.push(`第${index + 1}个指标未设置分值占比`)
|
||||
}
|
||||
})
|
||||
|
||||
if (Math.abs(totalWeight.value - 100) > 0.1) {
|
||||
errors.push(`分值占比总和必须等于100%,当前为${totalWeight.value}%`)
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return { valid: false, message: errors.join(';') }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// 获取所有有效指标数据
|
||||
const getAllIndicators = () => {
|
||||
return collectValidIndicators()
|
||||
}
|
||||
|
||||
return {
|
||||
indicators,
|
||||
totalWeight,
|
||||
emitChange,
|
||||
validate,
|
||||
getAllIndicators
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.performance-indicators {
|
||||
.category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #409eff;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-title {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目预算申报详情</span>
|
||||
<el-tag :type="getStatusType(detail.approvalStatus)" class="ml10">
|
||||
{{ getStatusText(detail.approvalStatus) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 申报摘要 -->
|
||||
<application-summary :application="detail" />
|
||||
|
||||
<!-- 审批流程 -->
|
||||
<el-divider content-position="left">审批流程</el-divider>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in approvalTimeline"
|
||||
:key="index"
|
||||
:type="item.type"
|
||||
:timestamp="item.timestamp"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<h4>{{ item.title }}</h4>
|
||||
<p>{{ item.description }}</p>
|
||||
<span class="timeline-user" v-if="item.user">{{ item.user }}</span>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 操作日志 -->
|
||||
<el-divider content-position="left">操作日志</el-divider>
|
||||
<el-table :data="operationLogs" style="width: 100%">
|
||||
<el-table-column prop="operation" label="操作" width="120"/>
|
||||
<el-table-column prop="operator" label="操作人" width="100"/>
|
||||
<el-table-column prop="operateTime" label="操作时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.operateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" show-overflow-tooltip/>
|
||||
</el-table>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="operation-buttons">
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'draft'"
|
||||
type="primary"
|
||||
@click="handleEdit"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'draft'"
|
||||
type="success"
|
||||
@click="handleSubmit"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
>提交</el-button>
|
||||
<el-button
|
||||
v-if="detail.approvalStatus === 'pending'"
|
||||
type="warning"
|
||||
@click="handleWithdraw"
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
>撤回</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CwApplicationDetail">
|
||||
import { ref, getCurrentInstance, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getProjectApplication, submitApplication, withdrawApplication } from "@/api/cw/projectApplication";
|
||||
import ApplicationSummary from "./summary.vue";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
|
||||
console.log('=== detail.vue 组件开始加载 ===');
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const detail = ref({});
|
||||
const approvalTimeline = ref([]);
|
||||
const operationLogs = ref([]);
|
||||
|
||||
console.log('=== detail.vue 组件初始化完成 ===');
|
||||
console.log('route对象:', route);
|
||||
console.log('初始查询参数:', route.query);
|
||||
|
||||
/** 获取状态类型 */
|
||||
function getStatusType(status) {
|
||||
const statusMap = {
|
||||
'draft': 'info',
|
||||
'pending': 'warning',
|
||||
'approving': 'primary',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'withdrawn': 'info',
|
||||
'cancelled': 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
}
|
||||
|
||||
/** 获取状态文本 */
|
||||
function getStatusText(status) {
|
||||
const statusMap = {
|
||||
'draft': '草稿',
|
||||
'pending': '待审批',
|
||||
'approving': '审批中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝',
|
||||
'withdrawn': '已撤回',
|
||||
'cancelled': '已取消'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
}
|
||||
|
||||
/** 编辑按钮操作 */
|
||||
function handleEdit() {
|
||||
router.push("/cw/application/form?id=" + detail.value.id);
|
||||
}
|
||||
|
||||
/** 提交按钮操作 */
|
||||
function handleSubmit() {
|
||||
proxy.$modal.confirm('是否确认提交申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
|
||||
return submitApplication(detail.value.id);
|
||||
}).then(() => {
|
||||
getDetail();
|
||||
proxy.$modal.msgSuccess("提交成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 撤回按钮操作 */
|
||||
function handleWithdraw() {
|
||||
proxy.$modal.confirm('是否确认撤回申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
|
||||
return withdrawApplication(detail.value.id);
|
||||
}).then(() => {
|
||||
getDetail();
|
||||
proxy.$modal.msgSuccess("撤回成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 返回按钮操作 */
|
||||
function goBack() {
|
||||
router.push("/cw/application");
|
||||
}
|
||||
|
||||
/** 获取详情数据 */
|
||||
function getDetail() {
|
||||
const id = route.query.id;
|
||||
console.log('=== 开始获取详情数据 ===');
|
||||
console.log('URL参数中的ID:', id);
|
||||
console.log('完整query参数:', route.query);
|
||||
|
||||
if (id) {
|
||||
console.log('开始调用API:getProjectApplication, ID:', id);
|
||||
getProjectApplication(id).then(response => {
|
||||
console.log('=== API调用成功 ===');
|
||||
console.log('API响应:', response);
|
||||
console.log('响应代码:', response.code);
|
||||
console.log('响应数据:', response.data);
|
||||
|
||||
detail.value = response.data;
|
||||
|
||||
// 调试日志:查看返回的数据
|
||||
console.log('=== 项目申报详情数据 ===');
|
||||
console.log('完整数据:', response.data);
|
||||
console.log('绩效指标数据:', response.data.performanceIndicators);
|
||||
console.log('绩效指标数量:', response.data.performanceIndicators ? response.data.performanceIndicators.length : 0);
|
||||
|
||||
// 模拟审批流程数据
|
||||
approvalTimeline.value = [
|
||||
{
|
||||
type: 'success',
|
||||
timestamp: detail.value.createTime,
|
||||
title: '申报提交',
|
||||
description: '申报人提交预算申报',
|
||||
user: detail.value.applicantName
|
||||
}
|
||||
];
|
||||
|
||||
if (detail.value.submitTime) {
|
||||
approvalTimeline.value.push({
|
||||
type: 'primary',
|
||||
timestamp: detail.value.submitTime,
|
||||
title: '提交审批',
|
||||
description: '申报已提交,等待部门审批',
|
||||
user: detail.value.applicantName
|
||||
});
|
||||
}
|
||||
|
||||
if (detail.value.approveTime && detail.value.approvalStatus === 'approved') {
|
||||
approvalTimeline.value.push({
|
||||
type: 'success',
|
||||
timestamp: detail.value.approveTime,
|
||||
title: '审批通过',
|
||||
description: '所有审批节点已通过',
|
||||
user: '系统管理员'
|
||||
});
|
||||
}
|
||||
|
||||
// 模拟操作日志数据
|
||||
operationLogs.value = [
|
||||
{
|
||||
operation: '创建申报',
|
||||
operator: detail.value.applicantName,
|
||||
operateTime: detail.value.createTime,
|
||||
remark: '创建项目预算申报'
|
||||
}
|
||||
];
|
||||
|
||||
if (detail.value.submitTime) {
|
||||
operationLogs.value.push({
|
||||
operation: '提交审批',
|
||||
operator: detail.value.applicantName,
|
||||
operateTime: detail.value.submitTime,
|
||||
remark: '提交预算申报进入审批流程'
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('=== API调用失败 ===');
|
||||
console.error('错误信息:', error);
|
||||
console.error('错误响应:', error.response);
|
||||
proxy.$modal.msgError('获取申报详情失败:' + (error.message || '未知错误'));
|
||||
});
|
||||
} else {
|
||||
console.error('=== URL参数错误 ===');
|
||||
console.error('缺少ID参数,无法获取详情数据');
|
||||
proxy.$modal.msgError('缺少申报ID参数');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('=== detail.vue 页面已挂载 ===');
|
||||
console.log('当前路由:', route);
|
||||
console.log('当前路由路径:', route.path);
|
||||
console.log('当前路由查询参数:', route.query);
|
||||
getDetail();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.timeline-content h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.timeline-content p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.timeline-user {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.operation-buttons .el-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,784 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 统计卡片区域 -->
|
||||
<el-row :gutter="20" class="stats-row">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-primary">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ totalCount }}</div>
|
||||
<div class="stat-label">总申报数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-success">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ approvedCount }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-warning">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ pendingCount }}</div>
|
||||
<div class="stat-label">审批中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-card-info">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ draftCount }}</div>
|
||||
<div class="stat-label">草稿</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="button-card" v-show="showSearch">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">项目申报查询</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="queryParams" ref="queryRef" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input
|
||||
v-model="queryParams.applicationNo"
|
||||
placeholder="请输入申报编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="queryParams.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目负责人" prop="principal">
|
||||
<el-input
|
||||
v-model="queryParams.principal"
|
||||
placeholder="请输入项目负责人"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option label="草稿" value="draft"/>
|
||||
<el-option label="已提交" value="submitted"/>
|
||||
<el-option label="审批中" value="approving"/>
|
||||
<el-option label="已通过" value="approved"/>
|
||||
<el-option label="已拒绝" value="rejected"/>
|
||||
<el-option label="已退回" value="returned"/>
|
||||
<el-option label="已取消" value="cancelled"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预算年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="queryParams.year"
|
||||
type="year"
|
||||
placeholder="选择年份"
|
||||
value-format="YYYY"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="button-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">项目申报管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['cw:application:add']"
|
||||
>新增申报</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['cw:application:remove']"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
icon="Upload"
|
||||
@click="handleSubmit"
|
||||
:disabled="multiple"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
>提交审批</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['cw:application:export']"
|
||||
>导出</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格区域 -->
|
||||
<el-card class="table-card">
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="applicationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
class="scroll-table"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" min-width="140" />
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="项目负责人" align="center" prop="principal" min-width="100" />
|
||||
<el-table-column label="所属部门" align="center" prop="departmentName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="预算年度" align="center" prop="year" width="100" />
|
||||
<el-table-column label="项目总投资" align="center" prop="totalInvestment" min-width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getStatusTagType(scope.row.approvalStatus)"
|
||||
effect="light"
|
||||
>
|
||||
{{ getStatusText(scope.row.approvalStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="View"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['cw:application:query']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="提交审批" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Upload"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleSubmit(scope.row)"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="撤回" placement="top" v-if="scope.row.approvalStatus === 'submitted'">
|
||||
<el-button
|
||||
type="info"
|
||||
icon="RefreshLeft"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleWithdraw(scope.row)"
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['cw:application:remove']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改项目申报对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="1200px" append-to-body>
|
||||
<ApplicationForm ref="applicationFormRef" :form-data="form" @submit="submitForm" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详情对话框 -->
|
||||
<el-dialog title="项目申报详情" v-model="viewOpen" width="1200px" append-to-body>
|
||||
<ApplicationSummary :application-id="currentId" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewOpen = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProjectApplication, getProjectApplication, delProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, getApplicationStatistics } from "@/api/cw/projectApplication"
|
||||
import ApplicationForm from "./form.vue"
|
||||
import ApplicationSummary from "./summary.vue"
|
||||
|
||||
export default {
|
||||
name: "ProjectApplication",
|
||||
components: {
|
||||
ApplicationForm,
|
||||
ApplicationSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 项目申报表格数据
|
||||
applicationList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否显示查看对话框
|
||||
viewOpen: false,
|
||||
// 当前查看的ID
|
||||
currentId: null,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: null,
|
||||
projectName: null,
|
||||
principal: null,
|
||||
approvalStatus: null,
|
||||
year: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {},
|
||||
// 统计数据
|
||||
totalCount: 0,
|
||||
approvedCount: 0,
|
||||
pendingCount: 0,
|
||||
draftCount: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
/** 查询项目申报列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listProjectApplication(this.queryParams).then(response => {
|
||||
this.applicationList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 获取统计数据 */
|
||||
getStatistics() {
|
||||
// 调用专门的统计API接口
|
||||
const year = this.queryParams.year ? parseInt(this.queryParams.year) : null
|
||||
getApplicationStatistics(year).then(response => {
|
||||
const data = response.data
|
||||
this.totalCount = data.totalCount || 0
|
||||
this.approvedCount = data.approvedCount || 0
|
||||
this.pendingCount = data.approvingCount || 0 // 注意:后端返回的是approvingCount
|
||||
this.draftCount = data.draftCount || 0
|
||||
}).catch(error => {
|
||||
console.error('获取统计数据失败:', error)
|
||||
// 如果API失败,使用本地计算作为后备方案
|
||||
this.totalCount = this.applicationList.length
|
||||
this.approvedCount = this.applicationList.filter(item => item.approvalStatus === 'approved').length
|
||||
this.pendingCount = this.applicationList.filter(item => item.approvalStatus === 'approving').length
|
||||
this.draftCount = this.applicationList.filter(item => item.approvalStatus === 'draft').length
|
||||
})
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
id: null,
|
||||
applicationNo: null,
|
||||
projectName: null,
|
||||
principal: null,
|
||||
year: new Date().getFullYear(),
|
||||
departmentId: null,
|
||||
departmentName: null,
|
||||
projectCategory: null,
|
||||
implementationPeriod: null,
|
||||
projectBackground: null,
|
||||
projectObjective: null,
|
||||
projectScope: null,
|
||||
projectContent: null,
|
||||
implementationPlan: null,
|
||||
expectedResults: null,
|
||||
riskAssessment: null,
|
||||
totalPerformanceGoal: null,
|
||||
currentYearGoal: null,
|
||||
totalInvestment: 0,
|
||||
totalMunicipalFiscal: 0,
|
||||
totalSelfFunding: 0,
|
||||
totalOtherFunding: 0,
|
||||
currentYearTotal: 0,
|
||||
currentYearMunicipal: 0,
|
||||
currentYearSelf: 0,
|
||||
currentYearOther: 0,
|
||||
nextYearTotal: 0,
|
||||
nextYearMunicipal: 0,
|
||||
nextYearSelf: 0,
|
||||
nextYearOther: 0,
|
||||
futureYearTotal: 0,
|
||||
futureYearMunicipal: 0,
|
||||
futureYearSelf: 0,
|
||||
futureYearOther: 0,
|
||||
performanceIndicators: [],
|
||||
approvalStatus: 'draft'
|
||||
}
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
this.getStatistics() // 搜索时也更新统计数据
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryRef")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加项目申报"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const id = row.id || this.ids
|
||||
getProjectApplication(id).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改项目申报"
|
||||
})
|
||||
},
|
||||
/** 查看详情 */
|
||||
handleView(row) {
|
||||
this.currentId = row.id
|
||||
this.viewOpen = true
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["applicationFormRef"].validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
updateProjectApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
this.getStatistics() // 修改后更新统计
|
||||
})
|
||||
} else {
|
||||
addProjectApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
this.getStatistics() // 新增后更新统计
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.id || this.ids
|
||||
this.$modal.confirm('是否确认删除项目申报编号为"' + ids + '"的数据项?').then(function() {
|
||||
return delProjectApplication(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.getStatistics() // 删除后更新统计
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 提交审批 */
|
||||
handleSubmit(row) {
|
||||
const applications = row?.applicationNo ? [row.applicationNo] : this.applicationList.filter(item => this.ids.includes(item.id)).map(item => item.applicationNo)
|
||||
|
||||
this.$modal.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?').then(() => {
|
||||
return submitApplication(row?.id || this.ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.getStatistics() // 提交后更新统计
|
||||
this.$modal.msgSuccess("提交成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 撤回申请 */
|
||||
handleWithdraw(row) {
|
||||
this.$modal.confirm('是否确认撤回该申报?').then(() => {
|
||||
// 调用撤回接口
|
||||
this.$modal.msgSuccess("撤回成功")
|
||||
this.getList()
|
||||
this.getStatistics() // 撤回后更新统计
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/application/export', {
|
||||
...this.queryParams
|
||||
}, `application_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00'
|
||||
return parseFloat(amount).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
},
|
||||
/** 获取状态标签类型 */
|
||||
getStatusTagType(status) {
|
||||
const statusMap = {
|
||||
draft: 'info',
|
||||
submitted: 'warning',
|
||||
approving: 'warning',
|
||||
approved: 'success',
|
||||
rejected: 'danger',
|
||||
returned: 'warning',
|
||||
cancelled: 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
},
|
||||
/** 获取状态文本 */
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
draft: '草稿',
|
||||
submitted: '已提交',
|
||||
approving: '审批中',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
returned: '已退回',
|
||||
cancelled: '已取消'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 继承后勤管理模块的样式风格 */
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stats-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
height: 90px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 16px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.stat-icon {
|
||||
font-size: 28px;
|
||||
margin-right: 15px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-card-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-warning {
|
||||
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-info {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.button-card, .table-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
:deep(.el-card__header) {
|
||||
border-bottom: 1px solid rgba(93, 135, 255, 0.1);
|
||||
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, rgb(93, 135, 255), rgb(73, 190, 255));
|
||||
border-radius: 2px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.el-button {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 金额文本样式 */
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.page-container {
|
||||
padding: 10px;
|
||||
|
||||
.stats-row {
|
||||
.stat-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="application-summary" v-loading="loading">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="申报编号">
|
||||
{{ currentApplication.applicationNo || '系统自动生成' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">
|
||||
{{ currentApplication.projectName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目负责人">
|
||||
{{ currentApplication.principal }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
{{ currentApplication.contactPhone }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预算年度">
|
||||
{{ currentApplication.year }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目周期">
|
||||
{{ formatDateRange(currentApplication.startDate, currentApplication.endDate) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目特征 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目特征</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.targetSme ? 'success' : 'info'">
|
||||
小微企业发展: {{ currentApplication.targetSme ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.targetMicroSme ? 'success' : 'info'">
|
||||
微小企业成长: {{ currentApplication.targetMicroSme ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tag :type="currentApplication.importEquipment ? 'success' : 'info'">
|
||||
进口设备: {{ currentApplication.importEquipment ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目内容 -->
|
||||
<el-card class="summary-card" v-if="hasProjectContent">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>项目内容</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="申报理由" v-if="currentApplication.applicationReason">
|
||||
<div class="content-text">{{ currentApplication.applicationReason }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="建设内容" v-if="currentApplication.constructionContent">
|
||||
<div class="content-text">{{ currentApplication.constructionContent }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总体绩效目标" v-if="currentApplication.totalPerformanceGoal">
|
||||
<div class="content-text">{{ currentApplication.totalPerformanceGoal }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当年完成目标" v-if="currentApplication.currentYearGoal">
|
||||
<div class="content-text">{{ currentApplication.currentYearGoal }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 资金预算汇总 -->
|
||||
<el-card class="summary-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>资金预算汇总</span>
|
||||
<el-tag type="warning" size="small">单位:万元</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 总体预算 -->
|
||||
<div class="budget-section">
|
||||
<h4>总体预算</h4>
|
||||
<el-row :gutter="20" class="budget-row">
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">总投资</div>
|
||||
<div class="budget-value highlight">{{ formatAmount(currentApplication.totalInvestment) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">市财政资金</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalMunicipalFiscal) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">单位自筹</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalSelfFunding) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="budget-item">
|
||||
<div class="budget-label">其他资金</div>
|
||||
<div class="budget-value">{{ formatAmount(currentApplication.totalOtherFunding) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 分年度预算 -->
|
||||
<div class="budget-section">
|
||||
<h4>分年度预算明细</h4>
|
||||
<el-table :data="budgetBreakdown" border>
|
||||
<el-table-column prop="year" label="年度" width="120" align="center"/>
|
||||
<el-table-column prop="total" label="总投资" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.total) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="municipal" label="市财政资金" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.municipal) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="self" label="单位自筹" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.self) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="other" label="其他资金" align="right">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.other) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 绩效指标设置 -->
|
||||
<el-card class="summary-card" v-if="hasPerformanceIndicators">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>绩效指标设置</span>
|
||||
<el-tag type="warning" size="small">分值占比总和:{{ performanceIndicatorTotal }}%</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="validPerformanceIndicators" border style="width: 100%">
|
||||
<el-table-column label="指标类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ scope.row.indicatorTypeName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绩效目标名称" min-width="200">
|
||||
<template #default="scope">
|
||||
<div class="content-text">{{ scope.row.goalName }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标值" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div class="content-text">{{ scope.row.targetValue }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值占比" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag type="primary">{{ parseFloat(scope.row.weightPercentage) || 0 }}%</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="详细说明" min-width="150">
|
||||
<template #default="scope">
|
||||
<div class="content-text" v-if="scope.row.description">{{ scope.row.description }}</div>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<el-card class="summary-card" v-if="currentApplication.remark">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>备注信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content-text">{{ currentApplication.remark }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { getProjectApplication } from '@/api/cw/projectApplication';
|
||||
|
||||
defineOptions({
|
||||
name: 'ApplicationSummary'
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
application: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
applicationId: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
// 内部数据状态
|
||||
const internalApplication = ref({});
|
||||
const loading = ref(false);
|
||||
|
||||
// 计算属性:优先使用传入的application,如果没有则使用内部获取的数据
|
||||
const currentApplication = computed(() => {
|
||||
// 如果直接传入了application对象且不为空,则使用它
|
||||
if (props.application && Object.keys(props.application).length > 0) {
|
||||
console.log('=== 使用传入的application对象 ===');
|
||||
console.log('application数据:', props.application);
|
||||
return props.application;
|
||||
}
|
||||
|
||||
// 否则使用内部获取的数据
|
||||
console.log('=== 使用内部获取的application数据 ===');
|
||||
console.log('internalApplication数据:', internalApplication.value);
|
||||
return internalApplication.value;
|
||||
});
|
||||
|
||||
// 获取申报详情数据
|
||||
const fetchApplicationData = async (id) => {
|
||||
if (!id) return;
|
||||
|
||||
console.log('=== ApplicationSummary 开始获取数据 ===');
|
||||
console.log('applicationId:', id);
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getProjectApplication(id);
|
||||
console.log('=== ApplicationSummary API调用成功 ===');
|
||||
console.log('API响应:', response);
|
||||
console.log('响应数据:', response.data);
|
||||
console.log('响应数据类型:', typeof response.data);
|
||||
|
||||
// 特别检查绩效指标字段
|
||||
if (response.data) {
|
||||
console.log('=== API响应中的绩效指标检查 ===');
|
||||
console.log('response.data.performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('performanceIndicators类型:', typeof response.data.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(response.data.performanceIndicators));
|
||||
|
||||
// 检查所有可能的绩效指标字段名
|
||||
console.log('可能的字段名检查:');
|
||||
console.log('- performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('- performance_indicators:', response.data.performance_indicators);
|
||||
console.log('- indicators:', response.data.indicators);
|
||||
console.log('- performanceIndicatorList:', response.data.performanceIndicatorList);
|
||||
|
||||
// 列出所有字段
|
||||
console.log('响应数据的所有字段:', Object.keys(response.data));
|
||||
}
|
||||
|
||||
internalApplication.value = response.data || {};
|
||||
} catch (error) {
|
||||
console.error('=== ApplicationSummary API调用失败 ===');
|
||||
console.error('错误信息:', error);
|
||||
internalApplication.value = {};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听applicationId变化
|
||||
watch(() => props.applicationId, (newId) => {
|
||||
if (newId && (!props.application || Object.keys(props.application).length === 0)) {
|
||||
fetchApplicationData(newId);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 组件挂载时检查是否需要获取数据
|
||||
onMounted(() => {
|
||||
console.log('=== ApplicationSummary 组件挂载 ===');
|
||||
console.log('props.application:', props.application);
|
||||
console.log('props.applicationId:', props.applicationId);
|
||||
|
||||
// 如果没有传入application但有applicationId,则获取数据
|
||||
if (props.applicationId && (!props.application || Object.keys(props.application).length === 0)) {
|
||||
fetchApplicationData(props.applicationId);
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否有项目内容
|
||||
const hasProjectContent = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
return app.applicationReason ||
|
||||
app.constructionContent ||
|
||||
app.totalPerformanceGoal ||
|
||||
app.currentYearGoal;
|
||||
});
|
||||
|
||||
// 判断是否有绩效指标
|
||||
const hasPerformanceIndicators = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
console.log('=== 绩效指标详细检查 ===');
|
||||
console.log('完整app对象:', app);
|
||||
console.log('app.performanceIndicators类型:', typeof app.performanceIndicators);
|
||||
console.log('app.performanceIndicators值:', app.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(app.performanceIndicators));
|
||||
|
||||
if (app.performanceIndicators) {
|
||||
console.log('绩效指标数组长度:', app.performanceIndicators.length);
|
||||
console.log('绩效指标数组内容:', JSON.stringify(app.performanceIndicators, null, 2));
|
||||
} else {
|
||||
console.log('绩效指标数据为空或未定义');
|
||||
}
|
||||
|
||||
const hasIndicators = app.performanceIndicators && app.performanceIndicators.length > 0;
|
||||
console.log('最终判断 - 是否有绩效指标:', hasIndicators);
|
||||
return hasIndicators;
|
||||
});
|
||||
|
||||
// 计算绩效指标总分值占比
|
||||
const performanceIndicatorTotal = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
if (!app.performanceIndicators || app.performanceIndicators.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const total = app.performanceIndicators.reduce((sum, indicator) => {
|
||||
const weight = parseFloat(indicator.weightPercentage) || 0;
|
||||
return sum + weight;
|
||||
}, 0);
|
||||
return Math.round(total * 100) / 100; // 保留两位小数
|
||||
});
|
||||
|
||||
// 过滤掉无效的绩效指标(分值占比为0的)
|
||||
const validPerformanceIndicators = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
console.log('=== validPerformanceIndicators 计算 ===');
|
||||
console.log('app.performanceIndicators:', app.performanceIndicators);
|
||||
|
||||
if (!app.performanceIndicators) {
|
||||
console.log('没有绩效指标数据,返回空数组');
|
||||
return [];
|
||||
}
|
||||
|
||||
const validIndicators = app.performanceIndicators.filter(indicator => {
|
||||
const weight = parseFloat(indicator.weightPercentage) || 0;
|
||||
console.log(`指标 ${indicator.goalName}: 权重=${indicator.weightPercentage}, 解析后权重=${weight}, 是否有效=${weight > 0}`);
|
||||
return weight > 0;
|
||||
});
|
||||
|
||||
console.log('有效绩效指标数量:', validIndicators.length);
|
||||
console.log('有效绩效指标详情:', validIndicators);
|
||||
return validIndicators;
|
||||
});
|
||||
|
||||
// 分年度预算明细
|
||||
const budgetBreakdown = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
const currentYear = app.year || new Date().getFullYear();
|
||||
console.log('=== 预算数据检查 ===');
|
||||
console.log('application数据:', app);
|
||||
console.log('当年预算字段:', {
|
||||
currentYearTotal: app.currentYearTotal,
|
||||
currentYearMunicipal: app.currentYearMunicipal,
|
||||
currentYearSelf: app.currentYearSelf,
|
||||
currentYearOther: app.currentYearOther
|
||||
});
|
||||
return [
|
||||
{
|
||||
year: `${currentYear}年`,
|
||||
total: app.currentYearTotal || 0,
|
||||
municipal: app.currentYearMunicipal || 0,
|
||||
self: app.currentYearSelf || 0,
|
||||
other: app.currentYearOther || 0
|
||||
},
|
||||
{
|
||||
year: `${parseInt(currentYear) + 1}年`,
|
||||
total: app.nextYearTotal || 0,
|
||||
municipal: app.nextYearMunicipal || 0,
|
||||
self: app.nextYearSelf || 0,
|
||||
other: app.nextYearOther || 0
|
||||
},
|
||||
{
|
||||
year: '以后年度',
|
||||
total: app.futureYearTotal || 0,
|
||||
municipal: app.futureYearMunicipal || 0,
|
||||
self: app.futureYearSelf || 0,
|
||||
other: app.futureYearOther || 0
|
||||
}
|
||||
].filter(item => item.total > 0); // 只显示有预算的年度
|
||||
});
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount) => {
|
||||
if (!amount || amount === 0) return '0.00';
|
||||
return Number(amount).toFixed(2);
|
||||
};
|
||||
|
||||
// 格式化日期范围
|
||||
const formatDateRange = (startDate, endDate) => {
|
||||
if (!startDate && !endDate) return '未设置';
|
||||
if (!startDate) return `截止 ${endDate}`;
|
||||
if (!endDate) return `${startDate} 开始`;
|
||||
return `${startDate} 至 ${endDate}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.application-summary {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.budget-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.budget-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.budget-section h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
border-left: 4px solid #409eff;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.budget-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.budget-item {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.budget-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.budget-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.budget-value.highlight {
|
||||
color: #409eff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-table td, .el-table th) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,869 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报人" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申报人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<template v-if="cw_approval_status && cw_approval_status.length > 0">
|
||||
<el-option v-for="dict in cw_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批人" prop="approverName">
|
||||
<el-input v-model="queryParams.approverName" placeholder="请输入审批人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="提交时间">
|
||||
<el-date-picker
|
||||
v-model="daterangeSubmitTime"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleQuery"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingApprovals">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待我审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyApprovals">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我已审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showDepartmentApprovals">
|
||||
<el-icon class="quick-icon"><OfficeBuilding /></el-icon>
|
||||
<span>部门审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showApprovalStatistics">
|
||||
<el-icon class="quick-icon"><DataAnalysis /></el-icon>
|
||||
<span>审批统计</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchApprove"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
>批量通过</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Close"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchReject"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
>批量拒绝</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:approvalRecord:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="approvalList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="申报人" align="center" prop="applicantName" width="100" />
|
||||
<el-table-column label="申报部门" align="center" prop="deptName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="总投资(万元)" align="center" prop="totalInvestment" width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批人" align="center" prop="currentApproverName" width="120" />
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="cw_approval_status" :value="scope.row.approvalStatus" v-if="cw_approval_status && cw_approval_status.length > 0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="submitTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.approvalTime ? parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="handleView(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待审批状态可以进行审批操作 -->
|
||||
<template v-if="scope.row.approvalStatus === 'submitted' || scope.row.approvalStatus === 'approving'">
|
||||
<el-tooltip content="通过" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="拒绝" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="退回" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="RefreshLeft"
|
||||
@click="handleReturn(scope.row)"
|
||||
v-hasPermi="['cw:application:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 所有状态都可以查看审批记录 -->
|
||||
<el-tooltip content="审批记录" placement="top">
|
||||
<el-button link type="primary" icon="Document" @click="handleApprovalHistory(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="queryParams.pageNum = $event"
|
||||
@update:limit="queryParams.pageSize = $event"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog
|
||||
v-model="approvalDialog.visible"
|
||||
:title="approvalDialog.title"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="closeApprovalDialog"
|
||||
>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
|
||||
<!-- 申报信息展示 -->
|
||||
<el-form-item label="申报信息">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="申报编号">{{ currentApplication.applicationNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ currentApplication.projectName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申报人">{{ currentApplication.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申报部门">{{ currentApplication.deptName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总投资">{{ formatAmount(currentApplication.totalInvestment) }} 万元</el-descriptions-item>
|
||||
<el-descriptions-item label="预算年度">{{ currentApplication.year }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 审批决定 -->
|
||||
<el-form-item label="审批决定" prop="approvalResult">
|
||||
<el-radio-group v-model="approvalForm.approvalResult">
|
||||
<el-radio value="approved">
|
||||
<el-icon class="approve-icon"><Check /></el-icon>
|
||||
通过
|
||||
</el-radio>
|
||||
<el-radio value="rejected">
|
||||
<el-icon class="reject-icon"><Close /></el-icon>
|
||||
拒绝
|
||||
</el-radio>
|
||||
<el-radio value="returned">
|
||||
<el-icon class="return-icon"><RefreshLeft /></el-icon>
|
||||
退回
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 审批意见 -->
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalComment"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见(必填)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 下一审批人(如果需要) -->
|
||||
<el-form-item label="下一审批人" prop="nextApprover" v-if="showNextApprover">
|
||||
<el-select v-model="approvalForm.nextApprover" placeholder="请选择下一审批人" style="width: 100%">
|
||||
<el-option
|
||||
v-for="approver in approverList"
|
||||
:key="approver.userId"
|
||||
:label="approver.userName"
|
||||
:value="approver.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeApprovalDialog">取消</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalSubmitting">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailDialog.visible"
|
||||
:title="detailDialog.title"
|
||||
width="90%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<application-summary :application="currentApplication" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
v-if="canApprove(currentApplication)"
|
||||
type="primary"
|
||||
@click="approveFromDetail"
|
||||
>审批</el-button>
|
||||
<el-button @click="detailDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批记录对话框 -->
|
||||
<el-dialog
|
||||
v-model="historyDialog.visible"
|
||||
title="审批记录"
|
||||
width="900px"
|
||||
append-to-body
|
||||
>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(record, index) in approvalHistory"
|
||||
:key="index"
|
||||
:icon="getHistoryIcon(record.approvalResult)"
|
||||
:type="getHistoryType(record.approvalResult)"
|
||||
:timestamp="parseTime(record.approvalTime, '{y}-{m}-{d} {h}:{i}')"
|
||||
placement="top"
|
||||
>
|
||||
<el-card shadow="hover">
|
||||
<div class="history-header">
|
||||
<span class="approver-name">{{ record.approverName }}</span>
|
||||
<el-tag :type="getStatusTagType(record.approvalResult)">
|
||||
{{ getApprovalResultText(record.approvalResult) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="history-content" v-if="record.approvalComment">
|
||||
<p>{{ record.approvalComment }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="historyDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listApprovalRecord,
|
||||
getApprovalRecord,
|
||||
approveApplication,
|
||||
batchApproveApplication,
|
||||
getApprovalHistory,
|
||||
getPendingApprovals,
|
||||
getMyApprovals,
|
||||
getDepartmentApprovals,
|
||||
getAvailableApprovers,
|
||||
exportApprovalRecord
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
|
||||
export default {
|
||||
name: 'CwApprovalRecord',
|
||||
components: {
|
||||
ApplicationSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 审批列表
|
||||
approvalList: [],
|
||||
// 时间范围
|
||||
daterangeSubmitTime: [],
|
||||
// 动画配置
|
||||
animate: {
|
||||
searchAnimate: {
|
||||
enter: 'animate__animated animate__fadeIn',
|
||||
leave: 'animate__animated animate__fadeOut'
|
||||
}
|
||||
},
|
||||
// 字典数据
|
||||
cw_approval_status: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: '',
|
||||
projectName: '',
|
||||
applicantName: '',
|
||||
approvalStatus: '',
|
||||
approverName: '',
|
||||
submitTimeBegin: '',
|
||||
submitTimeEnd: ''
|
||||
},
|
||||
// 审批对话框
|
||||
approvalDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 详情对话框
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 历史记录对话框
|
||||
historyDialog: {
|
||||
visible: false
|
||||
},
|
||||
// 当前申报信息
|
||||
currentApplication: {},
|
||||
// 审批表单
|
||||
approvalForm: {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: '请选择审批结果', trigger: 'change' }
|
||||
],
|
||||
approvalComment: [
|
||||
{ required: true, message: '请输入审批意见', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '审批意见长度在 5 到 500 个字符', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 审批提交状态
|
||||
approvalSubmitting: false,
|
||||
// 审批人列表
|
||||
approverList: [],
|
||||
// 审批历史
|
||||
approvalHistory: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 是否显示下一审批人选择
|
||||
showNextApprover() {
|
||||
return this.approvalForm.approvalResult === 'approved';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDictData();
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 加载字典数据 */
|
||||
async loadDictData() {
|
||||
try {
|
||||
const dictData = useDict('cw_approval_status');
|
||||
// 监听字典数据变化
|
||||
this.$watch(() => dictData.cw_approval_status, (newVal) => {
|
||||
this.cw_approval_status = newVal || [];
|
||||
}, { immediate: true });
|
||||
} catch (error) {
|
||||
console.error('加载审批状态字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/** 查询审批列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 处理时间范围
|
||||
this.queryParams.submitTimeBegin = this.daterangeSubmitTime[0] || '';
|
||||
this.queryParams.submitTimeEnd = this.daterangeSubmitTime[1] || '';
|
||||
|
||||
listApprovalRecord(this.queryParams).then(response => {
|
||||
this.approvalList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取审批列表失败:', error);
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.$refs.queryFormRef.resetFields();
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
|
||||
/** 快捷操作 */
|
||||
showPendingApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
approvalStatus: 'submitted',
|
||||
currentApprover: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
approverName: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showDepartmentApprovals() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
deptId: 'current'
|
||||
};
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
showApprovalStatistics() {
|
||||
this.$router.push('/cw/approvalRecord/statistics');
|
||||
},
|
||||
|
||||
/** 查看详情 */
|
||||
handleView(row) {
|
||||
const id = row.id || row.applicationId;
|
||||
getApprovalRecord(id).then(response => {
|
||||
this.currentApplication = response.data;
|
||||
this.detailDialog.visible = true;
|
||||
this.detailDialog.title = "申报详情";
|
||||
}).catch(error => {
|
||||
console.error('获取详情失败:', error);
|
||||
this.$modal.msgError('获取详情失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 从详情页审批 */
|
||||
approveFromDetail() {
|
||||
this.detailDialog.visible = false;
|
||||
this.handleApprove(this.currentApplication);
|
||||
},
|
||||
|
||||
/** 单个审批通过 */
|
||||
handleApprove(row) {
|
||||
this.openApprovalDialog('approved', row, '审批通过');
|
||||
},
|
||||
|
||||
/** 单个审批拒绝 */
|
||||
handleReject(row) {
|
||||
this.openApprovalDialog('rejected', row, '审批拒绝');
|
||||
},
|
||||
|
||||
/** 单个审批退回 */
|
||||
handleReturn(row) {
|
||||
this.openApprovalDialog('returned', row, '审批退回');
|
||||
},
|
||||
|
||||
/** 批量审批通过 */
|
||||
handleBatchApprove() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgWarning('请选择要审批的记录');
|
||||
return;
|
||||
}
|
||||
this.openBatchApprovalDialog('approved', '批量审批通过');
|
||||
},
|
||||
|
||||
/** 批量审批拒绝 */
|
||||
handleBatchReject() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgWarning('请选择要审批的记录');
|
||||
return;
|
||||
}
|
||||
this.openBatchApprovalDialog('rejected', '批量审批拒绝');
|
||||
},
|
||||
|
||||
/** 打开审批对话框 */
|
||||
openApprovalDialog(result, row, title) {
|
||||
this.currentApplication = row;
|
||||
this.approvalForm = {
|
||||
applicationId: row.id,
|
||||
approvalResult: result,
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
|
||||
// 如果是通过状态,加载可选审批人
|
||||
if (result === 'approved') {
|
||||
this.loadAvailableApprovers(row.id);
|
||||
}
|
||||
|
||||
this.approvalDialog.visible = true;
|
||||
this.approvalDialog.title = title;
|
||||
},
|
||||
|
||||
/** 打开批量审批对话框 */
|
||||
openBatchApprovalDialog(result, title) {
|
||||
this.approvalForm = {
|
||||
applicationIds: this.ids,
|
||||
approvalResult: result,
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
this.approvalDialog.visible = true;
|
||||
this.approvalDialog.title = title;
|
||||
},
|
||||
|
||||
/** 加载可选审批人 */
|
||||
loadAvailableApprovers(applicationId) {
|
||||
getAvailableApprovers(applicationId).then(response => {
|
||||
this.approverList = response.data || [];
|
||||
}).catch(error => {
|
||||
console.error('获取审批人列表失败:', error);
|
||||
this.approverList = [];
|
||||
});
|
||||
},
|
||||
|
||||
/** 关闭审批对话框 */
|
||||
closeApprovalDialog() {
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalForm = {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
nextApprover: null
|
||||
};
|
||||
this.approverList = [];
|
||||
if (this.$refs.approvalFormRef) {
|
||||
this.$refs.approvalFormRef.resetFields();
|
||||
}
|
||||
},
|
||||
|
||||
/** 提交审批 */
|
||||
submitApproval() {
|
||||
this.$refs.approvalFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
this.approvalSubmitting = true;
|
||||
|
||||
const apiCall = this.approvalForm.applicationIds
|
||||
? batchApproveApplication(this.approvalForm)
|
||||
: approveApplication(this.approvalForm);
|
||||
|
||||
apiCall.then(response => {
|
||||
this.$modal.msgSuccess("审批成功");
|
||||
this.closeApprovalDialog();
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('审批失败:', error);
|
||||
this.$modal.msgError('审批失败');
|
||||
}).finally(() => {
|
||||
this.approvalSubmitting = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** 查看审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.id || row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
// 显示模拟数据
|
||||
this.approvalHistory = [
|
||||
{
|
||||
approverName: '系统',
|
||||
approvalResult: 'submitted',
|
||||
approvalComment: '申报材料已提交,等待审批',
|
||||
approvalTime: new Date().toISOString(),
|
||||
stepName: '申报提交',
|
||||
stepOrder: 1
|
||||
}
|
||||
];
|
||||
this.historyDialog.visible = true;
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出 */
|
||||
handleExport() {
|
||||
exportApprovalRecord(this.queryParams).then(() => {
|
||||
this.$modal.msgSuccess('导出成功');
|
||||
}).catch(error => {
|
||||
console.error('导出失败:', error);
|
||||
this.$modal.msgError('导出失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 判断是否可以审批 */
|
||||
canApprove(row) {
|
||||
return row.approvalStatus === 'submitted' || row.approvalStatus === 'approving';
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
return amount ? amount.toFixed(2) : '0.00';
|
||||
},
|
||||
|
||||
/** 获取历史记录图标 */
|
||||
getHistoryIcon(result) {
|
||||
const iconMap = {
|
||||
'submitted': 'Upload',
|
||||
'approved': 'Check',
|
||||
'rejected': 'Close',
|
||||
'returned': 'RefreshLeft'
|
||||
};
|
||||
return iconMap[result] || 'Document';
|
||||
},
|
||||
|
||||
/** 获取历史记录类型 */
|
||||
getHistoryType(result) {
|
||||
const typeMap = {
|
||||
'submitted': 'primary',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'returned': 'warning'
|
||||
};
|
||||
return typeMap[result] || 'info';
|
||||
},
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
getStatusTagType(status) {
|
||||
const statusMap = {
|
||||
'submitted': 'warning',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'returned': 'warning'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(result) {
|
||||
const textMap = {
|
||||
'submitted': '已提交',
|
||||
'approved': '通过',
|
||||
'rejected': '拒绝',
|
||||
'returned': '退回'
|
||||
};
|
||||
return textMap[result] || result;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
// 快捷操作卡片
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409EFF;
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 28px;
|
||||
color: #409EFF;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表格卡片
|
||||
.button-card.table-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
// 金额文本
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
// 审批对话框样式
|
||||
.approve-icon {
|
||||
color: #67C23A;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.reject-icon {
|
||||
color: #F56C6C;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.return-icon {
|
||||
color: #E6A23C;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
// 审批历史样式
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.approver-name {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
.history-content {
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.quick-action {
|
||||
padding: 15px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="config-detail">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="detail-card mb-4">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="配置名称">
|
||||
{{ config.configName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批类型">
|
||||
<el-tag :type="getApprovalTypeTag(config.approvalType)">
|
||||
{{ getApprovalTypeText(config.approvalType) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="适用范围">
|
||||
{{ getScopeText(config.applicableScope) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="config.status === '1' ? 'success' : 'danger'">
|
||||
{{ config.status === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序">
|
||||
{{ config.orderNum }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ parseTime(config.createTime, '{y}-{m}-{d} {h}:{i}') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div v-if="config.description" class="mt-4">
|
||||
<h4 class="section-title">配置描述</h4>
|
||||
<p class="description-text">{{ config.description }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批流程 -->
|
||||
<el-card class="detail-card" v-if="config.steps && config.steps.length > 0">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><Share /></el-icon>
|
||||
<span>审批流程</span>
|
||||
<el-tag type="info" size="small">共 {{ config.steps.length }} 个步骤</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-steps :active="config.steps.length" finish-status="success" align-center>
|
||||
<el-step
|
||||
v-for="(step, index) in config.steps"
|
||||
:key="index"
|
||||
:title="step.stepName"
|
||||
:description="`${getApprovalModeText(step.approvalMode)} | ${step.timeoutHours}小时`"
|
||||
/>
|
||||
</el-steps>
|
||||
|
||||
<div class="steps-detail mt-6">
|
||||
<div
|
||||
v-for="(step, index) in config.steps"
|
||||
:key="index"
|
||||
class="step-detail-item"
|
||||
>
|
||||
<el-card shadow="hover" class="step-detail-card">
|
||||
<template #header>
|
||||
<div class="step-header">
|
||||
<span class="step-number">步骤 {{ index + 1 }}</span>
|
||||
<span class="step-name">{{ step.stepName }}</span>
|
||||
<el-tag :type="getModeTagType(step.approvalMode)" size="small">
|
||||
{{ getApprovalModeText(step.approvalMode) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">审批方式:</label>
|
||||
<span>{{ getApprovalModeText(step.approvalMode) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">超时时间:</label>
|
||||
<span>{{ step.timeoutHours }} 小时</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="step-info">
|
||||
<label class="info-label">审批人数:</label>
|
||||
<span>{{ step.approvers ? step.approvers.length : 0 }} 人</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="approvers-section mt-4" v-if="step.approvers && step.approvers.length > 0">
|
||||
<label class="info-label">审批人员:</label>
|
||||
<div class="approvers-list">
|
||||
<el-tag
|
||||
v-for="(approver, approverIndex) in step.approverNames"
|
||||
:key="approverIndex"
|
||||
class="approver-tag"
|
||||
>
|
||||
{{ approver }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="config.approvalType === 'custom'" class="custom-approver-notice">
|
||||
<el-alert
|
||||
title="自选审批人"
|
||||
description="申报人在提交时自行选择审批人"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 无流程步骤时的提示 -->
|
||||
<el-card class="detail-card" v-else>
|
||||
<el-empty description="暂无配置的审批流程步骤" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ApproverConfigDetail',
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取审批类型标签 */
|
||||
getApprovalTypeTag(type) {
|
||||
const typeMap = {
|
||||
'fixed': 'success',
|
||||
'role': 'warning',
|
||||
'dept': 'info',
|
||||
'custom': 'primary'
|
||||
};
|
||||
return typeMap[type] || 'info';
|
||||
},
|
||||
|
||||
/** 获取审批类型文本 */
|
||||
getApprovalTypeText(type) {
|
||||
const textMap = {
|
||||
'fixed': '固定审批人',
|
||||
'role': '角色审批',
|
||||
'dept': '部门审批',
|
||||
'custom': '自选审批人'
|
||||
};
|
||||
return textMap[type] || type;
|
||||
},
|
||||
|
||||
/** 获取适用范围文本 */
|
||||
getScopeText(scope) {
|
||||
const scopeMap = {
|
||||
'all': '全部',
|
||||
'dept': '部门',
|
||||
'project_type': '项目类型',
|
||||
'amount_range': '金额范围'
|
||||
};
|
||||
return scopeMap[scope] || scope;
|
||||
},
|
||||
|
||||
/** 获取审批方式文本 */
|
||||
getApprovalModeText(mode) {
|
||||
const modeMap = {
|
||||
'single': '单人审批',
|
||||
'multi_sign': '多人会签',
|
||||
'multi_or': '多人或签'
|
||||
};
|
||||
return modeMap[mode] || mode;
|
||||
},
|
||||
|
||||
/** 获取审批方式标签类型 */
|
||||
getModeTagType(mode) {
|
||||
const typeMap = {
|
||||
'single': 'primary',
|
||||
'multi_sign': 'success',
|
||||
'multi_or': 'warning'
|
||||
};
|
||||
return typeMap[mode] || 'info';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.config-detail {
|
||||
.detail-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.steps-detail {
|
||||
.step-detail-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.step-detail-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.approvers-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.approvers-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.approver-tag {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-approver-notice {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mt-6 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,890 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批类型" prop="approvalType">
|
||||
<el-select v-model="queryParams.approvalType" placeholder="请选择审批类型" clearable style="width: 100%">
|
||||
<el-option label="固定审批人" value="fixed" />
|
||||
<el-option label="角色审批" value="role" />
|
||||
<el-option label="部门审批" value="dept" />
|
||||
<el-option label="自选审批人" value="custom" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="禁用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="handleAdd">
|
||||
<el-icon class="quick-icon"><Plus /></el-icon>
|
||||
<span>新增配置</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showFlowDesigner">
|
||||
<el-icon class="quick-icon"><SetUp /></el-icon>
|
||||
<span>流程设计</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showTemplates">
|
||||
<el-icon class="quick-icon"><Document /></el-icon>
|
||||
<span>配置模板</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showTestFlow">
|
||||
<el-icon class="quick-icon"><Promotion /></el-icon>
|
||||
<span>流程测试</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['cw:approverConfig:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:approverConfig:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:approverConfig:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:approverConfig:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="configList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="配置名称" align="left" prop="configName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="审批类型" align="center" prop="approvalType" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalTypeTag(scope.row.approvalType)">
|
||||
{{ getApprovalTypeText(scope.row.approvalType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="步骤数量" align="center" prop="stepCount" width="100">
|
||||
<template #default="scope">
|
||||
<el-badge :value="scope.row.stepCount" class="step-badge" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批人" align="center" prop="approverNames" min-width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
v-for="(name, index) in scope.row.approverNames"
|
||||
:key="index"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>
|
||||
{{ name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用范围" align="center" prop="applicableScope" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag type="info" size="small">{{ scope.row.applicableScope }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="orderNum" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
v-hasPermi="['cw:approverConfig:edit']"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="240" class-name="small-padding fixed-width" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="handleDetail(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑配置" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['cw:approverConfig:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="复制配置" placement="top">
|
||||
<el-button link type="success" icon="CopyDocument" @click="handleCopy(scope.row)" v-hasPermi="['cw:approverConfig:add']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="流程图" placement="top">
|
||||
<el-button link type="warning" icon="Share" @click="handleViewFlow(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:approverConfig:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改审批人配置对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="900px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="form.configName" placeholder="请输入配置名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审批类型" prop="approvalType">
|
||||
<el-select v-model="form.approvalType" placeholder="请选择审批类型" style="width: 100%" @change="handleTypeChange">
|
||||
<el-option label="固定审批人" value="fixed" />
|
||||
<el-option label="角色审批" value="role" />
|
||||
<el-option label="部门审批" value="dept" />
|
||||
<el-option label="自选审批人" value="custom" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="适用范围" prop="applicableScope">
|
||||
<el-select v-model="form.applicableScope" placeholder="请选择适用范围" style="width: 100%">
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="部门" value="dept" />
|
||||
<el-option label="项目类型" value="project_type" />
|
||||
<el-option label="金额范围" value="amount_range" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="orderNum">
|
||||
<el-input-number v-model="form.orderNum" :min="0" :max="999" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="配置描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入配置描述" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 审批步骤配置 -->
|
||||
<el-form-item label="审批步骤">
|
||||
<el-card shadow="never" class="steps-card">
|
||||
<template #header>
|
||||
<div class="steps-header">
|
||||
<span>审批流程步骤</span>
|
||||
<el-button type="primary" size="small" icon="Plus" @click="addStep">添加步骤</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="form.steps && form.steps.length > 0" class="steps-container">
|
||||
<div
|
||||
v-for="(step, index) in form.steps"
|
||||
:key="index"
|
||||
class="step-item"
|
||||
>
|
||||
<el-card shadow="hover" class="step-card">
|
||||
<template #header>
|
||||
<div class="step-header">
|
||||
<span class="step-title">
|
||||
<el-icon><Position /></el-icon>
|
||||
步骤 {{ index + 1 }}: {{ step.stepName || '未命名步骤' }}
|
||||
</span>
|
||||
<div class="step-actions">
|
||||
<el-button type="text" size="small" @click="moveStepUp(index)" :disabled="index === 0">
|
||||
<el-icon><ArrowUp /></el-icon>
|
||||
</el-button>
|
||||
<el-button type="text" size="small" @click="moveStepDown(index)" :disabled="index === form.steps.length - 1">
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<el-button type="text" size="small" @click="removeStep(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="步骤名称" :prop="`steps.${index}.stepName`" :rules="stepRules.stepName">
|
||||
<el-input v-model="step.stepName" placeholder="请输入步骤名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="审批方式" :prop="`steps.${index}.approvalMode`" :rules="stepRules.approvalMode">
|
||||
<el-select v-model="step.approvalMode" placeholder="请选择审批方式" style="width: 100%">
|
||||
<el-option label="单人审批" value="single" />
|
||||
<el-option label="多人会签" value="multi_sign" />
|
||||
<el-option label="多人或签" value="multi_or" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="超时时间(小时)" :prop="`steps.${index}.timeoutHours`">
|
||||
<el-input-number v-model="step.timeoutHours" :min="1" :max="720" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="审批人设置" :prop="`steps.${index}.approvers`" :rules="stepRules.approvers">
|
||||
<div class="approvers-container">
|
||||
<!-- 固定审批人 -->
|
||||
<div v-if="form.approvalType === 'fixed'">
|
||||
<el-select
|
||||
v-model="step.approvers"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
style="width: 100%"
|
||||
@change="updateApproverNames"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.userId"
|
||||
:label="user.nickName"
|
||||
:value="user.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 角色审批 -->
|
||||
<div v-else-if="form.approvalType === 'role'">
|
||||
<el-select
|
||||
v-model="step.approvers"
|
||||
multiple
|
||||
placeholder="请选择审批角色"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="role in roleList"
|
||||
:key="role.roleId"
|
||||
:label="role.roleName"
|
||||
:value="role.roleId"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 部门审批 -->
|
||||
<div v-else-if="form.approvalType === 'dept'">
|
||||
<el-tree-select
|
||||
v-model="step.approvers"
|
||||
:data="deptTreeData"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||
multiple
|
||||
placeholder="请选择审批部门"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 自选审批人 -->
|
||||
<div v-else-if="form.approvalType === 'custom'">
|
||||
<el-alert
|
||||
title="自选审批人模式"
|
||||
description="申报人在提交时可以自行选择审批人,此处无需配置具体人员"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="暂无审批步骤,请添加步骤" />
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog v-model="detailDialog.visible" :title="detailDialog.title" width="900px" append-to-body>
|
||||
<approver-config-detail :config="currentConfig" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 流程图对话框 -->
|
||||
<el-dialog v-model="flowDialog.visible" title="审批流程图" width="1200px" append-to-body>
|
||||
<div ref="flowChartRef" style="height: 500px; border: 1px solid #e4e7ed;"></div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="flowDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listApproverConfig,
|
||||
getApproverConfig,
|
||||
addApproverConfig,
|
||||
updateApproverConfig,
|
||||
delApproverConfig
|
||||
} from "@/api/cw/projectApplication";
|
||||
|
||||
export default {
|
||||
name: 'CwApproverConfig',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 配置列表
|
||||
configList: [],
|
||||
// 是否展开,默认全部展开
|
||||
isExpandAll: true,
|
||||
// 弹出层标题
|
||||
dialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 详情弹窗
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 流程图弹窗
|
||||
flowDialog: {
|
||||
visible: false
|
||||
},
|
||||
// 当前配置数据
|
||||
currentConfig: {},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
configName: '',
|
||||
approvalType: '',
|
||||
status: ''
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
configName: [
|
||||
{ required: true, message: "配置名称不能为空", trigger: "blur" },
|
||||
{ min: 2, max: 50, message: "配置名称长度在 2 到 50 个字符", trigger: "blur" }
|
||||
],
|
||||
approvalType: [
|
||||
{ required: true, message: "审批类型不能为空", trigger: "change" }
|
||||
],
|
||||
applicableScope: [
|
||||
{ required: true, message: "适用范围不能为空", trigger: "change" }
|
||||
]
|
||||
},
|
||||
// 步骤表单校验
|
||||
stepRules: {
|
||||
stepName: [
|
||||
{ required: true, message: "步骤名称不能为空", trigger: "blur" }
|
||||
],
|
||||
approvalMode: [
|
||||
{ required: true, message: "审批方式不能为空", trigger: "change" }
|
||||
],
|
||||
approvers: [
|
||||
{ required: true, message: "请设置审批人", trigger: "change" }
|
||||
]
|
||||
},
|
||||
// 用户列表
|
||||
userList: [],
|
||||
// 角色列表
|
||||
roleList: [],
|
||||
// 部门树数据
|
||||
deptTreeData: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadBasicData();
|
||||
},
|
||||
methods: {
|
||||
/** 查询审批人配置列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listApproverConfig(this.queryParams).then(response => {
|
||||
this.configList = this.handleTree(response.data, "id", "parentId");
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/** 加载基础数据 */
|
||||
loadBasicData() {
|
||||
// 加载用户列表
|
||||
// listUser().then(response => {
|
||||
// this.userList = response.rows;
|
||||
// });
|
||||
|
||||
// 加载角色列表
|
||||
// listRole().then(response => {
|
||||
// this.roleList = response.rows;
|
||||
// });
|
||||
|
||||
// 加载部门树
|
||||
// listDept().then(response => {
|
||||
// this.deptTreeData = this.handleTree(response.data, "deptId");
|
||||
// });
|
||||
},
|
||||
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.dialog.visible = false;
|
||||
this.reset();
|
||||
},
|
||||
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
id: null,
|
||||
configName: null,
|
||||
approvalType: null,
|
||||
applicableScope: 'all',
|
||||
orderNum: 0,
|
||||
description: null,
|
||||
status: "1",
|
||||
steps: []
|
||||
};
|
||||
if (this.$refs.formRef) {
|
||||
this.$refs.formRef.resetFields();
|
||||
}
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.$refs.queryFormRef.resetFields();
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
toggleExpandAll() {
|
||||
this.isExpandAll = !this.isExpandAll;
|
||||
this.toggleExpandAllTree(this.configList, this.isExpandAll);
|
||||
},
|
||||
|
||||
/** 快捷操作 */
|
||||
showFlowDesigner() {
|
||||
this.$modal.msgInfo("流程设计器功能开发中...");
|
||||
},
|
||||
|
||||
showTemplates() {
|
||||
this.$modal.msgInfo("配置模板功能开发中...");
|
||||
},
|
||||
|
||||
showTestFlow() {
|
||||
this.$modal.msgInfo("流程测试功能开发中...");
|
||||
},
|
||||
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.dialog.visible = true;
|
||||
this.dialog.title = "添加审批人配置";
|
||||
},
|
||||
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
const id = row?.id || this.ids[0];
|
||||
getApproverConfig(id).then(response => {
|
||||
this.form = response.data;
|
||||
this.dialog.visible = true;
|
||||
this.dialog.title = "修改审批人配置";
|
||||
});
|
||||
},
|
||||
|
||||
/** 复制配置 */
|
||||
handleCopy(row) {
|
||||
getApproverConfig(row.id).then(response => {
|
||||
this.form = {
|
||||
...response.data,
|
||||
id: null,
|
||||
configName: response.data.configName + '_副本'
|
||||
};
|
||||
this.dialog.visible = true;
|
||||
this.dialog.title = "复制审批人配置";
|
||||
});
|
||||
},
|
||||
|
||||
/** 查看详情 */
|
||||
handleDetail(row) {
|
||||
getApproverConfig(row.id).then(response => {
|
||||
this.currentConfig = response.data;
|
||||
this.detailDialog.visible = true;
|
||||
this.detailDialog.title = "配置详情";
|
||||
});
|
||||
},
|
||||
|
||||
/** 查看流程图 */
|
||||
handleViewFlow(row) {
|
||||
this.flowDialog.visible = true;
|
||||
this.$nextTick(() => {
|
||||
this.renderFlowChart(row);
|
||||
});
|
||||
},
|
||||
|
||||
/** 状态变更 */
|
||||
handleStatusChange(row) {
|
||||
let text = row.status === "1" ? "启用" : "停用";
|
||||
this.$modal.confirm('确认要"' + text + '""' + row.configName + '"配置吗?').then(() => {
|
||||
return updateApproverConfig(row);
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess(text + "成功");
|
||||
}).catch(() => {
|
||||
row.status = row.status === "0" ? "1" : "0";
|
||||
});
|
||||
},
|
||||
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row?.id ? [row.id] : this.ids;
|
||||
const configs = row?.configName ? [row.configName] : this.configList.filter(item => this.ids.includes(item.id)).map(item => item.configName);
|
||||
|
||||
this.$modal.confirm('是否确认删除配置名称为"' + configs.join('", "') + '"的数据项?').then(() => {
|
||||
return delApproverConfig(ids);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
updateApproverConfig(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.dialog.visible = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
addApproverConfig(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.dialog.visible = false;
|
||||
this.getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** 审批类型变更 */
|
||||
handleTypeChange(value) {
|
||||
// 清空已配置的审批人
|
||||
if (this.form.steps) {
|
||||
this.form.steps.forEach(step => {
|
||||
step.approvers = [];
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** 添加步骤 */
|
||||
addStep() {
|
||||
if (!this.form.steps) {
|
||||
this.form.steps = [];
|
||||
}
|
||||
this.form.steps.push({
|
||||
stepName: `审批步骤${this.form.steps.length + 1}`,
|
||||
approvalMode: 'single',
|
||||
timeoutHours: 24,
|
||||
approvers: []
|
||||
});
|
||||
},
|
||||
|
||||
/** 删除步骤 */
|
||||
removeStep(index) {
|
||||
this.form.steps.splice(index, 1);
|
||||
},
|
||||
|
||||
/** 上移步骤 */
|
||||
moveStepUp(index) {
|
||||
if (index > 0) {
|
||||
const temp = this.form.steps[index];
|
||||
this.form.steps[index] = this.form.steps[index - 1];
|
||||
this.form.steps[index - 1] = temp;
|
||||
}
|
||||
},
|
||||
|
||||
/** 下移步骤 */
|
||||
moveStepDown(index) {
|
||||
if (index < this.form.steps.length - 1) {
|
||||
const temp = this.form.steps[index];
|
||||
this.form.steps[index] = this.form.steps[index + 1];
|
||||
this.form.steps[index + 1] = temp;
|
||||
}
|
||||
},
|
||||
|
||||
/** 更新审批人名称 */
|
||||
updateApproverNames() {
|
||||
// 更新审批人名称用于显示
|
||||
},
|
||||
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/approverConfig/export', {
|
||||
...this.queryParams
|
||||
}, `approver_config_${new Date().getTime()}.xlsx`);
|
||||
},
|
||||
|
||||
/** 渲染流程图 */
|
||||
renderFlowChart(row) {
|
||||
// 使用流程图库渲染流程图
|
||||
this.$modal.msgInfo("流程图功能开发中...");
|
||||
},
|
||||
|
||||
/** 获取审批类型标签 */
|
||||
getApprovalTypeTag(type) {
|
||||
const typeMap = {
|
||||
'fixed': 'success',
|
||||
'role': 'warning',
|
||||
'dept': 'info',
|
||||
'custom': 'primary'
|
||||
};
|
||||
return typeMap[type] || 'info';
|
||||
},
|
||||
|
||||
/** 获取审批类型文本 */
|
||||
getApprovalTypeText(type) {
|
||||
const textMap = {
|
||||
'fixed': '固定审批人',
|
||||
'role': '角色审批',
|
||||
'dept': '部门审批',
|
||||
'custom': '自选审批人'
|
||||
};
|
||||
return textMap[type] || type;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
// 快捷操作卡片
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409EFF;
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 28px;
|
||||
color: #409EFF;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表格卡片
|
||||
.button-card.table-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
// 步骤徽章
|
||||
.step-badge {
|
||||
:deep(.el-badge__content) {
|
||||
background-color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤配置卡片
|
||||
.steps-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.steps-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
.steps-container {
|
||||
.step-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.step-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.step-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
|
||||
.el-icon {
|
||||
margin-right: 8px;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.step-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 审批人容器
|
||||
.approvers-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 标签样式
|
||||
.mr-1 {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.quick-action {
|
||||
padding: 15px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,700 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 快捷功能按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="handleAdd">
|
||||
<el-icon class="quick-icon"><Plus /></el-icon>
|
||||
<span>新增申报</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyApplications">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我的申报</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingApplications">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showStatistics">
|
||||
<el-icon class="quick-icon"><DataAnalysis /></el-icon>
|
||||
<span>统计分析</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="mb-[10px]">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-primary">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Files /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.totalCount }}</div>
|
||||
<div class="stat-label">申报总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-warning">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.approvingCount }}</div>
|
||||
<div class="stat-label">审批中</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-success">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.approvedCount }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-danger">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><CircleClose /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.rejectedCount }}</div>
|
||||
<div class="stat-label">已拒绝</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-info">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Money /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ formatAmount(statistics.totalAmount) }}</div>
|
||||
<div class="stat-label">总金额(万元)</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-info">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Timer /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.avgProcessDays || 0 }}</div>
|
||||
<div class="stat-label">平均周期(天)</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-success">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.monthlyNew || 0 }}</div>
|
||||
<div class="stat-label">本月新增</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-card shadow="hover" class="stat-card stat-card-warning">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Finished /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.monthlyApproved || 0 }}</div>
|
||||
<div class="stat-label">本月通过</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="16" class="mb-[10px]">
|
||||
<!-- 月度趋势图表 -->
|
||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24">
|
||||
<el-card shadow="never" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>月度申报趋势</span>
|
||||
<el-select v-model="selectedYear" @change="handleYearChange" style="width: 120px;">
|
||||
<el-option
|
||||
v-for="year in yearOptions"
|
||||
:key="year"
|
||||
:label="year + '年'"
|
||||
:value="year"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="trendChart" style="height: 350px;"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 状态分布图表 -->
|
||||
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24">
|
||||
<el-card shadow="never" class="chart-card">
|
||||
<template #header>
|
||||
<span>状态分布</span>
|
||||
</template>
|
||||
<div ref="statusChart" style="height: 350px;"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 部门统计表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>部门统计详情</span>
|
||||
<el-button type="primary" icon="Download" @click="handleExportDeptStatistics">导出部门统计</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="statistics.deptStatistics"
|
||||
border
|
||||
style="width: 100%"
|
||||
v-loading="statisticsLoading"
|
||||
>
|
||||
<el-table-column prop="deptName" label="部门名称" align="center" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="totalCount" label="申报数量" align="center" width="100" />
|
||||
<el-table-column prop="approvingCount" label="审批中" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" size="small">{{ scope.row.approvingCount || 0 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approvedCount" label="已通过" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" size="small">{{ scope.row.approvedCount || 0 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rejectedCount" label="已拒绝" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag type="danger" size="small">{{ scope.row.rejectedCount || 0 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalAmount" label="总金额(万元)" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approvedAmount" label="已批金额(万元)" align="center" width="130">
|
||||
<template #default="scope">
|
||||
<span class="amount-text success">{{ formatAmount(scope.row.approvedAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="calculateApprovalRate(scope.row)"
|
||||
:status="getProgressStatus(scope.row)"
|
||||
:show-text="false"
|
||||
/>
|
||||
<span class="rate-text">{{ calculateApprovalRate(scope.row) }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看部门详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="viewDepartmentDetail(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="导出部门数据" placement="top">
|
||||
<el-button link type="info" icon="Download" @click="exportDepartmentData(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus, User, Clock, DataAnalysis, Files, CircleCheck, CircleClose,
|
||||
Money, Timer, TrendCharts, Finished
|
||||
} from '@element-plus/icons-vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { getApplicationStatistics, getStatisticsByDept, getStatisticsByMonth } from '@/api/cw/projectApplication'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 响应式数据
|
||||
const statistics = reactive({
|
||||
totalCount: 0,
|
||||
approvingCount: 0,
|
||||
approvedCount: 0,
|
||||
rejectedCount: 0,
|
||||
totalAmount: 0,
|
||||
avgProcessDays: 0,
|
||||
monthlyNew: 0,
|
||||
monthlyApproved: 0,
|
||||
deptStatistics: [] as any[]
|
||||
})
|
||||
|
||||
const selectedYear = ref(new Date().getFullYear())
|
||||
const yearOptions = ref<number[]>([])
|
||||
const statisticsLoading = ref(false)
|
||||
|
||||
// 图表引用
|
||||
const trendChart = ref()
|
||||
const statusChart = ref()
|
||||
let trendChartInstance: echarts.ECharts | null = null
|
||||
let statusChartInstance: echarts.ECharts | null = null
|
||||
|
||||
// 生成年份选项
|
||||
const generateYearOptions = () => {
|
||||
const currentYear = new Date().getFullYear()
|
||||
for (let i = currentYear - 4; i <= currentYear + 1; i++) {
|
||||
yearOptions.value.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷操作
|
||||
const handleAdd = () => {
|
||||
router.push('/cw/application')
|
||||
}
|
||||
|
||||
const showMyApplications = () => {
|
||||
router.push({ path: '/cw/application', query: { tab: 'my' } })
|
||||
}
|
||||
|
||||
const showPendingApplications = () => {
|
||||
router.push({ path: '/cw/application', query: { tab: 'pending' } })
|
||||
}
|
||||
|
||||
const showStatistics = () => {
|
||||
router.push('/cw/application/summary')
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount: number | undefined): string => {
|
||||
if (!amount) return '0.00'
|
||||
return amount.toFixed(2)
|
||||
}
|
||||
|
||||
// 计算审批率
|
||||
const calculateApprovalRate = (row: any): number => {
|
||||
if (!row.totalCount || row.totalCount === 0) return 0
|
||||
return Math.round((row.approvedCount / row.totalCount) * 100)
|
||||
}
|
||||
|
||||
// 获取进度条状态
|
||||
const getProgressStatus = (row: any): string => {
|
||||
const rate = calculateApprovalRate(row)
|
||||
if (rate >= 80) return 'success'
|
||||
if (rate >= 60) return 'warning'
|
||||
return 'exception'
|
||||
}
|
||||
|
||||
// 查看部门详情
|
||||
const viewDepartmentDetail = (row: any) => {
|
||||
router.push({
|
||||
path: '/cw/application',
|
||||
query: { deptId: row.deptId, deptName: row.deptName }
|
||||
})
|
||||
}
|
||||
|
||||
// 导出部门数据
|
||||
const exportDepartmentData = (row: any) => {
|
||||
ElMessage.success(`导出部门【${row.deptName}】数据功能开发中...`)
|
||||
}
|
||||
|
||||
// 导出部门统计
|
||||
const handleExportDeptStatistics = () => {
|
||||
ElMessage.success('导出部门统计功能开发中...')
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
const loadStatistics = async () => {
|
||||
try {
|
||||
statisticsLoading.value = true
|
||||
const [statsRes, deptRes] = await Promise.all([
|
||||
getApplicationStatistics(selectedYear.value),
|
||||
getStatisticsByDept()
|
||||
])
|
||||
|
||||
if (statsRes.code === 200) {
|
||||
Object.assign(statistics, statsRes.data)
|
||||
}
|
||||
|
||||
if (deptRes.code === 200) {
|
||||
statistics.deptStatistics = deptRes.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
ElMessage.error('获取统计数据失败')
|
||||
} finally {
|
||||
statisticsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 年份变化处理
|
||||
const handleYearChange = () => {
|
||||
loadStatistics()
|
||||
loadTrendChart()
|
||||
}
|
||||
|
||||
// 加载趋势图表
|
||||
const loadTrendChart = async () => {
|
||||
try {
|
||||
const response = await getStatisticsByMonth(selectedYear.value)
|
||||
if (response.code === 200) {
|
||||
renderTrendChart(response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取月度统计失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染趋势图表
|
||||
const renderTrendChart = (data: any[]) => {
|
||||
if (!trendChartInstance) return
|
||||
|
||||
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
const applicationCounts = new Array(12).fill(0)
|
||||
const approvedCounts = new Array(12).fill(0)
|
||||
|
||||
data.forEach(item => {
|
||||
const monthIndex = item.month - 1
|
||||
if (monthIndex >= 0 && monthIndex < 12) {
|
||||
applicationCounts[monthIndex] = item.applicationCount || 0
|
||||
approvedCounts[monthIndex] = item.approvedCount || 0
|
||||
}
|
||||
})
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['申报数量', '通过数量']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: months
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '申报数量',
|
||||
type: 'line',
|
||||
data: applicationCounts,
|
||||
smooth: true,
|
||||
itemStyle: { color: '#409EFF' }
|
||||
},
|
||||
{
|
||||
name: '通过数量',
|
||||
type: 'line',
|
||||
data: approvedCounts,
|
||||
smooth: true,
|
||||
itemStyle: { color: '#67C23A' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
trendChartInstance.setOption(option)
|
||||
}
|
||||
|
||||
// 渲染状态分布图
|
||||
const renderStatusChart = () => {
|
||||
if (!statusChartInstance) return
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '申报状态',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: statistics.approvingCount, name: '审批中', itemStyle: { color: '#E6A23C' } },
|
||||
{ value: statistics.approvedCount, name: '已通过', itemStyle: { color: '#67C23A' } },
|
||||
{ value: statistics.rejectedCount, name: '已拒绝', itemStyle: { color: '#F56C6C' } }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
statusChartInstance.setOption(option)
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = async () => {
|
||||
await nextTick()
|
||||
|
||||
if (trendChart.value) {
|
||||
trendChartInstance = echarts.init(trendChart.value)
|
||||
loadTrendChart()
|
||||
}
|
||||
|
||||
if (statusChart.value) {
|
||||
statusChartInstance = echarts.init(statusChart.value)
|
||||
renderStatusChart()
|
||||
}
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
trendChartInstance?.resize()
|
||||
statusChartInstance?.resize()
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
generateYearOptions()
|
||||
loadStatistics()
|
||||
initCharts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
// 快捷操作卡片
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409EFF;
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 28px;
|
||||
color: #409EFF;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片
|
||||
.stat-card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
&.stat-card-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-warning {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-danger {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ffa726 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-info {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32px;
|
||||
margin-right: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图表卡片
|
||||
.chart-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格卡片
|
||||
.table-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
|
||||
&.success {
|
||||
color: #67C23A;
|
||||
}
|
||||
}
|
||||
|
||||
.rate-text {
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
// 动画效果
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-action-card,
|
||||
.stat-card,
|
||||
.chart-card,
|
||||
.table-card {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.quick-action {
|
||||
padding: 15px;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
padding: 15px;
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.stat-info .stat-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div class="image-preview-test">
|
||||
<el-card header="图片预览组件测试">
|
||||
<el-form :model="testForm" label-width="120px">
|
||||
<el-form-item label="图片源:">
|
||||
<el-input v-model="testForm.src" placeholder="输入OSS ID或图片URL" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模式:">
|
||||
<el-radio-group v-model="testForm.isOssId">
|
||||
<el-radio :label="true">OSS ID模式</el-radio>
|
||||
<el-radio :label="false">直接URL模式</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="调试模式:">
|
||||
<el-switch v-model="testForm.debug" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="尺寸:">
|
||||
<el-input v-model="testForm.width" placeholder="宽度" style="width: 100px" />
|
||||
<span style="margin: 0 10px">×</span>
|
||||
<el-input v-model="testForm.height" placeholder="高度" style="width: 100px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="testImage">测试图片</el-button>
|
||||
<el-button @click="checkOssId" :loading="checkingOssId">检查OSS ID</el-button>
|
||||
<el-button @click="clearTest">清除</el-button>
|
||||
<el-button @click="testDefaultOssId">测试默认OSS ID</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider content-position="left">预览结果</el-divider>
|
||||
|
||||
<div class="preview-container">
|
||||
<ImagePreview
|
||||
v-if="showPreview"
|
||||
:src="testForm.src"
|
||||
:is-oss-id="testForm.isOssId"
|
||||
:width="testForm.width || 200"
|
||||
:height="testForm.height || 200"
|
||||
:debug="testForm.debug"
|
||||
/>
|
||||
<div v-else class="no-preview">
|
||||
<el-icon><picture /></el-icon>
|
||||
<p>请输入图片源进行测试</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">调试指南</el-divider>
|
||||
|
||||
<div class="debug-guide">
|
||||
<el-alert title="调试步骤" type="info" show-icon :closable="false">
|
||||
<template #default>
|
||||
<p>1. 打开浏览器开发者工具 → Console 面板</p>
|
||||
<p>2. 开启"调试模式"可以看到详细的执行日志</p>
|
||||
<p>3. 观察 [ImagePreview Debug] 开头的日志信息</p>
|
||||
<p>4. 查看是否有错误信息(红色日志)</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-card style="margin-top: 15px" header="常见问题排查">
|
||||
<el-collapse>
|
||||
<el-collapse-item title="1. OSS接口调用失败" name="1">
|
||||
<p><strong>现象:</strong>控制台显示 "获取OSS URL失败"</p>
|
||||
<p><strong>排查:</strong>检查OSS ID是否正确,后端接口是否正常</p>
|
||||
<p><strong>解决:</strong>确认 /resource/oss/listByIds/{ossId} 接口可以正常访问</p>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="2. 图片请求权限问题" name="2">
|
||||
<p><strong>现象:</strong>控制台显示 401/403 错误</p>
|
||||
<p><strong>排查:</strong>检查token是否有效,clientId是否正确</p>
|
||||
<p><strong>解决:</strong>确认request拦截器正确添加了Authorization头</p>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="3. blob URL创建失败" name="3">
|
||||
<p><strong>现象:</strong>控制台显示 "无效的图片格式"</p>
|
||||
<p><strong>排查:</strong>检查图片URL返回的内容类型</p>
|
||||
<p><strong>解决:</strong>确认返回的是真实图片数据,不是HTML错误页面</p>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="4. CORS跨域问题" name="4">
|
||||
<p><strong>现象:</strong>控制台显示CORS相关错误</p>
|
||||
<p><strong>排查:</strong>检查图片服务器的CORS配置</p>
|
||||
<p><strong>解决:</strong>配置后端代理或修改CORS策略</p>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import { Picture } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
|
||||
const testForm = ref({
|
||||
src: '',
|
||||
isOssId: true,
|
||||
debug: true,
|
||||
width: '200',
|
||||
height: '200'
|
||||
});
|
||||
|
||||
const checkingOssId = ref(false);
|
||||
|
||||
const showPreview = computed(() => {
|
||||
return testForm.value.src && testForm.value.src.trim() !== '';
|
||||
});
|
||||
|
||||
const testImage = () => {
|
||||
if (!testForm.value.src) {
|
||||
ElMessage.warning('请输入图片源');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('='.repeat(50));
|
||||
console.log('开始测试图片预览组件');
|
||||
console.log('测试参数:', {
|
||||
src: testForm.value.src,
|
||||
isOssId: testForm.value.isOssId,
|
||||
debug: testForm.value.debug,
|
||||
width: testForm.value.width,
|
||||
height: testForm.value.height
|
||||
});
|
||||
console.log('='.repeat(50));
|
||||
};
|
||||
|
||||
const checkOssId = async () => {
|
||||
if (!testForm.value.src) {
|
||||
ElMessage.warning('请输入OSS ID');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!testForm.value.isOssId) {
|
||||
ElMessage.warning('请切换到OSS ID模式进行检查');
|
||||
return;
|
||||
}
|
||||
|
||||
checkingOssId.value = true;
|
||||
|
||||
try {
|
||||
console.log('='.repeat(50));
|
||||
console.log('检查OSS ID:', testForm.value.src);
|
||||
|
||||
const response = await listByIds(testForm.value.src);
|
||||
// RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
console.log('API响应原始数据:', response);
|
||||
const ossList = response.data || response;
|
||||
|
||||
console.log('OSS检查结果:', {
|
||||
dataLength: ossList?.length || 0,
|
||||
data: ossList
|
||||
});
|
||||
|
||||
if (!ossList || ossList.length === 0) {
|
||||
ElMessage.error(`OSS ID ${testForm.value.src} 在数据库中不存在`);
|
||||
console.error('OSS ID不存在:', testForm.value.src);
|
||||
} else {
|
||||
const ossFile = ossList[0];
|
||||
ElMessage.success(`OSS ID ${testForm.value.src} 存在`);
|
||||
console.log('OSS文件信息:', {
|
||||
ossId: ossFile.ossId,
|
||||
fileName: ossFile.fileName,
|
||||
originalName: ossFile.originalName,
|
||||
fileSuffix: ossFile.fileSuffix,
|
||||
url: ossFile.url,
|
||||
createTime: ossFile.createTime,
|
||||
service: ossFile.service
|
||||
});
|
||||
|
||||
// 检查文件类型
|
||||
if (ossFile.fileSuffix && !ossFile.fileSuffix.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp|bmp)$/)) {
|
||||
ElMessage.warning(`文件类型 ${ossFile.fileSuffix} 可能不是图片格式`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('='.repeat(50));
|
||||
} catch (error) {
|
||||
console.error('检查OSS ID失败:', error);
|
||||
ElMessage.error(`检查OSS ID失败: ${error.message}`);
|
||||
} finally {
|
||||
checkingOssId.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const clearTest = () => {
|
||||
testForm.value.src = '';
|
||||
console.clear();
|
||||
ElMessage.success('已清除测试数据和控制台日志');
|
||||
};
|
||||
|
||||
const testDefaultOssId = () => {
|
||||
// 这里可以设置一个测试用的OSS ID
|
||||
testForm.value.src = '1'; // 请替换为实际存在的OSS ID
|
||||
testForm.value.isOssId = true;
|
||||
testForm.value.debug = true;
|
||||
|
||||
ElMessage.info('已设置默认测试OSS ID,请根据实际情况修改');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-preview-test {
|
||||
padding: 20px;
|
||||
|
||||
.preview-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 250px;
|
||||
border: 2px dashed #dcdfe6;
|
||||
border-radius: 8px;
|
||||
background-color: #fafafa;
|
||||
|
||||
.no-preview {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
|
||||
.el-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debug-guide {
|
||||
.el-alert {
|
||||
:deep(.el-alert__content) {
|
||||
p {
|
||||
margin: 5px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-collapse {
|
||||
:deep(.el-collapse-item__content) {
|
||||
p {
|
||||
margin: 8px 0;
|
||||
line-height: 1.6;
|
||||
|
||||
strong {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="image-upload-test">
|
||||
<el-card header="ImageUpload组件测试 - 认证图片回显和预览">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="测试数据1">
|
||||
<el-button @click="loadTestData1" type="primary">加载单个OSS ID</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试单个OSS文件回显</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="测试数据2">
|
||||
<el-button @click="loadTestData2" type="primary">加载多个OSS ID</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试多个OSS文件回显</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="测试数据3">
|
||||
<el-button @click="loadTestData3" type="primary">加载URL格式</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试直接URL回显</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="清空数据">
|
||||
<el-button @click="clearData" type="danger">清空</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">清空所有图片数据</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="当前值">
|
||||
<el-input v-model="form.images" type="textarea" :rows="3" readonly />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="图片上传">
|
||||
<ImageUpload v-model="form.images" :limit="5" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="test-info">
|
||||
<h3>测试说明</h3>
|
||||
<ul>
|
||||
<li><strong>功能验证</strong>:
|
||||
<ol>
|
||||
<li>界面打开时历史图片是否正常显示(通过认证接口获取blob URL)</li>
|
||||
<li>点击图片预览是否正常工作</li>
|
||||
<li>上传新图片功能是否正常</li>
|
||||
<li>删除图片功能是否正常</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><strong>技术要点</strong>:
|
||||
<ul>
|
||||
<li>使用 Promise.all 并行处理多个文件的blob URL转换</li>
|
||||
<li>缓存机制避免重复请求相同图片的认证数据</li>
|
||||
<li>降级处理:认证失败时使用原始URL</li>
|
||||
<li>完整的blob URL生命周期管理,防止内存泄漏</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>错误处理</strong>:如果图片无法显示,检查控制台错误信息</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import ImageUpload from '@/components/ImageUpload/index.vue';
|
||||
|
||||
const form = reactive({
|
||||
images: ''
|
||||
});
|
||||
|
||||
// 测试数据1:单个OSS ID
|
||||
const loadTestData1 = () => {
|
||||
form.images = '1951584287279034370'; // 示例OSS ID,实际使用时需要替换为有效的OSS ID
|
||||
};
|
||||
|
||||
// 测试数据2:多个OSS ID
|
||||
const loadTestData2 = () => {
|
||||
form.images = '1951584287279034370,1951584287279034371'; // 示例多个OSS ID
|
||||
};
|
||||
|
||||
// 测试数据3:URL格式
|
||||
const loadTestData3 = () => {
|
||||
form.images = '/resource/oss/download/1951584287279034370'; // 示例OSS下载URL
|
||||
};
|
||||
|
||||
// 清空数据
|
||||
const clearData = () => {
|
||||
form.images = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-upload-test {
|
||||
padding: 20px;
|
||||
|
||||
.test-info {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 20px;
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -80,9 +80,9 @@
|
||||
>拒绝</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -216,11 +216,12 @@
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item">
|
||||
<!-- <img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
<el-icon class="preview-icon"><ZoomIn /></el-icon>
|
||||
</div>
|
||||
</div> -->
|
||||
<image-preview :src="image" :width="120" :height="90" :isOssId="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
@@ -283,11 +284,12 @@
|
||||
</div>
|
||||
<div class="hall-images" v-if="getHallImagesByName(hallName).length > 0">
|
||||
<div class="image-gallery">
|
||||
<div v-for="(image, index) in getHallImagesByName(hallName)" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div v-for="(image, index) in getHallImagesByName(hallName)" :key="index" class="image-item">
|
||||
<!-- <img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
<el-icon class="preview-icon"><ZoomIn /></el-icon>
|
||||
</div>
|
||||
</div> -->
|
||||
<image-preview :src="image" :width="120" :height="90" :isOssId="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -418,6 +420,8 @@ import { listHallInfo } from '@/api/hq/hallInfo';
|
||||
import { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
|
||||
import { HallInfoVO } from '@/api/hq/hallInfo/types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
import { OssVO } from '@/api/system/oss/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { hall_reserv_status } = toRefs<any>(proxy?.useDict('hall_reserv_status'));
|
||||
@@ -426,6 +430,7 @@ const userStore = useUserStore();
|
||||
const hallReservationList = ref<HallReservationVO[]>([]);
|
||||
const hallList = ref<HallInfoVO[]>([]);
|
||||
const viewHallImages = ref<string[]>([]);
|
||||
const hallImagesCache = ref<Map<string, string[]>>(new Map()); // 缓存报告厅图片URL
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
@@ -510,49 +515,75 @@ const getHallList = async () => {
|
||||
|
||||
// 调试信息:检查每个报告厅的图片信息
|
||||
console.log('报告厅列表数据:', hallList.value);
|
||||
hallList.value.forEach((hall, index) => {
|
||||
console.log(`报告厅 ${index + 1} - ${hall.hallName}:`, {
|
||||
id: hall.id,
|
||||
hallImage: hall.hallImage,
|
||||
images: getHallImages(hall.hallImage || '')
|
||||
});
|
||||
});
|
||||
|
||||
// 异步加载所有报告厅的图片URL
|
||||
await loadAllHallImages();
|
||||
} catch (error) {
|
||||
console.error('获取报告厅列表失败:', error);
|
||||
hallList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理报告厅图片 - 可能有多张图片,用逗号分隔 */
|
||||
const getHallImages = (imageUrl: string): string[] => {
|
||||
if (!imageUrl || imageUrl.trim() === '') return [];
|
||||
// 支持多种分隔符:逗号、分号、空格
|
||||
const separators = /[,;|\\s]+/;
|
||||
const images = imageUrl
|
||||
.split(separators)
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url && url !== 'null' && url !== 'undefined');
|
||||
/** 加载所有报告厅的图片URL */
|
||||
const loadAllHallImages = async () => {
|
||||
const imagePromises = hallList.value.map(async (hall) => {
|
||||
if (hall.hallImage) {
|
||||
try {
|
||||
const images = await getHallImages(hall.hallImage);
|
||||
hallImagesCache.value.set(hall.hallName, images);
|
||||
console.log(`报告厅 ${hall.hallName} 图片加载完成:`, images);
|
||||
} catch (error) {
|
||||
console.error(`报告厅 ${hall.hallName} 图片加载失败:`, error);
|
||||
hallImagesCache.value.set(hall.hallName, []);
|
||||
}
|
||||
} else {
|
||||
hallImagesCache.value.set(hall.hallName, []);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('处理图片URL:', imageUrl, '结果:', images);
|
||||
return images;
|
||||
await Promise.all(imagePromises);
|
||||
console.log('所有报告厅图片加载完成');
|
||||
};
|
||||
|
||||
/** 处理报告厅图片 - 通过OSS ID获取真实URL */
|
||||
const getHallImages = async (ossIds: string): Promise<string[]> => {
|
||||
if (!ossIds || ossIds.trim() === '') return [];
|
||||
|
||||
try {
|
||||
// 支持多种分隔符:逗号、分号、空格
|
||||
const separators = /[,;|\\s]+/;
|
||||
const idArray = ossIds
|
||||
.split(separators)
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id && id !== 'null' && id !== 'undefined');
|
||||
|
||||
if (idArray.length === 0) return [];
|
||||
|
||||
console.log('处理OSS ID:', ossIds, 'ID数组:', idArray);
|
||||
|
||||
// 调用API获取真实的图片URL
|
||||
// const response = await listByIds(idArray.join(','));
|
||||
// const ossList: OssVO[] = response.data || [];
|
||||
|
||||
// // 提取URL列表
|
||||
// const urls = ossList.map((oss) => oss.url).filter((url) => url);
|
||||
|
||||
// console.log('获取到的图片URL:', urls);
|
||||
return idArray;
|
||||
} catch (error) {
|
||||
console.error('获取图片URL失败:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取记录对应的报告厅图片 */
|
||||
const getRecordHallImages = (record: HallReservationVO): string[] => {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === record.hallName);
|
||||
if (selectedHall && selectedHall.hallImage) {
|
||||
return getHallImages(selectedHall.hallImage);
|
||||
}
|
||||
return [];
|
||||
return hallImagesCache.value.get(record.hallName) || [];
|
||||
};
|
||||
|
||||
/** 根据报告厅名称获取图片 */
|
||||
const getHallImagesByName = (hallName: string): string[] => {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === hallName);
|
||||
if (selectedHall && selectedHall.hallImage) {
|
||||
return getHallImages(selectedHall.hallImage);
|
||||
}
|
||||
return [];
|
||||
return hallImagesCache.value.get(hallName) || [];
|
||||
};
|
||||
|
||||
/** 获取选中记录中的唯一报告厅名称 */
|
||||
@@ -698,12 +729,7 @@ const handleView = async (row: HallReservationVO) => {
|
||||
|
||||
// 查找对应的报告厅并显示图片
|
||||
if (viewForm.value.hallName) {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
|
||||
if (selectedHall && selectedHall.hallImage) {
|
||||
viewHallImages.value = getHallImages(selectedHall.hallImage);
|
||||
} else {
|
||||
viewHallImages.value = [];
|
||||
}
|
||||
viewHallImages.value = hallImagesCache.value.get(viewForm.value.hallName) || [];
|
||||
} else {
|
||||
viewHallImages.value = [];
|
||||
}
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallInfo:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallInfo:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -254,6 +254,7 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listHallInfo(queryParams.value);
|
||||
console.log('res', res);
|
||||
hallInfoList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
@@ -311,6 +312,7 @@ const handleUpdate = async (row?: HallInfoVO) => {
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
console.log('form.value', form.value);
|
||||
hallInfoFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -210,15 +210,18 @@
|
||||
{{ displayHallDescription(hall.hallDescription) }}
|
||||
</div>
|
||||
<div class="hall-images">
|
||||
<template v-if="getHallImages(hall.hallImage || '').length > 0">
|
||||
<img
|
||||
v-for="(image, index) in getHallImages(hall.hallImage || '')"
|
||||
<template v-if="hall.hallImage && hall.hallImage.length > 0">
|
||||
<!-- <img
|
||||
v-for="(image, index) in hall.imageUrls"
|
||||
:key="index"
|
||||
:src="image"
|
||||
class="hall-image"
|
||||
@error="handleImageError"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
/> -->
|
||||
<div v-for="(image, index) in hall.hallImage" :key="index">
|
||||
<image-preview :src="image" :width="40" :height="40" :isOssId="true" />
|
||||
</div>
|
||||
</template>
|
||||
<span v-else class="no-image-text">暂无图片</span>
|
||||
</div>
|
||||
@@ -230,11 +233,12 @@
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="selected-hall-images">
|
||||
<div class="image-gallery" v-if="selectedHallImages.length > 0">
|
||||
<div v-for="(image, index) in selectedHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div v-for="(image, index) in selectedHallImages" :key="index">
|
||||
<!-- <img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
<el-icon class="preview-icon"><ZoomIn /></el-icon>
|
||||
</div>
|
||||
</div> -->
|
||||
<image-preview :src="image" :width="120" :height="90" :isOssId="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
@@ -298,11 +302,12 @@
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item">
|
||||
<!-- <img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
<el-icon class="preview-icon"><ZoomIn /></el-icon>
|
||||
</div>
|
||||
</div> -->
|
||||
<image-preview :src="image" :width="120" :height="90" :isOssId="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
@@ -338,7 +343,7 @@
|
||||
<el-avatar :size="32" :src="auditor.avatar">{{ auditor.nickName?.substring(0, 1) }}</el-avatar>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.dept?.deptName }}</div>
|
||||
<div class="auditor-dept">{{ auditor?.phonenumber }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auditorList.length === 0" class="no-auditor">暂无可审核人员</div>
|
||||
@@ -525,6 +530,29 @@ const getHallList = async () => {
|
||||
hallStatus: '1'
|
||||
}); // 获取正常状态的报告厅
|
||||
hallList.value = res.rows || [];
|
||||
// for (const hall of hallList.value) {
|
||||
// if (hall.hallImage) {
|
||||
// try {
|
||||
// const imageUrls = await getHallImages(hall.hallImage);
|
||||
// hall.imageUrls = imageUrls;
|
||||
// } catch (error) {
|
||||
// console.error(`获取报告厅 ${hall.hallName} 图片失败:`, error);
|
||||
// hall.imageUrls = [];
|
||||
// }
|
||||
// } else {
|
||||
// hall.imageUrls = [];
|
||||
// }
|
||||
// }
|
||||
//hall.hallImage.split(',')
|
||||
hallList.value.forEach((hall) => {
|
||||
//加一些判断
|
||||
if (hall.hallImage) {
|
||||
hall.hallImage = hall.hallImage.split(',');
|
||||
} else {
|
||||
hall.hallImage = [];
|
||||
}
|
||||
});
|
||||
// 为每个报告厅获取图片URL
|
||||
|
||||
// 调试信息:检查每个报告厅的图片信息
|
||||
console.log('报告厅列表数据:', hallList.value);
|
||||
@@ -532,7 +560,7 @@ const getHallList = async () => {
|
||||
console.log(`报告厅 ${index + 1} - ${hall.hallName}:`, {
|
||||
id: hall.id,
|
||||
hallImage: hall.hallImage,
|
||||
images: getHallImages(hall.hallImage || '')
|
||||
imageUrls: hall.imageUrls
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -547,24 +575,62 @@ const handleHallChange = (hallName: string) => {
|
||||
if (selectedHall) {
|
||||
form.value.hallId = selectedHall.id;
|
||||
// 更新选中的报告厅图片
|
||||
selectedHallImages.value = getHallImages(selectedHall.hallImage || '');
|
||||
selectedHallImages.value = selectedHall.hallImage || [];
|
||||
} else {
|
||||
selectedHallImages.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理报告厅图片 - 可能有多张图片,用逗号分隔 */
|
||||
const getHallImages = (imageUrl: string): string[] => {
|
||||
/** 处理报告厅图片 - 支持OSS ID和直接URL格式 */
|
||||
const getHallImages = async (imageUrl: string): Promise<string[]> => {
|
||||
if (!imageUrl || imageUrl.trim() === '') return [];
|
||||
|
||||
// 支持多种分隔符:逗号、分号、空格
|
||||
const separators = /[,;|\s]+/;
|
||||
const images = imageUrl
|
||||
const items = imageUrl
|
||||
.split(separators)
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url && url !== 'null' && url !== 'undefined');
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item && item !== 'null' && item !== 'undefined');
|
||||
|
||||
console.log('处理图片URL:', imageUrl, '结果:', images);
|
||||
return images;
|
||||
console.log('处理图片信息:', imageUrl, '结果:', items);
|
||||
|
||||
if (items.length === 0) return [];
|
||||
|
||||
// 判断是否为数字(OSS ID)还是URL
|
||||
const isOssId = items.every((item) => /^\d+$/.test(item));
|
||||
|
||||
if (isOssId) {
|
||||
// 如果是OSS ID,需要调用接口获取URL
|
||||
console.log('检测到OSS ID格式,需要调用接口获取URL');
|
||||
try {
|
||||
// 动态导入OSS模块
|
||||
const { listByIds } = await import('@/api/system/oss');
|
||||
|
||||
// 批量获取OSS信息
|
||||
const ossIdsParam = items.join(',');
|
||||
const res = await listByIds(ossIdsParam);
|
||||
const ossList = res.data || [];
|
||||
|
||||
// 获取图片URL列表
|
||||
const imageUrls: string[] = [];
|
||||
|
||||
for (const ossInfo of ossList) {
|
||||
if (ossInfo.url) {
|
||||
imageUrls.push(ossInfo.url);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('获取到的图片URL:', imageUrls);
|
||||
return imageUrls;
|
||||
} catch (error) {
|
||||
console.error('获取OSS信息失败:', error);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
// 如果是URL格式,直接返回
|
||||
console.log('检测到URL格式,直接使用');
|
||||
return items;
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理图片加载成功 */
|
||||
@@ -744,7 +810,7 @@ const handleView = async (row: HallReservationVO) => {
|
||||
if (viewForm.value.hallName) {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
|
||||
if (selectedHall && selectedHall.hallImage) {
|
||||
viewHallImages.value = getHallImages(selectedHall.hallImage);
|
||||
viewHallImages.value = selectedHall.hallImage;
|
||||
} else {
|
||||
viewHallImages.value = [];
|
||||
}
|
||||
@@ -787,8 +853,9 @@ const handleUpdate = async (row?: HallReservationVO) => {
|
||||
// 如果有报告厅名称,找到对应的报告厅并显示图片
|
||||
if (form.value.hallName) {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === form.value.hallName);
|
||||
console.log('selectedHall', selectedHall);
|
||||
if (selectedHall && selectedHall.hallImage) {
|
||||
selectedHallImages.value = getHallImages(selectedHall.hallImage);
|
||||
selectedHallImages.value = selectedHall.hallImage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="sc_showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="sc_queryFormRef" :model="sc_queryParams" :inline="true">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable>
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="分类编码" prop="categoryCode">
|
||||
<el-input v-model="sc_queryParams.categoryCode" placeholder="请输入分类编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="categoryName">
|
||||
<el-input v-model="sc_queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="分类描述" prop="categoryDesc">
|
||||
<el-input v-model="sc_queryParams.categoryDesc" placeholder="请输入分类描述" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="排序" prop="sortOrder">
|
||||
<el-input v-model="sc_queryParams.sortOrder" placeholder="请输入排序" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="sc_queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satCategory:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Edit" :disabled="sc_single" @click="handleUpdate()" v-hasPermi="['hq:satCategory:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="sc_multiple" @click="handleDelete()" v-hasPermi="['hq:satCategory:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:satCategory:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="sc_showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table v-loading="sc_loading" border :data="sc_satCategoryList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_delete" :value="scope.row.deleted"/>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="分类编码" align="center" prop="categoryCode" />
|
||||
<el-table-column label="分类名称" align="center" prop="categoryName" />
|
||||
<el-table-column label="分类描述" align="center" prop="categoryDesc" />
|
||||
<!-- <el-table-column label="排序" align="center" prop="sortOrder" /> -->
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_enable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satCategory:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satCategory:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="sc_total > 0"
|
||||
:total="sc_total"
|
||||
v-model:page="sc_queryParams.pageNum"
|
||||
v-model:limit="sc_queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改题目分类对话框 -->
|
||||
<el-dialog :title="sc_dialog.title" v-model="sc_dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="sc_satCategoryFormRef" :model="sc_form" :rules="sc_rules" label-width="80px">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="form.deleted" placeholder="请选择删除标志">
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="分类编码" prop="categoryCode">
|
||||
<el-input v-model="sc_form.categoryCode" placeholder="请输入分类编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="categoryName">
|
||||
<el-input v-model="sc_form.categoryName" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类描述" prop="categoryDesc">
|
||||
<el-input v-model="sc_form.categoryDesc" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="排序" prop="sortOrder">
|
||||
<el-input v-model="sc_form.sortOrder" placeholder="请输入排序" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="sc_form.status" placeholder="请选择状态">
|
||||
<el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="sc_form.remark" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="sc_buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="SatCategory" lang="ts">
|
||||
import { ref, reactive, toRefs, getCurrentInstance, onMounted } from 'vue';
|
||||
import type { ComponentInternalInstance, ElFormInstance } from 'vue';
|
||||
import { listSatCategory, getSatCategory, delSatCategory, addSatCategory, updateSatCategory } from '@/api/hq/satCategory';
|
||||
import { SatCategoryVO, SatCategoryQuery, SatCategoryForm } from '@/api/hq/satCategory/types';
|
||||
|
||||
// 类型定义
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface PageData<T, Q> {
|
||||
form: T;
|
||||
queryParams: Q;
|
||||
rules: Record<string, any>;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_item_enable, sys_item_delete } = toRefs<any>(proxy?.useDict('sys_item_enable', 'sys_item_delete'));
|
||||
|
||||
const sc_satCategoryList = ref<SatCategoryVO[]>([]);
|
||||
const sc_buttonLoading = ref(false);
|
||||
const sc_loading = ref(true);
|
||||
const sc_showSearch = ref(true);
|
||||
const sc_ids = ref<Array<string | number>>([]);
|
||||
const sc_single = ref(true);
|
||||
const sc_multiple = ref(true);
|
||||
const sc_total = ref(0);
|
||||
|
||||
const sc_queryFormRef = ref<ElFormInstance>();
|
||||
const sc_satCategoryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const sc_dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const sc_initFormData: SatCategoryForm = {
|
||||
id: undefined,
|
||||
deleted: undefined,
|
||||
categoryCode: undefined,
|
||||
categoryName: undefined,
|
||||
categoryDesc: undefined,
|
||||
sortOrder: undefined,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
};
|
||||
const sc_data = reactive<PageData<SatCategoryForm, SatCategoryQuery>>({
|
||||
form: { ...sc_initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deleted: undefined,
|
||||
categoryCode: undefined,
|
||||
categoryName: undefined,
|
||||
categoryDesc: undefined,
|
||||
sortOrder: undefined,
|
||||
status: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
categoryCode: [{ required: true, message: '分类编码不能为空', trigger: 'blur' }],
|
||||
categoryName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams: sc_queryParams, form: sc_form, rules: sc_rules } = toRefs(sc_data);
|
||||
|
||||
/** 查询题目分类列表 */
|
||||
const getList = async () => {
|
||||
sc_loading.value = true;
|
||||
const res = await listSatCategory(sc_queryParams.value);
|
||||
sc_satCategoryList.value = res.rows;
|
||||
sc_total.value = res.total;
|
||||
sc_loading.value = false;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
sc_dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
sc_form.value = { ...sc_initFormData };
|
||||
sc_satCategoryFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
sc_queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
sc_queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: SatCategoryVO[]) => {
|
||||
sc_ids.value = selection.map((item) => item.id);
|
||||
sc_single.value = selection.length != 1;
|
||||
sc_multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
sc_dialog.visible = true;
|
||||
sc_dialog.title = '添加题目分类';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: SatCategoryVO) => {
|
||||
reset();
|
||||
const _id = row?.id || sc_ids.value[0];
|
||||
const res = await getSatCategory(_id);
|
||||
Object.assign(sc_form.value, res.data);
|
||||
sc_dialog.visible = true;
|
||||
sc_dialog.title = '修改题目分类';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
sc_satCategoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
sc_buttonLoading.value = true;
|
||||
if (sc_form.value.id) {
|
||||
await updateSatCategory(sc_form.value).finally(() => (sc_buttonLoading.value = false));
|
||||
} else {
|
||||
await addSatCategory(sc_form.value).finally(() => (sc_buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
sc_dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: SatCategoryVO) => {
|
||||
const _ids = row?.id || sc_ids.value;
|
||||
let confirmMessage = '';
|
||||
|
||||
if (row) {
|
||||
// 单个删除
|
||||
confirmMessage = `是否确认删除题目分类\"${row.categoryName}(${row.categoryCode})\"的数据项?\n\n【提示】本操作为逻辑删除,数据不会被物理移除,可在后台恢复。`;
|
||||
} else {
|
||||
// 批量删除
|
||||
const selectedRows = sc_satCategoryList.value.filter((item) => sc_ids.value.includes(item.id));
|
||||
if (selectedRows.length === 1) {
|
||||
confirmMessage = `是否确认删除题目分类\"${selectedRows[0].categoryName}(${selectedRows[0].categoryCode})\"的数据项?\n\n【提示】本操作为逻辑删除,数据不会被物理移除,可在后台恢复。`;
|
||||
} else {
|
||||
const categoryNames = selectedRows.map((item) => `${item.categoryName}(${item.categoryCode})`).join('、');
|
||||
confirmMessage = `是否确认删除题目分类\"${categoryNames}\"的数据项?\n\n【提示】本操作为逻辑删除,数据不会被物理移除,可在后台恢复。`;
|
||||
}
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(confirmMessage).finally(() => (sc_loading.value = false));
|
||||
await delSatCategory(_ids);
|
||||
proxy?.$modal.msgSuccess('已逻辑删除');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'hq/satCategory/export',
|
||||
{
|
||||
...sc_queryParams.value
|
||||
},
|
||||
`satCategory_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -47,9 +47,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satCategory:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:satCategory:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:satDetail:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satOption:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:satOption:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,738 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="sq_proxy?.animate.searchAnimate.enter" :leave-active-class="sq_proxy?.animate.searchAnimate.leave">
|
||||
<div ref="sq_searchRef" v-show="sq_showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="sq_queryFormRef" :model="sq_queryParams" :inline="true">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="题目标题" prop="questionTitle">
|
||||
<el-input v-model="sq_queryParams.questionTitle" placeholder="请输入题目标题" clearable @keyup.enter="sq_handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属分类" prop="categoryId">
|
||||
<el-select v-model="sq_queryParams.categoryId" placeholder="请选择所属分类" clearable @change="sq_handleQuery">
|
||||
<el-option v-for="category in sq_satCategoryList" :key="category.id" :label="category.categoryName" :value="category.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="题目类型" prop="questionType">
|
||||
<el-select v-model="sq_queryParams.questionType" placeholder="请选择题目类型" clearable>
|
||||
<el-option v-for="dict in sq_sys_question_category" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="sq_queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in sq_sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="sq_handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="sq_resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row ref="sq_toolbarRef" :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="sq_handleAdd" v-hasPermi="['hq:satQuestion:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Edit" :disabled="sq_single" @click="sq_handleUpdate()" v-hasPermi="['hq:satQuestion:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="sq_multiple" @click="sq_handleDelete()" v-hasPermi="['hq:satQuestion:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="sq_handleExport" v-hasPermi="['hq:satQuestion:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch="sq_showSearch" @queryTable="sq_getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="table-container">
|
||||
<el-table v-loading="sq_loading" border :data="sq_satQuestionList" @selection-change="sq_handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="题目标题" align="center" prop="questionTitle" width="200">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="scope.row.questionTitle"
|
||||
placement="top"
|
||||
:show-after="100"
|
||||
:hide-after="100"
|
||||
popper-style="max-width: 300px; max-height: 200px;"
|
||||
>
|
||||
<div class="text-ellipsis">
|
||||
{{ scope.row.questionTitle }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属分类" align="center" prop="categoryId">
|
||||
<template #default="scope">
|
||||
{{ sq_satCategoryAllList.find((cat) => cat.id == scope.row.categoryId)?.categoryName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" align="center" prop="score">
|
||||
<template #default="scope">
|
||||
{{ scope.row.score }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="题目类型" align="center" prop="questionType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sq_sys_question_category" :value="scope.row.questionType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否必填" align="center" prop="isRequired">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sq_sys_item_required" :value="scope.row.isRequired" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sq_sys_item_enable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" width="200">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="scope.row.remark"
|
||||
placement="top"
|
||||
:show-after="100"
|
||||
:hide-after="100"
|
||||
popper-style="max-width: 300px; max-height: 200px;"
|
||||
>
|
||||
<div class="text-ellipsis">
|
||||
{{ scope.row.remark }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="设置选项" placement="top" v-if="scope.row.questionType === '1' || scope.row.questionType === '2'">
|
||||
<el-button link type="success" icon="List" @click="sq_handleOptionManage(scope.row)" v-hasPermi="['hq:satOption:list']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="sq_handleUpdate(scope.row)" v-hasPermi="['hq:satQuestion:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="sq_handleDelete(scope.row)" v-hasPermi="['hq:satQuestion:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="sq_total > 0"
|
||||
:total="sq_total"
|
||||
:page="sq_queryParams.pageNum"
|
||||
:limit="sq_queryParams.pageSize"
|
||||
@pagination="sq_getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改题库对话框 -->
|
||||
<el-dialog :title="sq_dialog.title" v-model="sq_dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="sq_satQuestionFormRef" :model="sq_form" :rules="sq_rules" label-width="80px">
|
||||
<el-form-item label="题目标题" prop="questionTitle">
|
||||
<el-input v-model="sq_form.questionTitle" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入题目标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属分类" prop="categoryId">
|
||||
<el-select v-model="sq_form.categoryId" placeholder="请选择所属分类">
|
||||
<el-option v-for="category in sq_satCategoryList" :key="category.id" :label="category.categoryName" :value="category.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="题目类型" prop="questionType">
|
||||
<el-select v-model="sq_form.questionType" placeholder="请选择题目类型">
|
||||
<el-option v-for="dict in sq_sys_question_category" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必填" prop="isRequired">
|
||||
<el-select v-model="sq_form.isRequired" placeholder="请选择是否必填">
|
||||
<el-option v-for="dict in sq_sys_item_required" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="sq_form.status" placeholder="请选择状态">
|
||||
<el-option v-for="dict in sq_sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="sq_form.remark" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="sq_buttonLoading" type="primary" @click="sq_submitForm">确 定</el-button>
|
||||
<el-button @click="sq_cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 选项管理对话框 -->
|
||||
<el-dialog :title="`管理题目【${sq_optionDialog.question?.questionTitle}】的选项`" v-model="sq_optionDialog.visible" width="850px" append-to-body>
|
||||
<div>
|
||||
<el-button type="primary" icon="Plus" @click="sq_addOption" size="small" class="mb-2">新增选项</el-button>
|
||||
<el-button type="success" icon="Menu" @click="sq_addDefaultOptions" size="small" class="mb-2 ml-2">默认选项</el-button>
|
||||
<el-table :data="sq_optionDialog.options" border size="small">
|
||||
<el-table-column label="内容" prop="optionContent" width="295">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.optionContent" placeholder="请输入选项内容" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" prop="optionScore" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionScore" :min="0" :max="100" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="optionOrder" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionOrder" :min="0" :max="99" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" prop="isDefault" width="90">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.isDefault" active-value="Y" inactive-value="N" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button icon="Delete" type="danger" size="small" @click="sq_removeOption(scope.$index)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="sq_saveOptions" :loading="sq_optionDialog.saveLoading">保存</el-button>
|
||||
<el-button @click="sq_optionDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="SqSatQuestion" lang="ts">
|
||||
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
|
||||
import { listSatQuestion, getSatQuestion, delSatQuestion, addSatQuestion, updateSatQuestion } from '@/api/hq/satQuestion';
|
||||
import { SatQuestionVO, SatQuestionQuery, SatQuestionForm } from '@/api/hq/satQuestion/types';
|
||||
import { listSatCategory, listAllSatCategory } from '@/api/hq/satCategory';
|
||||
import { SatCategoryVO } from '@/api/hq/satCategory/types';
|
||||
import { nextTick } from 'vue';
|
||||
import { listSatOption, addSatOption, updateSatOption, delSatOption } from '@/api/hq/satOption';
|
||||
|
||||
// 定义类型接口
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface PageData<T, Q> {
|
||||
form: T;
|
||||
queryParams: Q;
|
||||
rules: any;
|
||||
}
|
||||
|
||||
const { proxy: sq_proxy } = getCurrentInstance() as any;
|
||||
const {
|
||||
sys_question_category: sq_sys_question_category,
|
||||
sys_item_enable: sq_sys_item_enable,
|
||||
sys_item_required: sq_sys_item_required,
|
||||
sys_item_delete: sq_sys_item_delete
|
||||
} = toRefs<any>(sq_proxy?.useDict('sys_question_category', 'sys_item_enable', 'sys_item_required', 'sys_item_delete'));
|
||||
|
||||
const sq_satQuestionList = ref<SatQuestionVO[]>([]);
|
||||
const sq_satCategoryList = ref<SatCategoryVO[]>([]);
|
||||
const sq_satCategoryAllList = ref<SatCategoryVO[]>([]);
|
||||
const sq_buttonLoading = ref(false);
|
||||
const sq_loading = ref(true);
|
||||
const sq_showSearch = ref(true);
|
||||
const sq_ids = ref<Array<string | number>>([]);
|
||||
const sq_single = ref(true);
|
||||
const sq_multiple = ref(true);
|
||||
const sq_total = ref(0);
|
||||
|
||||
const sq_queryFormRef = ref<any>();
|
||||
const sq_satQuestionFormRef = ref<any>();
|
||||
|
||||
const sq_dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const sq_optionDialog = reactive({
|
||||
visible: false,
|
||||
question: null as SatQuestionVO | null,
|
||||
options: [] as any[],
|
||||
saveLoading: false
|
||||
});
|
||||
|
||||
const sq_initFormData: SatQuestionForm = {
|
||||
id: undefined,
|
||||
deleted: undefined,
|
||||
questionTitle: undefined,
|
||||
questionContent: undefined,
|
||||
categoryId: undefined,
|
||||
questionType: '1',
|
||||
isRequired: 1,
|
||||
sortOrder: undefined,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const sq_data = reactive<PageData<SatQuestionForm, SatQuestionQuery>>({
|
||||
form: { ...sq_initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deleted: undefined,
|
||||
questionTitle: undefined,
|
||||
questionContent: undefined,
|
||||
categoryId: undefined,
|
||||
questionType: undefined,
|
||||
isRequired: undefined,
|
||||
sortOrder: undefined,
|
||||
status: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
questionTitle: [{ required: true, message: '题目标题不能为空', trigger: 'blur' }],
|
||||
categoryId: [{ required: true, message: '所属分类不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams: sq_queryParams, form: sq_form, rules: sq_rules } = toRefs(sq_data);
|
||||
|
||||
/** 查询题库列表 */
|
||||
const sq_getList = async () => {
|
||||
sq_loading.value = true;
|
||||
const res = await listSatQuestion(sq_queryParams.value);
|
||||
sq_satQuestionList.value = res.rows;
|
||||
sq_total.value = res.total;
|
||||
sq_loading.value = false;
|
||||
};
|
||||
|
||||
/** 获取分类列表 */
|
||||
const sq_getCategoryList = async () => {
|
||||
const res = await listSatCategory({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
status: '0'
|
||||
});
|
||||
sq_satCategoryList.value = res.rows.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
};
|
||||
|
||||
/** 获取全部分类列表(包括已删除的) */
|
||||
const sq_getAllCategoryList = async () => {
|
||||
const res = await listAllSatCategory({
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
sq_satCategoryAllList.value = res.data.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const sq_cancel = () => {
|
||||
sq_reset();
|
||||
sq_dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const sq_reset = () => {
|
||||
sq_form.value = { ...sq_initFormData };
|
||||
sq_satQuestionFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const sq_handleQuery = () => {
|
||||
sq_queryParams.value.pageNum = 1;
|
||||
sq_getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const sq_resetQuery = () => {
|
||||
sq_queryFormRef.value?.resetFields();
|
||||
sq_handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const sq_handleSelectionChange = (selection: SatQuestionVO[]) => {
|
||||
sq_ids.value = selection.map((item) => item.id);
|
||||
sq_single.value = selection.length != 1;
|
||||
sq_multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const sq_handleAdd = () => {
|
||||
sq_reset();
|
||||
sq_dialog.visible = true;
|
||||
sq_dialog.title = '添加题库';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const sq_handleUpdate = async (row?: SatQuestionVO) => {
|
||||
sq_reset();
|
||||
const _id = row?.id || sq_ids.value[0];
|
||||
const res = await getSatQuestion(_id);
|
||||
Object.assign(sq_form.value, res.data);
|
||||
sq_dialog.visible = true;
|
||||
sq_dialog.title = '修改题库';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const sq_submitForm = () => {
|
||||
sq_satQuestionFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
sq_buttonLoading.value = true;
|
||||
if (sq_form.value.id) {
|
||||
await updateSatQuestion(sq_form.value).finally(() => (sq_buttonLoading.value = false));
|
||||
} else {
|
||||
await addSatQuestion(sq_form.value).finally(() => (sq_buttonLoading.value = false));
|
||||
}
|
||||
sq_proxy?.$modal.msgSuccess('操作成功');
|
||||
sq_dialog.visible = false;
|
||||
await sq_getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const sq_handleDelete = async (row?: SatQuestionVO) => {
|
||||
const _ids = row?.id || sq_ids.value;
|
||||
|
||||
let confirmMessage = '';
|
||||
if (row) {
|
||||
confirmMessage = `是否确认删除题目"${row.questionTitle}"?`;
|
||||
} else {
|
||||
confirmMessage = `是否确认删除选中的${sq_ids.value.length}个题目?`;
|
||||
}
|
||||
|
||||
await sq_proxy?.$modal.confirm(confirmMessage).finally(() => (sq_loading.value = false));
|
||||
await delSatQuestion(_ids);
|
||||
sq_proxy?.$modal.msgSuccess('删除成功');
|
||||
await sq_getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const sq_handleExport = () => {
|
||||
sq_proxy?.download(
|
||||
'hq/satQuestion/export',
|
||||
{
|
||||
...sq_queryParams.value
|
||||
},
|
||||
`satQuestion_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
const sq_toolbarRef = ref<HTMLElement>();
|
||||
const sq_searchRef = ref<HTMLElement>();
|
||||
|
||||
const sq_handleOptionManage = async (row: SatQuestionVO) => {
|
||||
sq_optionDialog.visible = true;
|
||||
sq_optionDialog.question = row;
|
||||
|
||||
sq_proxy?.$modal.loading('正在加载选项数据...');
|
||||
|
||||
try {
|
||||
const res = await listSatOption({
|
||||
questionId: row.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
|
||||
sq_optionDialog.options = res.rows.map((item) => ({
|
||||
id: item.id,
|
||||
questionId: item.questionId,
|
||||
optionContent: item.optionContent,
|
||||
optionScore: item.optionScore,
|
||||
optionOrder: item.optionOrder,
|
||||
isDefault: item.isDefault || 'N',
|
||||
tenantId: item.tenantId,
|
||||
createBy: item.createBy,
|
||||
createTime: item.createTime,
|
||||
updateBy: item.updateBy,
|
||||
updateTime: item.updateTime,
|
||||
deleted: item.deleted
|
||||
}));
|
||||
|
||||
sq_optionDialog.options.sort((a, b) => a.optionOrder - b.optionOrder);
|
||||
} catch (error) {
|
||||
console.error('加载选项失败:', error);
|
||||
sq_optionDialog.options = [];
|
||||
sq_proxy?.$modal.msgError('选项数据加载失败,请重试');
|
||||
} finally {
|
||||
sq_proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const sq_addOption = () => {
|
||||
sq_optionDialog.options.push({
|
||||
id: undefined,
|
||||
questionId: sq_optionDialog.question?.id,
|
||||
optionContent: '',
|
||||
optionScore: 0,
|
||||
optionOrder: sq_optionDialog.options.length + 1,
|
||||
isDefault: 'N',
|
||||
tenantId: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
deleted: '0'
|
||||
});
|
||||
nextTick(() => {
|
||||
// 可选:自动聚焦新行
|
||||
});
|
||||
};
|
||||
|
||||
const sq_addDefaultOptions = () => {
|
||||
const defaultOptions = [
|
||||
{
|
||||
optionContent: '非常满意',
|
||||
optionScore: 5,
|
||||
optionOrder: 0,
|
||||
isDefault: 'Y'
|
||||
},
|
||||
{
|
||||
optionContent: '满意',
|
||||
optionScore: 3,
|
||||
optionOrder: 1,
|
||||
isDefault: 'N'
|
||||
},
|
||||
{
|
||||
optionContent: '一般满意',
|
||||
optionScore: 1,
|
||||
optionOrder: 2,
|
||||
isDefault: 'N'
|
||||
},
|
||||
{
|
||||
optionContent: '不满意',
|
||||
optionScore: 0,
|
||||
optionOrder: 3,
|
||||
isDefault: 'N'
|
||||
}
|
||||
];
|
||||
|
||||
defaultOptions.forEach((option) => {
|
||||
sq_optionDialog.options.push({
|
||||
id: undefined,
|
||||
questionId: sq_optionDialog.question?.id,
|
||||
optionContent: option.optionContent,
|
||||
optionScore: option.optionScore,
|
||||
optionOrder: option.optionOrder,
|
||||
isDefault: option.isDefault,
|
||||
tenantId: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
deleted: '0'
|
||||
});
|
||||
});
|
||||
|
||||
sq_proxy?.$modal.msgSuccess('已添加默认选项');
|
||||
};
|
||||
|
||||
const sq_removeOption = (index: number) => {
|
||||
sq_optionDialog.options.splice(index, 1);
|
||||
};
|
||||
|
||||
const sq_saveOptions = async () => {
|
||||
if (!sq_optionDialog.question?.id) {
|
||||
sq_proxy?.$modal.msgError('题目ID不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
if (sq_optionDialog.saveLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
sq_optionDialog.saveLoading = true;
|
||||
sq_proxy?.$modal.loading('正在保存选项数据...');
|
||||
|
||||
try {
|
||||
const res = await listSatOption({
|
||||
questionId: sq_optionDialog.question.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
const existingOptions = res.rows || [];
|
||||
|
||||
const promises = [];
|
||||
|
||||
for (const option of sq_optionDialog.options) {
|
||||
if (!option.optionContent || option.optionContent.trim() === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const params = {
|
||||
id: option.id,
|
||||
questionId: sq_optionDialog.question.id,
|
||||
optionContent: option.optionContent,
|
||||
optionScore: option.optionScore || 0,
|
||||
optionOrder: option.optionOrder || 1,
|
||||
status: '0',
|
||||
isDefault: option.isDefault || 'N'
|
||||
};
|
||||
|
||||
if (option.id) {
|
||||
promises.push(updateSatOption(params));
|
||||
} else {
|
||||
promises.push(addSatOption(params));
|
||||
}
|
||||
}
|
||||
|
||||
const currentOptionIds = sq_optionDialog.options.filter((opt) => opt.id).map((opt) => opt.id);
|
||||
const optionsToDelete = existingOptions.filter((opt) => !currentOptionIds.includes(opt.id)).map((opt) => opt.id);
|
||||
|
||||
if (optionsToDelete.length > 0) {
|
||||
promises.push(delSatOption(optionsToDelete));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const validOptions = sq_optionDialog.options.filter((opt) => opt.optionContent && opt.optionContent.trim() !== '');
|
||||
let score = 0;
|
||||
|
||||
if (sq_optionDialog.question.questionType === '1') {
|
||||
score = Math.max(...validOptions.map((opt) => Number(opt.optionScore) || 0));
|
||||
} else if (sq_optionDialog.question.questionType === '2') {
|
||||
score = validOptions.reduce((sum, opt) => sum + (Number(opt.optionScore) || 0), 0);
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
await updateSatQuestion({
|
||||
id: sq_optionDialog.question.id,
|
||||
categoryId: sq_optionDialog.question.categoryId,
|
||||
questionType: sq_optionDialog.question.questionType,
|
||||
isRequired: sq_optionDialog.question.isRequired,
|
||||
sortOrder: sq_optionDialog.question.sortOrder,
|
||||
status: sq_optionDialog.question.status,
|
||||
remark: sq_optionDialog.question.remark,
|
||||
questionTitle: sq_optionDialog.question.questionTitle,
|
||||
questionContent: sq_optionDialog.question.questionContent,
|
||||
score: score
|
||||
});
|
||||
}
|
||||
|
||||
await sq_getList();
|
||||
sq_proxy?.$modal.msgSuccess('保存成功');
|
||||
|
||||
const currentQuestion = { ...sq_optionDialog.question };
|
||||
|
||||
const optRes = await listSatOption({
|
||||
questionId: currentQuestion.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
|
||||
sq_optionDialog.options = optRes.rows.map((item) => ({
|
||||
id: item.id,
|
||||
questionId: item.questionId,
|
||||
optionContent: item.optionContent,
|
||||
optionScore: item.optionScore,
|
||||
optionOrder: item.optionOrder,
|
||||
isDefault: item.isDefault || 'N',
|
||||
tenantId: item.tenantId,
|
||||
createBy: item.createBy,
|
||||
createTime: item.createTime,
|
||||
updateBy: item.updateBy,
|
||||
updateTime: item.updateTime,
|
||||
deleted: item.deleted
|
||||
}));
|
||||
|
||||
sq_optionDialog.options.sort((a, b) => a.optionOrder - b.optionOrder);
|
||||
} catch (error) {
|
||||
console.error('保存选项失败:', error);
|
||||
sq_proxy?.$modal.msgError('保存失败,请重试');
|
||||
} finally {
|
||||
sq_proxy?.$modal.closeLoading();
|
||||
sq_optionDialog.saveLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
sq_getList();
|
||||
sq_getCategoryList();
|
||||
sq_getAllCategoryList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 文字省略样式 */
|
||||
.text-ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 左边距类 */
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -70,9 +70,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satQuestion:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:satQuestion:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -212,46 +212,46 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<el-dialog :title="`管理题目【${optionDialog.question?.questionTitle}】的选项`" v-model="optionDialog.visible" width="850px" append-to-body>
|
||||
<div>
|
||||
<el-button type="primary" icon="Plus" @click="addOption" size="small" class="mb-2">新增选项</el-button>
|
||||
<el-button type="success" icon="Menu" @click="addDefaultOptions" size="small" class="mb-2 ml-2">默认选项</el-button>
|
||||
<el-table :data="optionDialog.options" border size="small">
|
||||
<el-table-column label="内容" prop="optionContent" width="295">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.optionContent" placeholder="请输入选项内容" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" prop="optionScore" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionScore" :min="0" :max="100" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="optionOrder" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionOrder" :min="0" :max="99" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" prop="isDefault" width="90">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.isDefault" active-value="Y" inactive-value="N" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button icon="Delete" type="danger" size="small" @click="removeOption(scope.$index)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="saveOptions" :loading="optionDialog.saveLoading">保存</el-button>
|
||||
<el-button @click="optionDialog.visible = false">关闭</el-button>
|
||||
<el-dialog :title="`管理题目【${optionDialog.question?.questionTitle}】的选项`" v-model="optionDialog.visible" width="850px" append-to-body>
|
||||
<div>
|
||||
<el-button type="primary" icon="Plus" @click="addOption" size="small" class="mb-2">新增选项</el-button>
|
||||
<el-button type="success" icon="Menu" @click="addDefaultOptions" size="small" class="mb-2 ml-2">默认选项</el-button>
|
||||
<el-table :data="optionDialog.options" border size="small">
|
||||
<el-table-column label="内容" prop="optionContent" width="295">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.optionContent" placeholder="请输入选项内容" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" prop="optionScore" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionScore" :min="0" :max="100" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="optionOrder" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionOrder" :min="0" :max="99" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" prop="isDefault" width="90">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.isDefault" active-value="Y" inactive-value="N" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button icon="Delete" type="danger" size="small" @click="removeOption(scope.$index)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="saveOptions" :loading="optionDialog.saveLoading">保存</el-button>
|
||||
<el-button @click="optionDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="SatQuestion" lang="ts">
|
||||
|
||||
@@ -214,9 +214,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['pm:repairOrder:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['pm:repairOrder:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -562,13 +562,15 @@
|
||||
<div class="upload-file-card">
|
||||
<!-- 缩略图区域 -->
|
||||
<div class="upload-file-thumb">
|
||||
<el-image
|
||||
<!-- {{ file.url }} -->
|
||||
<image-preview v-if="isImageFile(file) && file.url" :src="file.url" class="upload-thumb-image" :isOssId="true" />
|
||||
<!-- <el-image
|
||||
v-if="isImageFile(file) && file.url"
|
||||
:src="file.url"
|
||||
fit="cover"
|
||||
:preview-src-list="[file.url]"
|
||||
class="upload-thumb-image"
|
||||
/>
|
||||
/> -->
|
||||
<div v-else class="upload-file-placeholder">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
||||
@@ -822,14 +824,15 @@
|
||||
<div v-for="attachment in currentAttachments" :key="attachment.id" class="attachment-card">
|
||||
<!-- 附件缩略图 -->
|
||||
<div class="attachment-thumbnail-wrapper">
|
||||
<el-image
|
||||
<!-- <el-image
|
||||
v-if="attachment.fileType === 'image'"
|
||||
:src="attachment.filePath"
|
||||
:preview-src-list="getImagePreviewList()"
|
||||
fit="cover"
|
||||
class="attachment-image"
|
||||
lazy
|
||||
/>
|
||||
/> -->
|
||||
<image-preview v-if="attachment.fileType === 'image'" :src="attachment.filePath" class="attachment-image" :isOssId="true" />
|
||||
<div v-else class="attachment-file-icon">
|
||||
<el-icon :size="28">
|
||||
<Document v-if="attachment.fileType === 'document'" />
|
||||
@@ -857,14 +860,14 @@
|
||||
<!-- 附件操作 -->
|
||||
<div class="attachment-card-actions">
|
||||
<el-button-group size="small">
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
type="primary"
|
||||
@click="previewAttachment(attachment)"
|
||||
:disabled="!canPreview(attachment)"
|
||||
:title="canPreview(attachment) ? '预览' : '不支持预览'"
|
||||
>
|
||||
<el-icon><View /></el-icon>
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
<el-button type="success" @click="downloadAttachment(attachment)" title="下载">
|
||||
<el-icon><Download /></el-icon>
|
||||
</el-button>
|
||||
@@ -1230,6 +1233,7 @@ import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
|
||||
import { getToken } from '@/utils/auth'; // 修复:添加getToken导入
|
||||
import { vLoading, ElLoading } from 'element-plus';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
@@ -1271,7 +1275,7 @@ const workerOptions = ref<Array<{ value: string | number; label: string; userNam
|
||||
|
||||
// 附件相关数据
|
||||
const attachmentList = ref<any[]>([]);
|
||||
const uploadAction = ref('/dev-api/hq/pm/attachment/upload'); // 修复:使用正确的API路径
|
||||
const uploadAction = ref(import.meta.env.VITE_APP_BASE_API + '/hq/pm/attachment/upload'); // 修复:使用正确的API路径
|
||||
const uploadHeaders = ref({
|
||||
'Authorization': `Bearer ${getToken() || ''}`,
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
@@ -1826,7 +1830,10 @@ const submitForm = () => {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
let orderId: string | number;
|
||||
|
||||
// todo form.attachementCount的数量等于附件栏中附件的数量
|
||||
console.log('form.value', form.value);
|
||||
console.log('attachmentList 长度:', attachmentList.value.length);
|
||||
form.value.attachmentCount=attachmentList.value.length
|
||||
if (form.value.id) {
|
||||
await updateRepairOrder(form.value);
|
||||
orderId = form.value.id;
|
||||
@@ -2330,7 +2337,9 @@ const handleUploadSuccess = (response: any, file: any) => {
|
||||
|
||||
if (response.code === 200) {
|
||||
proxy?.$modal.msgSuccess(`文件 ${file.name} 上传成功`);
|
||||
|
||||
// todo
|
||||
console.log('orderDetail', orderDetail.value);
|
||||
// orderDetail.value.attachmentCount++;
|
||||
// 立即记录附件数据
|
||||
const attachmentData = response.data;
|
||||
if (attachmentData && attachmentData.id) {
|
||||
@@ -2415,12 +2424,28 @@ const handleDownloadAttachment = async (file: any) => {
|
||||
target: document.body
|
||||
});
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// const response = await fetch(downloadUrl);
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// }
|
||||
// 通过fetch获取文件内容,强制下载
|
||||
// const response = await req('/resource/oss/download/' + attachment.filePath);
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + downloadUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
// const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
@@ -2713,12 +2738,23 @@ const downloadAttachment = async (attachment: any) => {
|
||||
});
|
||||
|
||||
// 通过fetch获取文件内容,强制下载
|
||||
const response = await fetch(attachment.filePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// const response = await req('/resource/oss/download/' + attachment.filePath);
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + attachment.filePath,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
// const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// 创建下载链接
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:venue:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:venue:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -97,9 +97,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:reservation:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:reservation:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -93,10 +93,10 @@
|
||||
<template #icon><Search /></template>
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="exportReport">
|
||||
<!-- <el-button @click="exportReport">
|
||||
<template #icon><Download /></template>
|
||||
导出报表
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="mode === 'add' ? '新增' + getTypeLabel(type) : '编辑' + getTypeLabel(type)"
|
||||
v-model="visible"
|
||||
width="500px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="form.sex">
|
||||
<el-radio :label="0">男</el-radio>
|
||||
<el-radio :label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watchEffect, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'school'
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit']);
|
||||
|
||||
// 计算属性:对话框可见性
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
// 表单和校验
|
||||
const formRef = ref(null);
|
||||
const form = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
phone: '',
|
||||
sex: 0,
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
// 监听传入的admin数据,更新表单
|
||||
watchEffect(() => {
|
||||
if (props.admin) {
|
||||
form.id = props.admin.id;
|
||||
form.name = props.admin.name;
|
||||
form.phone = props.admin.phone;
|
||||
form.sex = props.admin.sex;
|
||||
form.remark = props.admin.remark;
|
||||
}
|
||||
});
|
||||
|
||||
// 根据类型获取标签文本
|
||||
function getTypeLabel(type) {
|
||||
switch (type) {
|
||||
case 'school':
|
||||
return '全校管理员';
|
||||
case 'custom':
|
||||
return '自定义管理员';
|
||||
case 'teacher':
|
||||
return '班主任';
|
||||
default:
|
||||
return '管理员';
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
function submitForm() {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
// 这里模拟保存
|
||||
emit('submit', {
|
||||
id: form.id,
|
||||
name: form.name,
|
||||
phone: form.phone,
|
||||
sex: form.sex,
|
||||
remark: form.remark,
|
||||
scope: props.admin?.scope || ''
|
||||
});
|
||||
|
||||
ElMessage.success('保存成功');
|
||||
visible.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 取消
|
||||
function cancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({
|
||||
submitForm,
|
||||
cancel
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -1,165 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :title="getScopeDialogTitle(type)" v-model="visible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item :label="getScopeName(type)">
|
||||
<div v-if="type === 'custom'">
|
||||
<el-checkbox-group v-model="form.selectedScopes">
|
||||
<el-checkbox v-for="item in scopeOptions.custom" :key="item.id" :label="item.id">
|
||||
{{ item.name }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<div v-else-if="type === 'teacher'">
|
||||
<el-checkbox-group v-model="form.selectedScopes">
|
||||
<el-checkbox v-for="item in scopeOptions.teacher" :key="item.id" :label="item.id">
|
||||
{{ item.name }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'custom'
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit']);
|
||||
|
||||
// 计算属性:对话框可见性
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
// 范围选项(模拟数据)
|
||||
const scopeOptions = {
|
||||
custom: [
|
||||
{ id: 1, name: '高一年级' },
|
||||
{ id: 2, name: '高二年级' },
|
||||
{ id: 3, name: '高三年级' },
|
||||
{ id: 4, name: '初一年级' },
|
||||
{ id: 5, name: '初二年级' },
|
||||
{ id: 6, name: '初三年级' }
|
||||
],
|
||||
teacher: [
|
||||
{ id: 101, name: '高一(1)班' },
|
||||
{ id: 102, name: '高一(2)班' },
|
||||
{ id: 103, name: '高一(3)班' },
|
||||
{ id: 104, name: '高二(1)班' },
|
||||
{ id: 105, name: '高二(2)班' },
|
||||
{ id: 106, name: '高三(1)班' },
|
||||
{ id: 107, name: '高三(2)班' }
|
||||
]
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
selectedScopes: [] as number[]
|
||||
});
|
||||
|
||||
// 获取范围对话框标题
|
||||
function getScopeDialogTitle(type: string) {
|
||||
const name = props.admin?.name || '';
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return `为【${name}】分配管理范围`;
|
||||
case 'teacher':
|
||||
return `为【${name}】分配班级`;
|
||||
default:
|
||||
return `为【${name}】分配范围`;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取范围名称
|
||||
function getScopeName(type: string) {
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return '管理范围';
|
||||
case 'teacher':
|
||||
return '所管理班级';
|
||||
default:
|
||||
return '管理范围';
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
function submitForm() {
|
||||
// 组装要提交的数据
|
||||
const selectedIds = form.selectedScopes;
|
||||
let scopeName = '';
|
||||
|
||||
// 根据类型获取选中的名称
|
||||
if (type.value === 'custom') {
|
||||
scopeName = scopeOptions.custom
|
||||
.filter((item) => selectedIds.includes(item.id))
|
||||
.map((item) => item.name)
|
||||
.join(', ');
|
||||
} else if (type.value === 'teacher') {
|
||||
scopeName = scopeOptions.teacher
|
||||
.filter((item) => selectedIds.includes(item.id))
|
||||
.map((item) => item.name)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
// 提交
|
||||
emit('submit', {
|
||||
sourceIds: selectedIds,
|
||||
scopeName: scopeName
|
||||
});
|
||||
|
||||
ElMessage.success('分配成功');
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 取消
|
||||
function cancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 从props获取类型的响应式引用
|
||||
const type = computed(() => props.type);
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({
|
||||
submitForm,
|
||||
cancel
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.el-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.el-checkbox {
|
||||
margin-right: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,713 +0,0 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<el-row :gutter="20">
|
||||
<!-- 部门树 -->
|
||||
<el-col :lg="4" :xs="24" style="">
|
||||
<el-card shadow="hover">
|
||||
<el-input v-model="deptName" placeholder="请输入院系名称" prefix-icon="Search" clearable />
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="mt-2"
|
||||
node-key="id"
|
||||
:data="deptOptions"
|
||||
:props="{ label: 'label', children: 'children' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="20" :xs="24">
|
||||
<transition enter-active-class="animate__animated animate__fadeIn" leave-active-class="animate__animated animate__fadeOut">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" inline>
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="学生状态" clearable>
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button :disabled="single" icon="Edit" @click="handleUpdate()"> 修改 </el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button :disabled="multiple" icon="Delete" @click="handleDelete()"> 删除 </el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-dropdown class="mt-[1px]">
|
||||
<el-button>
|
||||
更多
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
|
||||
<el-dropdown-item icon="Top" @click="handleImport">导入数据</el-dropdown-item>
|
||||
<el-dropdown-item icon="Download" @click="handleExport">导出数据</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="studentList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column v-if="columns[0].visible" key="studentId" label="学生编号" align="center" prop="studentId" />
|
||||
<el-table-column v-if="columns[1].visible" key="studentNo" label="学号" align="center" prop="studentNo" :show-overflow-tooltip="true" />
|
||||
<el-table-column
|
||||
v-if="columns[2].visible"
|
||||
key="studentName"
|
||||
label="姓名"
|
||||
align="center"
|
||||
prop="studentName"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column v-if="columns[3].visible" key="deptName" label="院系" align="center" prop="deptName" :show-overflow-tooltip="true" />
|
||||
<el-table-column v-if="columns[4].visible" key="majorName" label="专业" align="center" prop="majorName" :show-overflow-tooltip="true" />
|
||||
<el-table-column v-if="columns[5].visible" key="className" label="班级" align="center" prop="className" :show-overflow-tooltip="true" />
|
||||
<el-table-column v-if="columns[6].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
|
||||
<el-table-column v-if="columns[7].visible" key="status" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column v-if="columns[8].visible" label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.createTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" fixed="right" width="240" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="重置密码" placement="top">
|
||||
<el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="详细信息" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 添加或修改学生配置对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
|
||||
<el-form ref="studentFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="form.studentName" placeholder="请输入学生姓名" maxlength="30" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="昵称" prop="nickName">
|
||||
<el-input v-model="form.nickName" placeholder="请输入昵称" maxlength="30" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="归属院系" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="form.deptId"
|
||||
:data="deptOptions"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择归属院系"
|
||||
check-strictly
|
||||
@change="handleDeptChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="majorId">
|
||||
<el-select v-model="form.majorId" placeholder="请选择专业" @change="handleMajorChange">
|
||||
<el-option v-for="item in majorOptions" :key="item.majorId" :label="item.majorName" :value="item.majorId"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="classId">
|
||||
<el-select v-model="form.classId" placeholder="请选择班级">
|
||||
<el-option v-for="item in classOptions" :key="item.classId" :label="item.className" :value="item.classId"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="form.studentId == undefined" label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="请输入学号" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="form.studentId == undefined" label="密码" prop="password">
|
||||
<el-input v-model="form.password" placeholder="请输入密码" type="password" maxlength="20" show-password />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生性别">
|
||||
<el-select v-model="form.sex" placeholder="请选择">
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="form.address" type="textarea" placeholder="请输入地址"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生详情对话框 -->
|
||||
<el-dialog v-model="detailDialog.visible" :title="detailDialog.title" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学生姓名">{{ detailForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学号">{{ detailForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ detailForm.deptName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ detailForm.majorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ detailForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
{{ detailForm.sex === '0' ? '男' : detailForm.sex === '1' ? '女' : '未知' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ detailForm.phonenumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ detailForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detailForm.idCard }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="detailForm.status === '0' ? 'success' : 'danger'">
|
||||
{{ detailForm.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地址" :span="2">{{ detailForm.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailForm.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生导入对话框 -->
|
||||
<el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<i-ep-upload-filled />
|
||||
</el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="text-center el-upload__tip">
|
||||
<div class="el-upload__tip"><el-checkbox v-model="upload.updateSupport" />是否更新已经存在的学生数据</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import api from '@/api/studentMsg/index';
|
||||
import { StudentForm, StudentQuery, StudentVO } from '@/api/studentMsg/types';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getCurrentInstance, ref, reactive, toRefs, watchEffect, onMounted } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { to } from 'await-to-js';
|
||||
import type { UploadFile, UploadInstance } from 'element-plus';
|
||||
import type { FormInstance, TreeInstance } from 'element-plus';
|
||||
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const sys_normal_disable = ref([
|
||||
{ value: '0', label: '正常' },
|
||||
{ value: '1', label: '停用' }
|
||||
]);
|
||||
const sys_user_sex = ref([
|
||||
{ value: '0', label: '男' },
|
||||
{ value: '1', label: '女' },
|
||||
{ value: '2', label: '未知' }
|
||||
]);
|
||||
|
||||
const studentList = ref<StudentVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<number | string>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const dateRange = ref<[string, string]>(['', '']);
|
||||
const deptName = ref('');
|
||||
const deptOptions = ref([]);
|
||||
const majorOptions = ref([]);
|
||||
const classOptions = ref([]);
|
||||
|
||||
// 列显隐信息
|
||||
const columns = ref([
|
||||
{ key: 0, label: `学生编号`, visible: false },
|
||||
{ key: 1, label: `学号`, visible: true },
|
||||
{ key: 2, label: `姓名`, visible: true },
|
||||
{ key: 3, label: `院系`, visible: true },
|
||||
{ key: 4, label: `专业`, visible: true },
|
||||
{ key: 5, label: `班级`, visible: true },
|
||||
{ key: 6, label: `手机号码`, visible: true },
|
||||
{ key: 7, label: `状态`, visible: true },
|
||||
{ key: 8, label: `创建时间`, visible: true }
|
||||
]);
|
||||
|
||||
const deptTreeRef = ref<TreeInstance>();
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const studentFormRef = ref<FormInstance>();
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
|
||||
const initPassword = ref('123456');
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const detailDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生详情'
|
||||
});
|
||||
|
||||
const detailForm = reactive<StudentVO>({
|
||||
studentId: '',
|
||||
studentNo: '',
|
||||
studentName: ''
|
||||
});
|
||||
|
||||
// 导入参数
|
||||
const upload = reactive({
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否禁用上传
|
||||
isUploading: false,
|
||||
// 是否更新已经存在的数据
|
||||
updateSupport: false,
|
||||
// 设置上传的请求头部
|
||||
headers: { Authorization: 'Bearer ' },
|
||||
// 上传的地址
|
||||
url: '/api/studentMsg/student/importData'
|
||||
});
|
||||
|
||||
const initFormData: StudentForm = {
|
||||
studentId: undefined,
|
||||
deptId: undefined,
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
nickName: '',
|
||||
password: '',
|
||||
phonenumber: '',
|
||||
email: '',
|
||||
idCard: '',
|
||||
sex: '0',
|
||||
status: '0',
|
||||
majorId: undefined,
|
||||
classId: undefined,
|
||||
address: '',
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const form = reactive<StudentForm>({
|
||||
...initFormData
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
studentName: [{ required: true, message: '学生姓名不能为空', trigger: 'blur' }],
|
||||
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
deptId: [{ required: true, message: '归属院系不能为空', trigger: 'blur' }],
|
||||
majorId: [{ required: true, message: '专业不能为空', trigger: 'blur' }],
|
||||
classId: [{ required: true, message: '班级不能为空', trigger: 'blur' }],
|
||||
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
|
||||
phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
|
||||
idCard: [{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号码', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
const queryParams = reactive<StudentQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
phonenumber: '',
|
||||
status: '',
|
||||
deptId: undefined
|
||||
});
|
||||
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/** 根据名称筛选部门树 */
|
||||
watchEffect(
|
||||
() => {
|
||||
deptTreeRef.value?.filter(deptName.value);
|
||||
},
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
/** 查询学生列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await api.listStudent(queryParams);
|
||||
loading.value = false;
|
||||
studentList.value = res.data.data.rows;
|
||||
total.value = res.data.data.total;
|
||||
};
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
const res = await api.XgDeptTreeSelect(101);
|
||||
// XgDeptTreeSelect返回的数据结构与deptTreeSelect不同,需要进行适当处理
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
deptOptions.value = res.data;
|
||||
} else {
|
||||
console.warn('部门树数据格式不符合预期');
|
||||
// 回退到默认处理
|
||||
const backup = await api.deptTreeSelect();
|
||||
deptOptions.value = backup.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
// 如果API调用失败,可以使用默认的deptTreeSelect作为备选
|
||||
const backup = await api.deptTreeSelect();
|
||||
deptOptions.value = backup.data.data;
|
||||
}
|
||||
};
|
||||
|
||||
/** 节点单击事件 */
|
||||
const handleNodeClick = (data: any) => {
|
||||
queryParams.deptId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
dateRange.value = ['', ''];
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.deptId = undefined;
|
||||
deptTreeRef.value?.setCurrentKey(undefined);
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StudentVO) => {
|
||||
const studentIds = row?.studentId || ids.value;
|
||||
const [err] = await to(ElMessageBox.confirm('是否确认删除学生编号为"' + studentIds + '"的数据项?', '提示'));
|
||||
if (!err) {
|
||||
await api.delStudent(studentIds);
|
||||
await getList();
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
};
|
||||
|
||||
/** 学生状态修改 */
|
||||
const handleStatusChange = async (row: StudentVO) => {
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await ElMessageBox.confirm('确认要"' + text + '""' + row.studentName + '"学生吗?', '提示');
|
||||
await api.changeStudentStatus(row.studentId, row.status);
|
||||
ElMessage.success(text + '成功');
|
||||
} catch (err) {
|
||||
row.status = row.status === '0' ? '1' : '0';
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详情按钮操作 */
|
||||
const handleDetail = async (row: StudentVO) => {
|
||||
Object.assign(detailForm, row);
|
||||
detailDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 重置密码按钮操作 */
|
||||
const handleResetPwd = async (row: StudentVO) => {
|
||||
const [err, res] = await to(
|
||||
ElMessageBox.prompt('请输入"' + row.studentName + '"的新密码', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
closeOnClickModal: false,
|
||||
inputPattern: /^.{6,20}$/,
|
||||
inputErrorMessage: '密码长度必须介于 6 和 20 之间'
|
||||
})
|
||||
);
|
||||
if (!err && res) {
|
||||
// 实际项目中需要调用重置密码API
|
||||
ElMessage.success('修改成功,新密码是:' + res.value);
|
||||
}
|
||||
};
|
||||
|
||||
/** 选择条数 */
|
||||
const handleSelectionChange = (selection: StudentVO[]) => {
|
||||
ids.value = selection.map((item) => item.studentId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImport = () => {
|
||||
upload.title = '学生导入';
|
||||
upload.open = true;
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
// 实际项目中需要调用导出API
|
||||
ElMessage.success('导出成功');
|
||||
};
|
||||
|
||||
/** 下载模板操作 */
|
||||
const importTemplate = () => {
|
||||
// 实际项目中需要调用下载模板API
|
||||
ElMessage.success('下载模板成功');
|
||||
};
|
||||
|
||||
/**文件上传中处理 */
|
||||
const handleFileUploadProgress = () => {
|
||||
upload.isUploading = true;
|
||||
};
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response: any) => {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
|
||||
dangerouslyUseHTMLString: true
|
||||
});
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 提交上传文件 */
|
||||
const submitFileForm = () => {
|
||||
uploadRef.value?.submit();
|
||||
};
|
||||
|
||||
/** 重置操作表单 */
|
||||
const reset = () => {
|
||||
Object.assign(form, initFormData);
|
||||
studentFormRef.value?.resetFields();
|
||||
majorOptions.value = [];
|
||||
classOptions.value = [];
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = async () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '新增学生';
|
||||
form.password = initPassword.value;
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StudentForm) => {
|
||||
reset();
|
||||
const studentId = row?.studentId || ids.value[0];
|
||||
const { data } = await api.getStudent(studentId);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改学生';
|
||||
Object.assign(form, data.data.student);
|
||||
majorOptions.value = data.data.majors;
|
||||
classOptions.value = data.data.classes;
|
||||
};
|
||||
|
||||
/** 院系变更事件 */
|
||||
const handleDeptChange = (deptId: number) => {
|
||||
form.majorId = undefined;
|
||||
form.classId = undefined;
|
||||
majorOptions.value = [];
|
||||
classOptions.value = [];
|
||||
// 根据院系ID过滤专业选项
|
||||
const majors = api.getStudent().then((res) => {
|
||||
return res.data.data.majors.filter((item: any) => item.deptId === deptId);
|
||||
});
|
||||
majors.then((data) => {
|
||||
majorOptions.value = data;
|
||||
});
|
||||
};
|
||||
|
||||
/** 专业变更事件 */
|
||||
const handleMajorChange = (majorId: number) => {
|
||||
form.classId = undefined;
|
||||
classOptions.value = [];
|
||||
// 根据专业ID过滤班级选项
|
||||
const classes = api.getStudent().then((res) => {
|
||||
return res.data.data.classes.filter((item: any) => item.majorId === majorId);
|
||||
});
|
||||
classes.then((data) => {
|
||||
classOptions.value = data;
|
||||
});
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
studentFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
if (form.studentId !== undefined) {
|
||||
await api.updateStudent(form);
|
||||
} else {
|
||||
await api.addStudent(form);
|
||||
}
|
||||
ElMessage.success('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭学生弹窗
|
||||
*/
|
||||
const closeDialog = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDeptTree(); // 初始化部门数据
|
||||
getList(); // 初始化列表数据
|
||||
});
|
||||
</script>
|
||||
@@ -63,7 +63,13 @@
|
||||
<el-table-column label="显示顺序" prop="roleSort" width="100" />
|
||||
<el-table-column label="状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
:disabled="isSystemRoleInTable(scope.row)"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime">
|
||||
@@ -77,14 +83,36 @@
|
||||
<el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
||||
<el-tooltip
|
||||
v-if="scope.row.roleId !== 1"
|
||||
:content="isSystemRoleInTable(scope.row) ? '系统内置角色不可删除,请前往【学生信息管理】->【班级干部职务配置】界面进行操作' : '删除'"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
v-hasPermi="['system:role:remove']"
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
:disabled="isSystemRoleInTable(scope.row)"
|
||||
@click="handleDelete(scope.row)"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
|
||||
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
|
||||
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
|
||||
<el-tooltip
|
||||
v-if="scope.row.roleId !== 1"
|
||||
:content="isSystemRoleInTable(scope.row) ? '系统内置角色不可分配用户,请前往学生信息管理-班级干部信息界面进行操作' : '分配用户'"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
v-hasPermi="['system:role:edit']"
|
||||
link
|
||||
type="primary"
|
||||
icon="User"
|
||||
:disabled="isSystemRoleInTable(scope.row)"
|
||||
@click="handleAuthUser(scope.row)"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -100,9 +128,18 @@
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
||||
<el-alert
|
||||
v-if="isSystemRole"
|
||||
title="系统角色提示"
|
||||
type="warning"
|
||||
description="此角色为系统内置角色,角色名称、权限字符、角色顺序、状态等基础信息不可修改,如需修改请前往【学生信息管理】->【班级干部职务配置】"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
/>
|
||||
<el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
|
||||
<el-input v-model="form.roleName" placeholder="请输入角色名称" :disabled="isSystemRole" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="roleKey">
|
||||
<template #label>
|
||||
@@ -113,13 +150,13 @@
|
||||
权限字符
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
|
||||
<el-input v-model="form.roleKey" placeholder="请输入权限字符" :disabled="isSystemRole" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色顺序" prop="roleSort">
|
||||
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
|
||||
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" :disabled="isSystemRole" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio-group v-model="form.status" :disabled="isSystemRole">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
@@ -192,6 +229,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Role" lang="ts">
|
||||
import { ref, reactive, computed, toRefs, nextTick, onMounted, getCurrentInstance, ComponentInternalInstance } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from '@/api/system/role';
|
||||
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
|
||||
import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
|
||||
@@ -269,6 +308,25 @@ const dialog = reactive<DialogOption>({
|
||||
title: ''
|
||||
});
|
||||
|
||||
/** 判断是否为系统角色(权限字符以XT-BJZW-开头) */
|
||||
const isSystemRole = computed(() => {
|
||||
if (form.value.roleKey && form.value.roleKey.trim() !== '') {
|
||||
return form.value.roleKey.startsWith('XT-BJZW-');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/** 判断表格中的角色是否为系统角色 */
|
||||
const isSystemRoleInTable = (row: RoleVO) => {
|
||||
if (row.roleKey && row.roleKey.trim() !== '') {
|
||||
return row.roleKey.startsWith('XT-BJZW-');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// return row.roleKey && row.roleKey.trim() !== '' && row.roleKey.startsWith('XT-BJZW-');
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询角色列表
|
||||
*/
|
||||
@@ -297,6 +355,12 @@ const resetQuery = () => {
|
||||
};
|
||||
/**删除按钮操作 */
|
||||
const handleDelete = async (row?: RoleVO) => {
|
||||
// 检查是否为系统角色
|
||||
if (row && isSystemRoleInTable(row)) {
|
||||
proxy?.$modal.msgError('系统内置角色不可删除,请前往学生信息管理-学生干部职务配置界面进行操作');
|
||||
return;
|
||||
}
|
||||
|
||||
const roleids = row?.roleId || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
|
||||
await delRole(roleids);
|
||||
@@ -323,6 +387,14 @@ const handleSelectionChange = (selection: RoleVO[]) => {
|
||||
|
||||
/** 角色状态修改 */
|
||||
const handleStatusChange = async (row: RoleVO) => {
|
||||
// 检查是否为系统角色
|
||||
if (isSystemRoleInTable(row)) {
|
||||
proxy?.$modal.msgError('系统内置角色状态不可修改,请前往学生信息管理-班级干部职务配置界面进行操作');
|
||||
// 恢复原状态
|
||||
row.status = row.status === '0' ? '1' : '0';
|
||||
return;
|
||||
}
|
||||
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
|
||||
@@ -335,6 +407,12 @@ const handleStatusChange = async (row: RoleVO) => {
|
||||
|
||||
/** 分配用户 */
|
||||
const handleAuthUser = (row: RoleVO) => {
|
||||
// 检查是否为系统角色
|
||||
if (isSystemRoleInTable(row)) {
|
||||
proxy?.$modal.msgError('系统内置角色不可分配用户,请前往学生信息管理-学生干部职务配置界面进行操作');
|
||||
return;
|
||||
}
|
||||
|
||||
router.push('/system/role-auth/user/' + row.roleId);
|
||||
};
|
||||
|
||||
@@ -362,12 +440,17 @@ const reset = () => {
|
||||
deptExpand.value = true;
|
||||
deptNodeAll.value = false;
|
||||
form.value = { ...initForm };
|
||||
// 确保roleKey被明确重置为空字符串
|
||||
form.value.roleKey = '';
|
||||
roleFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 添加角色 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
// 确保roleKey被正确重置为空字符串
|
||||
form.value.roleKey = '';
|
||||
console.log('新增角色 - roleKey:', `"${form.value.roleKey}"`, 'isSystemRole:', isSystemRole.value);
|
||||
getMenuTreeselect();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加角色';
|
||||
@@ -379,6 +462,7 @@ const handleUpdate = async (row?: RoleVO) => {
|
||||
const { data } = await getRole(roleId);
|
||||
Object.assign(form.value, data);
|
||||
form.value.roleSort = Number(form.value.roleSort);
|
||||
console.log('修改角色 - roleKey:', `"${form.value.roleKey}"`, 'isSystemRole:', isSystemRole.value);
|
||||
const res = await getRoleMenuTreeselect(roleId);
|
||||
dialog.title = '修改角色';
|
||||
dialog.visible = true;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<el-row :gutter="20">
|
||||
<div style="display: flex; gap: 16px">
|
||||
<!-- 部门树 -->
|
||||
<el-col :lg="4" :xs="24" style="">
|
||||
<div style="width: 360px">
|
||||
<el-card shadow="hover">
|
||||
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="mt-2"
|
||||
class="mt-2 dept-tree"
|
||||
node-key="id"
|
||||
:data="deptOptions"
|
||||
:props="{ label: 'label', children: 'children' } as any"
|
||||
:props="{ label: 'label', children: 'children' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
@@ -18,8 +18,8 @@
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="20" :xs="24">
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0; /* 防止内容撑开容器 */">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
@@ -63,13 +63,17 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
||||
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增(教职工)</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-has-permi="['system:user:edit']" :disabled="single" icon="Edit" @click="handleUpdate()"> 修改 </el-button>
|
||||
<el-button v-has-permi="['system:user:edit']" :disabled="single || isSelectedStudentUser" icon="Edit" @click="handleUpdate()">
|
||||
修改
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-has-permi="['system:user:remove']" :disabled="multiple" icon="Delete" @click="handleDelete()"> 删除 </el-button>
|
||||
<el-button v-has-permi="['system:user:remove']" :disabled="multiple || hasSelectedStudentUser" icon="Delete" @click="handleDelete()">
|
||||
删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-dropdown class="mt-[1px]">
|
||||
@@ -100,7 +104,13 @@
|
||||
<el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
|
||||
<el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
:disabled="scope.row.accountType === '2'"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -112,10 +122,10 @@
|
||||
|
||||
<el-table-column label="操作" fixed="right" width="280" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top">
|
||||
<el-tooltip v-if="scope.row.userId !== 1 && scope.row.accountType !== '2'" content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top">
|
||||
<el-tooltip v-if="scope.row.userId !== 1 && scope.row.accountType !== '2'" content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
@@ -126,6 +136,11 @@
|
||||
<el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
|
||||
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 学生用户显示提示信息 -->
|
||||
<el-tooltip v-if="scope.row.accountType === '2'" content="学生用户不允许操作" placement="top">
|
||||
<span class="text-gray-400">学生用户</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -138,8 +153,8 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加或修改用户配置对话框 -->
|
||||
<el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
|
||||
@@ -155,7 +170,7 @@
|
||||
<el-tree-select
|
||||
v-model="form.deptId"
|
||||
:data="enabledDeptOptions"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' } as any"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
check-strictly
|
||||
@@ -316,6 +331,20 @@ const enabledDeptOptions = ref<DeptTreeVO[]>([]);
|
||||
const initPassword = ref<string>('');
|
||||
const postOptions = ref<PostVO[]>([]);
|
||||
const roleOptions = ref<RoleVO[]>([]);
|
||||
|
||||
// 选中的用户相关计算属性
|
||||
const selectedUsers = ref<UserVO[]>([]);
|
||||
|
||||
// 计算属性:是否选中了学生用户
|
||||
const isSelectedStudentUser = computed(() => {
|
||||
return selectedUsers.value.length === 1 && selectedUsers.value[0].accountType === '2';
|
||||
});
|
||||
|
||||
// 计算属性:选中的用户中是否包含学生用户
|
||||
const hasSelectedStudentUser = computed(() => {
|
||||
return selectedUsers.value.some((user) => user.accountType === '2');
|
||||
});
|
||||
|
||||
/*** 用户导入参数 */
|
||||
const upload = reactive<ImportOption>({
|
||||
// 是否显示弹出层(用户导入)
|
||||
@@ -489,6 +518,19 @@ const resetQuery = () => {
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: UserVO) => {
|
||||
const userIds = row?.userId || ids.value;
|
||||
|
||||
// 检查是否是学生用户
|
||||
if (row && row.accountType === '2') {
|
||||
proxy?.$modal.msgError('学生用户不允许删除操作');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查批量删除时是否包含学生用户
|
||||
if (!row && hasSelectedStudentUser.value) {
|
||||
proxy?.$modal.msgError('选中的用户中包含学生用户,不允许删除操作');
|
||||
return;
|
||||
}
|
||||
|
||||
const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any);
|
||||
if (!err) {
|
||||
await api.delUser(userIds);
|
||||
@@ -499,6 +541,14 @@ const handleDelete = async (row?: UserVO) => {
|
||||
|
||||
/** 用户状态修改 */
|
||||
const handleStatusChange = async (row: UserVO) => {
|
||||
// 检查是否是学生用户
|
||||
if (row.accountType === '2') {
|
||||
// 恢复原状态
|
||||
row.status = row.status === '0' ? '1' : '0';
|
||||
proxy?.$modal.msgError('学生用户不允许修改状态');
|
||||
return;
|
||||
}
|
||||
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
|
||||
@@ -510,12 +560,24 @@ const handleStatusChange = async (row: UserVO) => {
|
||||
};
|
||||
/** 跳转角色分配 */
|
||||
const handleAuthRole = (row: UserVO) => {
|
||||
// 检查是否是学生用户
|
||||
// if (row.accountType === '2') {
|
||||
// proxy?.$modal.msgError('学生用户不允许分配角色操作');
|
||||
// return;
|
||||
// }
|
||||
|
||||
const userId = row.userId;
|
||||
router.push('/system/user-auth/role/' + userId);
|
||||
};
|
||||
|
||||
/** 重置密码按钮操作 */
|
||||
const handleResetPwd = async (row: UserVO) => {
|
||||
// 检查是否是学生用户
|
||||
// if (row.accountType === '2') {
|
||||
// proxy?.$modal.msgError('学生用户不允许重置密码操作');
|
||||
// return;
|
||||
// }
|
||||
|
||||
const [err, res] = await to(
|
||||
ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
@@ -538,6 +600,7 @@ const handleResetPwd = async (row: UserVO) => {
|
||||
|
||||
/** 选择条数 */
|
||||
const handleSelectionChange = (selection: UserVO[]) => {
|
||||
selectedUsers.value = selection;
|
||||
ids.value = selection.map((item) => item.userId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
@@ -607,6 +670,18 @@ const handleAdd = async () => {
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: UserForm) => {
|
||||
// 检查是否是学生用户
|
||||
if (row && (row as UserVO).accountType === '2') {
|
||||
proxy?.$modal.msgError('学生用户不允许修改操作');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查选中的用户是否是学生用户
|
||||
if (!row && isSelectedStudentUser.value) {
|
||||
proxy?.$modal.msgError('学生用户不允许修改操作');
|
||||
return;
|
||||
}
|
||||
|
||||
reset();
|
||||
const userId = row?.userId || ids.value[0];
|
||||
const { data } = await api.getUser(userId);
|
||||
@@ -664,3 +739,59 @@ async function handleDeptChange(value: number | string) {
|
||||
form.value.postIds = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 部门树组件样式 */
|
||||
.dept-tree {
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
width: 330px; /* 固定宽度,超出时显示滚动条 */
|
||||
min-width: 330px;
|
||||
}
|
||||
|
||||
/* 强制覆盖el-tree的默认样式 */
|
||||
.dept-tree :deep(.el-tree-node) {
|
||||
white-space: nowrap !important;
|
||||
width: 100% !important;
|
||||
min-width: 300px !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__content) {
|
||||
white-space: nowrap !important;
|
||||
width: 100% !important;
|
||||
min-width: 300px !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: unset !important; /* 移除省略号 */
|
||||
max-width: none !important;
|
||||
padding-right: 20px !important; /* 增加右侧内边距,确保hover效果完整显示 */
|
||||
}
|
||||
|
||||
/* 树节点hover效果优化 */
|
||||
.dept-tree :deep(.el-tree-node__content:hover) {
|
||||
background-color: var(--el-tree-node-hover-bg-color, #f5f7fa) !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__label) {
|
||||
white-space: nowrap !important;
|
||||
display: inline-block !important;
|
||||
flex-shrink: 0 !important;
|
||||
max-width: none !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: unset !important; /* 移除省略号效果 */
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__expand-icon) {
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保树容器内部元素都不被截断 */
|
||||
.dept-tree :deep(.el-tree-node__children) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node > .el-tree-node__children) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -266,6 +266,8 @@ const handleStartWorkFlow = async (data: LeaveForm) => {
|
||||
submitFormData.value.businessId = data.id;
|
||||
//流程变量
|
||||
taskVariables.value = {
|
||||
// hzdyjs-qj 使用的流程变量
|
||||
qjts: data.leaveDays,
|
||||
// leave2/6 使用的流程变量
|
||||
leaveDays: data.leaveDays,
|
||||
// leave4/5 使用的流程变量
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+6
-6
@@ -72,9 +72,9 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['sslxx:dormBuilding:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -173,10 +173,10 @@ import {
|
||||
updateDormBuilding,
|
||||
listCampusOptions,
|
||||
listAreaTreeData
|
||||
} from '@/api/xgss/sslxx/dormBuilding';
|
||||
import { DormBuildingVO, DormBuildingQuery, DormBuildingForm } from '@/api/xgss/sslxx/dormBuilding/types';
|
||||
import { DormCampusVO } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
import { DormAreaVO } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
} from '@/api/xg/xgss/sslxx/dormBuilding';
|
||||
import { DormBuildingVO, DormBuildingQuery, DormBuildingForm } from '@/api/xg/xgss/sslxx/dormBuilding/types';
|
||||
import { DormCampusVO } from '@/api/xg/xgss/xqxx/dormCampus/types';
|
||||
import { DormAreaVO } from '@/api/xg/xgss/ssqxx/dormArea/types';
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onMounted } from 'vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
+6
-6
@@ -50,9 +50,9 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ssqxx:dormArea:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -112,10 +112,10 @@
|
||||
</template>
|
||||
|
||||
<script setup name="DormArea" lang="ts">
|
||||
import { listDormArea, getDormArea, delDormArea, addDormArea, updateDormArea } from '@/api/xgss/ssqxx/dormArea';
|
||||
import { DormAreaVO, DormAreaQuery, DormAreaForm } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
import { listDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import type { DormCampusVO } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
import { listDormArea, getDormArea, delDormArea, addDormArea, updateDormArea } from '@/api/xg/xgss/ssqxx/dormArea';
|
||||
import { DormAreaVO, DormAreaQuery, DormAreaForm } from '@/api/xg/xgss/ssqxx/dormArea/types';
|
||||
import { listDormCampus } from '@/api/xg/xgss/xqxx/dormCampus';
|
||||
import type { DormCampusVO } from '@/api/xg/xgss/xqxx/dormCampus/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
+4
-4
@@ -35,9 +35,9 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xqxx:dormCampus:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -94,8 +94,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="DormCampus" lang="ts">
|
||||
import { listDormCampus, getDormCampus, delDormCampus, addDormCampus, updateDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import { DormCampusVO, DormCampusQuery, DormCampusForm } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
import { listDormCampus, getDormCampus, delDormCampus, addDormCampus, updateDormCampus } from '@/api/xg/xgss/xqxx/dormCampus';
|
||||
import { DormCampusVO, DormCampusQuery, DormCampusForm } from '@/api/xg/xgss/xqxx/dormCampus/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
+2
-2
@@ -38,8 +38,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormBedForm } from '@/api/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
|
||||
import { DormBedForm } from '@/api/xg/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xg/xgss/dormBed';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
+2
-2
@@ -50,8 +50,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xgss/dormRoom';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xg/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xg/xgss/dormRoom';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
@@ -213,12 +213,12 @@
|
||||
import { ref, computed, reactive, onMounted, nextTick, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { InfoFilled, Search } from '@element-plus/icons-vue';
|
||||
import { DormTreeNode, BedInfo, DormQuery, BedForm } from '@/api/xgss/xsssfjgl/types';
|
||||
import { DormRoomVO, DormRoomQuery } from '@/api/xgss/dormRoom/types';
|
||||
import { getDormTree } from '@/api/xgss/xsssfjgl';
|
||||
import { listDormBed, delDormBed, addDormBed } from '@/api/xgss/dormBed';
|
||||
import type { DormBedQuery } from '@/api/xgss/dormBed/types';
|
||||
import { listDormRoom, delDormRoom, addDormRoom } from '@/api/xgss/dormRoom';
|
||||
import { DormTreeNode, BedInfo, DormQuery, BedForm } from '@/api/xg/xgss/xsssfjgl/types';
|
||||
import { DormRoomVO, DormRoomQuery } from '@/api/xg/xgss/dormRoom/types';
|
||||
import { getDormTree } from '@/api/xg/xgss/xsssfjgl';
|
||||
import { listDormBed, delDormBed, addDormBed } from '@/api/xg/xgss/dormBed';
|
||||
import type { DormBedQuery } from '@/api/xg/xgss/dormBed/types';
|
||||
import { listDormRoom, delDormRoom, addDormRoom } from '@/api/xg/xgss/dormRoom';
|
||||
|
||||
// 引入子组件
|
||||
import RoomDialog from './components/RoomDialog.vue';
|
||||
+2
-2
@@ -38,8 +38,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormBedForm } from '@/api/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
|
||||
import { DormBedForm } from '@/api/xg/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xg/xgss/dormBed';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
+2
-2
@@ -50,8 +50,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xgss/dormRoom';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xg/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xg/xgss/dormRoom';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
+5
-5
@@ -256,11 +256,11 @@
|
||||
<script setup name="DormRoomTreeManagement" lang="ts">
|
||||
import { ref, computed, reactive, onMounted, nextTick, watch, getCurrentInstance } from 'vue';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import type { DormTreeNode, BedInfo } from '@/api/xgss/xsssfjgl/types';
|
||||
import type { DormRoomVO } from '@/api/xgss/dormRoom/types';
|
||||
import { getDormTree } from '@/api/xgss/xsssfjgl';
|
||||
import { listDormBed, delDormBed, getDormBed } from '@/api/xgss/dormBed';
|
||||
import { listDormRoom, delDormRoom } from '@/api/xgss/dormRoom';
|
||||
import type { DormTreeNode, BedInfo } from '@/api/xg/xgss/xsssfjgl/types';
|
||||
import type { DormRoomVO } from '@/api/xg/xgss/dormRoom/types';
|
||||
import { getDormTree } from '@/api/xg/xgss/xsssfjgl';
|
||||
import { listDormBed, delDormBed, getDormBed } from '@/api/xg/xgss/dormBed';
|
||||
import { listDormRoom, delDormRoom } from '@/api/xg/xgss/dormRoom';
|
||||
|
||||
// 引入子组件
|
||||
import RoomDialog from './components/RoomDialog.vue';
|
||||
+2
-2
@@ -47,8 +47,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { getTeacherList } from '@/api/xgss/xsssgly/dormManager';
|
||||
import { addDormManager, updateDormManager } from '@/api/xgss/ssglyxx/dormManager/index';
|
||||
import { getTeacherList } from '@/api/xg/xgss/xsssgly/dormManager';
|
||||
import { addDormManager, updateDormManager } from '@/api/xg/xgss/ssglyxx/dormManager/index';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
File renamed without changes.
+5
-5
@@ -82,11 +82,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { listDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import { getAreaTree } from '@/api/xgss/ssqxx/dormArea';
|
||||
import { getBuildingTree } from '@/api/xgss/sslxx/dormBuilding';
|
||||
import type { TreeSelectVo } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
import type { BuildingTreeSelectVo } from '@/api/xgss/sslxx/dormBuilding/types';
|
||||
import { listDormCampus } from '@/api/xg/xgss/xqxx/dormCampus';
|
||||
import { getAreaTree } from '@/api/xg/xgss/ssqxx/dormArea';
|
||||
import { getBuildingTree } from '@/api/xg/xgss/sslxx/dormBuilding';
|
||||
import type { TreeSelectVo } from '@/api/xg/xgss/ssqxx/dormArea/types';
|
||||
import type { BuildingTreeSelectVo } from '@/api/xg/xgss/sslxx/dormBuilding/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ScopeDialog',
|
||||
File renamed without changes.
@@ -0,0 +1,612 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['XgStuColBatch:stuColBatch:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col>
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['XgStuColBatch:stuColBatch:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="stuColBatchList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="批次ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="批次名称" align="center" prop="batchName" />
|
||||
<el-table-column label="批次描述" align="center" prop="description" />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收集类型" align="center" prop="collectionType">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ scope.row.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详细" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
plain
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
plain
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="收集范围" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
plain
|
||||
icon="Setting"
|
||||
@click="handleScope(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:scope']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
plain
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改学生信息收集批次对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="stuColBatchFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="form.batchName" placeholder="请输入批次名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.startTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择开始时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.endTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择结束时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收集类型" prop="collectionType">
|
||||
<el-select v-model="form.collectionType" placeholder="请选择收集类型">
|
||||
<el-option label="新学期收集" value="1" />
|
||||
<el-option label="其他" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 收集范围设置对话框 -->
|
||||
<el-dialog title="设置收集范围" v-model="scopeDialog.visible" width="500px" append-to-body @close="handleScopeDialogClose">
|
||||
<div class="scope-content">
|
||||
<el-tree
|
||||
ref="scopeTreeRef"
|
||||
:data="deptTreeData"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:props="{ children: 'children', label: 'label' }"
|
||||
:check-strictly="false"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="scopeButtonLoading" type="primary" @click="submitScope">确 定</el-button>
|
||||
<el-button @click="cancelScope">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详细对话框 -->
|
||||
<el-dialog title="批次详细信息" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-tabs v-model="viewActiveTab" type="border-card">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="批次ID">{{ viewForm.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次名称">{{ viewForm.batchName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次描述" :span="2">{{ viewForm.description || '暂无描述' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewForm.status === '0' ? 'success' : 'danger'">
|
||||
{{ viewForm.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收集类型">
|
||||
<el-tag :type="viewForm.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ viewForm.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(viewForm.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="权限范围" name="scope">
|
||||
<div class="scope-detail-content">
|
||||
<div v-if="scopeDetailList.length === 0" class="no-data">
|
||||
<el-empty description="暂无权限范围配置" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert title="权限范围说明" type="info" :closable="false" show-icon class="mb-3">
|
||||
<template #default> 该批次配置了 {{ scopeDetailList.length }} 个班级的收集权限,具体如下: </template>
|
||||
</el-alert>
|
||||
<el-table :data="scopeDetailList" border stripe>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="学院名称" prop="collegeName" align="center" />
|
||||
<el-table-column label="专业名称" prop="majorName" align="center" />
|
||||
<el-table-column label="班级名称" prop="className" align="center" />
|
||||
<el-table-column label="配置时间" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StuColBatch" lang="ts">
|
||||
import { listStuColBatch, getStuColBatch, delStuColBatch, addStuColBatch, updateStuColBatch } from '@/api/xg/xsjcxx/XgStuColBatch/stuColBatch';
|
||||
import { StuColBatchVO, StuColBatchQuery, StuColBatchForm } from '@/api/xg/xsjcxx/XgStuColBatch/stuColBatch/types';
|
||||
import { setScope, getScope, getScopeDetail } from '@/api/xg/xsjcxx/stuColBatchClass/stuColBatchClass';
|
||||
import request from '@/utils/request';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const stuColBatchList = ref<StuColBatchVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const stuColBatchFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 收集范围弹窗相关数据
|
||||
const scopeDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const scopeButtonLoading = ref(false);
|
||||
const currentBatch = ref<StuColBatchVO | null>(null);
|
||||
const deptTreeData = ref<any[]>([]);
|
||||
const checkedDeptIds = ref<number[]>([]);
|
||||
const scopeTreeRef = ref();
|
||||
|
||||
// 查看详细弹窗相关数据
|
||||
const viewDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const viewForm = ref<StuColBatchVO>({} as StuColBatchVO);
|
||||
const viewActiveTab = ref('basic');
|
||||
const scopeDetailList = ref<any[]>([]);
|
||||
|
||||
const initFormData: StuColBatchForm = {
|
||||
id: undefined,
|
||||
batchName: undefined,
|
||||
description: undefined,
|
||||
startTime: new Date()
|
||||
.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
.replace(/\//g, '-'),
|
||||
endTime: undefined,
|
||||
status: '0',
|
||||
collectionType: '1'
|
||||
};
|
||||
const data = reactive<PageData<StuColBatchForm, StuColBatchQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
batchName: undefined,
|
||||
status: undefined,
|
||||
collectionType: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '批次ID不能为空', trigger: 'blur' }],
|
||||
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
collectionType: [{ required: true, message: '收集类型 1 - 新学期收集 2 - 其他 不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询学生信息收集批次列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listStuColBatch(queryParams.value);
|
||||
stuColBatchList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
stuColBatchFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StuColBatchVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StuColBatchVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getStuColBatch(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
stuColBatchFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StuColBatchVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除学生信息收集批次编号为"' + row?.batchName + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delStuColBatch(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'XgStuColBatch/stuColBatch/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`stuColBatch_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 收集范围按钮操作 */
|
||||
const handleScope = async (row: StuColBatchVO) => {
|
||||
currentBatch.value = row;
|
||||
// 先清空勾选状态
|
||||
checkedDeptIds.value = [];
|
||||
|
||||
await loadDeptTree();
|
||||
await loadCurrentScope(row.id);
|
||||
scopeDialog.visible = true;
|
||||
|
||||
// 使用nextTick确保DOM更新完成后再设置勾选状态
|
||||
await nextTick();
|
||||
if (scopeTreeRef.value && checkedDeptIds.value.length > 0) {
|
||||
scopeTreeRef.value.setCheckedKeys(checkedDeptIds.value);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载部门树数据 */
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId: 101
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
deptTreeData.value = response.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树数据失败:', error);
|
||||
ElMessage.error('获取范围选项失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载当前批次的收集范围 */
|
||||
const loadCurrentScope = async (batchId: string | number) => {
|
||||
try {
|
||||
checkedDeptIds.value = [];
|
||||
const response = await getScope(batchId);
|
||||
if (response && response.code === 200) {
|
||||
checkedDeptIds.value = response.data || [];
|
||||
} else {
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收集范围失败:', error);
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 构建树形数据 */
|
||||
const buildTree = (data: any[]) => {
|
||||
// 如果数据已经是树形结构,直接转换格式
|
||||
const convertToTreeNode = (item: any): any => {
|
||||
return {
|
||||
id: item.deptId,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map(convertToTreeNode) : []
|
||||
};
|
||||
};
|
||||
|
||||
return data.map(convertToTreeNode);
|
||||
};
|
||||
|
||||
/** 提交收集范围 */
|
||||
const submitScope = async () => {
|
||||
if (!currentBatch.value) return;
|
||||
|
||||
try {
|
||||
scopeButtonLoading.value = true;
|
||||
const checkedNodes = scopeTreeRef.value.getCheckedNodes();
|
||||
const checkedIds = checkedNodes.map((node: any) => node.id);
|
||||
|
||||
// 调用后端API设置收集范围
|
||||
const response = await setScope(currentBatch.value.id, checkedIds);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
ElMessage.success(response.msg || '收集范围设置成功');
|
||||
scopeDialog.visible = false;
|
||||
// 提交成功后清空状态
|
||||
handleScopeDialogClose();
|
||||
} else {
|
||||
ElMessage.error(response?.msg || '设置收集范围失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置收集范围失败:', error);
|
||||
ElMessage.error('设置收集范围失败');
|
||||
} finally {
|
||||
scopeButtonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消收集范围设置 */
|
||||
const cancelScope = () => {
|
||||
scopeDialog.visible = false;
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理收集范围弹窗关闭 */
|
||||
const handleScopeDialogClose = () => {
|
||||
// 清空相关状态
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详细按钮操作 */
|
||||
const handleView = async (row: StuColBatchVO) => {
|
||||
try {
|
||||
// 获取批次基本信息
|
||||
const res = await getStuColBatch(row.id);
|
||||
viewForm.value = res.data;
|
||||
|
||||
// 获取权限范围详细信息
|
||||
await loadScopeDetail(row.id);
|
||||
|
||||
viewDialog.visible = true;
|
||||
viewActiveTab.value = 'basic';
|
||||
} catch (error) {
|
||||
console.error('获取批次详情失败:', error);
|
||||
proxy?.$modal.msgError('获取批次详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载权限范围详细信息 */
|
||||
const loadScopeDetail = async (batchId: string | number) => {
|
||||
try {
|
||||
const response = await getScopeDetail(batchId);
|
||||
if (response && response.code === 200) {
|
||||
scopeDetailList.value = response.data || [];
|
||||
} else {
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载权限范围详情失败:', error);
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消查看详细 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
viewForm.value = {} as StuColBatchVO;
|
||||
scopeDetailList.value = [];
|
||||
viewActiveTab.value = 'basic';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-detail-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
+85
-27
@@ -59,9 +59,9 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['classMsg:classMsg:export']">导出</el-button>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -74,19 +74,22 @@
|
||||
<el-table-column label="学院名称" align="center" prop="collegeName" />
|
||||
<el-table-column label="专业名称" align="center" prop="majorName" />
|
||||
<el-table-column label="班级名称" align="center" prop="className" />
|
||||
<el-table-column label="班级类型" align="center" prop="classType">
|
||||
<el-table-column label="学历层次" align="center" prop="classType">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getClassTypeTag(scope.row.classType)">{{ formatClassType(scope.row.classType) }}</el-tag>
|
||||
<!-- <el-tag :type="getClassTypeTag(scope.row.classType)">{{ formatClassType(scope.row.classType) }}</el-tag> -->
|
||||
<dict-tag :options="zsjy_education_level" :value="scope.row.classType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="班主任" align="center" prop="classTeacherName" />
|
||||
<el-table-column label="辅导员" align="center" prop="counselorName" />
|
||||
<el-table-column label="年级" align="center" prop="grade" />
|
||||
<el-table-column label="入学年份" align="center" prop="enrollmentYear" />
|
||||
<el-table-column label="学制(年)" align="center" prop="schoolSystem" />
|
||||
<el-table-column label="毕业年份" align="center" prop="graduationYear" />
|
||||
<el-table-column label="班级状态" align="center" prop="classStatus">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getClassStatusTag(scope.row.classStatus)">{{ formatClassStatus(scope.row.classStatus) }}</el-tag>
|
||||
<!-- <el-tag :type="getClassStatusTag(scope.row.classStatus)">{{ formatClassStatus(scope.row.classStatus) }}</el-tag> -->
|
||||
<dict-tag :options="xg_class_status" :value="scope.row.classStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注信息" align="center" prop="remark" />
|
||||
@@ -156,13 +159,14 @@
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="form.className" placeholder="请输入班级名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班级类型" prop="classType">
|
||||
<el-select v-model="form.classType" placeholder="请选择班级类型" style="width: 100%">
|
||||
<el-option label="中级工" :value="1" />
|
||||
<el-form-item label="学历层级" prop="classType">
|
||||
<el-select v-model="form.classType" placeholder="请选择学历层级" style="width: 100%">
|
||||
<el-option v-for="dict in zsjy_education_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
<!-- <el-option label="中级工" :value="1" />
|
||||
<el-option label="高级工" :value="2" />
|
||||
<el-option label="预备技师" :value="3" />
|
||||
<el-option label="技师" :value="4" />
|
||||
<el-option label="中高职一体" :value="5" />
|
||||
<el-option label="中高职一体" :value="5" /> -->
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="年级" prop="grade">
|
||||
@@ -171,6 +175,9 @@
|
||||
<el-form-item label="入学年份" prop="enrollmentYear">
|
||||
<el-input v-model="form.enrollmentYear" placeholder="请输入入学年份" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学制(年)" prop="schoolSystem">
|
||||
<el-input v-model="form.schoolSystem" placeholder="请输入学制(年)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业年份" prop="graduationYear">
|
||||
<el-input v-model="form.graduationYear" placeholder="请输入毕业年份" />
|
||||
</el-form-item>
|
||||
@@ -206,8 +213,9 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="是否已毕业" prop="isGraduation">
|
||||
<el-select v-model="form.isGraduation" placeholder="请选择是否已毕业" style="width: 100%">
|
||||
<el-option label="未毕业" :value="0" />
|
||||
<el-option label="已毕业" :value="1" />
|
||||
<!-- <el-option label="未毕业" :value="0" />
|
||||
<el-option label="已毕业" :value="1" /> -->
|
||||
<el-option v-for="dict in is_graduation" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业日期" prop="graduationDate" v-if="false">
|
||||
@@ -280,10 +288,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="班级状态" prop="classStatus" v-if="false">
|
||||
<el-select v-model="form.classStatus" placeholder="请选择班级状态" style="width: 100%">
|
||||
<el-option label="正常" :value="1" />
|
||||
<!-- <el-option label="正常" :value="1" />
|
||||
<el-option label="暂停" :value="2" />
|
||||
<el-option label="毕业" :value="3" />
|
||||
<el-option label="解散" :value="4" />
|
||||
<el-option label="解散" :value="4" /> -->
|
||||
<el-option v-for="dict in xg_class_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="班级描述" prop="classDescription">
|
||||
@@ -404,9 +413,9 @@
|
||||
</template>
|
||||
|
||||
<script setup name="ClassMsg" lang="ts">
|
||||
import { listClassMsg, getClassMsg, delClassMsg, addClassMsg, updateClassMsg } from '@/api/classMsg/classMsg';
|
||||
import { listColleges, collegeTree, listMajorsByCollegeId } from '@/api/classMsg/college';
|
||||
import { ClassMsgVO, ClassMsgQuery, ClassMsgForm, SysDeptVo } from '@/api/classMsg/types';
|
||||
import { listClassMsg, getClassMsg, delClassMsg, addClassMsg, updateClassMsg } from '@/api/xg/xsjcxx/classmsg/classMsg';
|
||||
import { listColleges, collegeTree, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classmsg/college';
|
||||
import { ClassMsgVO, ClassMsgQuery, ClassMsgForm, SysDeptVo } from '@/api/xg/xsjcxx/classmsg/types';
|
||||
import { ComponentInternalInstance, ref, reactive, toRefs, onMounted, getCurrentInstance, watch } from 'vue';
|
||||
import type { FormInstance as ElFormInstance } from 'element-plus';
|
||||
import { getActiveUserList } from '@/api/xg/sysUser'; // 添加用户API导入
|
||||
@@ -423,7 +432,55 @@ interface PageData<T, Q> {
|
||||
rules: Record<string, any[]>;
|
||||
}
|
||||
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
}
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const {
|
||||
xg_class_status,
|
||||
is_graduation,
|
||||
wenhuachengdu,
|
||||
xg_political_status,
|
||||
sys_user_sex,
|
||||
xg_nation,
|
||||
sys_question_category,
|
||||
sys_item_enable,
|
||||
sys_item_required,
|
||||
sys_item_delete,
|
||||
house_hold_register_type,
|
||||
marital_status,
|
||||
sys_yesno,
|
||||
xg_family_economic,
|
||||
xg_blood_type,
|
||||
disability_type,
|
||||
zsjy_education_level,
|
||||
xg_training_mode,
|
||||
admission_category,
|
||||
student_status
|
||||
} = toRefs<any>(
|
||||
proxy?.useDict(
|
||||
'xg_class_status',
|
||||
'is_graduation',
|
||||
'wenhuachengdu',
|
||||
'xg_political_status',
|
||||
'sys_user_sex',
|
||||
'xg_nation',
|
||||
'sys_question_category',
|
||||
'sys_item_enable',
|
||||
'sys_item_required',
|
||||
'sys_item_delete',
|
||||
'house_hold_register_type',
|
||||
'marital_status',
|
||||
'sys_yesno',
|
||||
'xg_family_economic',
|
||||
'xg_blood_type',
|
||||
'disability_type',
|
||||
'zsjy_education_level',
|
||||
'xg_training_mode',
|
||||
'admission_category',
|
||||
'student_status'
|
||||
)
|
||||
);
|
||||
|
||||
const classMsgList = ref<ClassMsgVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
@@ -454,9 +511,10 @@ const initFormData: ClassMsgForm = {
|
||||
majorName: undefined,
|
||||
classCode: undefined,
|
||||
className: undefined,
|
||||
classType: undefined,
|
||||
classType: '4',
|
||||
grade: undefined,
|
||||
enrollmentYear: undefined,
|
||||
schoolSystem: undefined,
|
||||
graduationYear: undefined,
|
||||
trainingType: undefined,
|
||||
degreeType: undefined,
|
||||
@@ -471,7 +529,7 @@ const initFormData: ClassMsgForm = {
|
||||
deputyMonitorId: undefined,
|
||||
deputyMonitorName: undefined,
|
||||
classStatus: 1,
|
||||
isGraduation: 0,
|
||||
isGraduation: '0',
|
||||
graduationDate: undefined,
|
||||
plannedCapacity: undefined,
|
||||
currentStudentCount: undefined,
|
||||
@@ -514,7 +572,12 @@ const data = reactive<PageData<ClassMsgForm, ClassMsgQuery>>({
|
||||
classCode: [{ required: true, message: '班级代码不能为空', trigger: 'blur' }],
|
||||
className: [{ required: true, message: '班级名称不能为空', trigger: 'blur' }],
|
||||
grade: [{ required: true, message: '年级不能为空', trigger: 'blur' }],
|
||||
enrollmentYear: [{ required: true, message: '入学年份不能为空', trigger: 'blur' }]
|
||||
enrollmentYear: [{ required: true, message: '入学年份不能为空', trigger: 'blur' }],
|
||||
majorId: [{ required: true, message: '专业不能为空', trigger: 'blur' }],
|
||||
classType: [{ required: true, message: '学历层级不能为空', trigger: 'blur' }],
|
||||
schoolSystem: [{ required: true, message: '学制(年)不能为空', trigger: 'blur' }],
|
||||
graduationYear: [{ required: true, message: '毕业年份不能为空', trigger: 'blur' }],
|
||||
collegeId: [{ required: true, message: '学院不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -661,17 +724,12 @@ const handleUpdate = async (row?: ClassMsgVO) => {
|
||||
|
||||
// 确保classType是数字类型
|
||||
if (form.value.classType !== undefined) {
|
||||
form.value.classType = Number(form.value.classType);
|
||||
form.value.classType = String(form.value.classType);
|
||||
}
|
||||
|
||||
// 确保classStatus是数字类型
|
||||
if (form.value.classStatus !== undefined) {
|
||||
form.value.classStatus = Number(form.value.classStatus);
|
||||
}
|
||||
|
||||
// 确保isGraduation是数字类型
|
||||
// 确保isGraduation是字符串类型
|
||||
if (form.value.isGraduation !== undefined) {
|
||||
form.value.isGraduation = Number(form.value.isGraduation);
|
||||
form.value.isGraduation = String(form.value.isGraduation);
|
||||
}
|
||||
|
||||
// 获取该学院下的专业列表
|
||||
@@ -0,0 +1,889 @@
|
||||
<template>
|
||||
<div class="audit-dialog">
|
||||
<el-dialog v-model="visible" :title="title" width="80%" append-to-body :before-close="handleClose" top="5vh" class="audit-dialog-container">
|
||||
<div class="layout-container">
|
||||
<!-- 部门树 -->
|
||||
<div class="left-panel">
|
||||
<el-card shadow="hover">
|
||||
<el-input v-model="deptName" placeholder="请输入院系名称" prefix-icon="Search" clearable />
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="mt-2 dept-tree"
|
||||
node-key="id"
|
||||
:data="deptOptions"
|
||||
:props="{ label: 'label', children: 'children', disabled: 'disabled' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="right-panel">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="queryParams.phone" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="提交状态" prop="submitStatus">
|
||||
<el-select v-model="queryParams.submitStatus" placeholder="提交状态" clearable>
|
||||
<!-- 3-审核中,4-已驳回,5-已锁定 -->
|
||||
<el-option label="审核中" value="3" />
|
||||
<el-option label="已驳回" value="4" />
|
||||
<el-option label="已锁定" value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 学生信息表格 -->
|
||||
<el-card shadow="hover">
|
||||
<!-- <template #header>
|
||||
<div class="card-header">
|
||||
<span>学生信息审核 - {{ batchInfo?.batchName || '未知批次' }}</span>
|
||||
<div class="header-actions">
|
||||
<el-button type="success" icon="Check" :disabled="!hasSelected" @click="handleBatchApprove"> 批量通过 </el-button>
|
||||
<el-button type="danger" icon="Close" :disabled="!hasSelected" @click="handleBatchReject"> 批量拒绝 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<el-table v-loading="loading" border :data="studentList" @selection-change="handleSelectionChange" class="right-table">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="姓名" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="院系" align="center" prop="collegeName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="专业" align="center" prop="majorName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="班级" align="center" prop="className" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机号码" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="提交状态" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getSubmitStatusType(scope.row.infoStatus)">
|
||||
{{ getSubmitStatusLabel(scope.row.infoStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-right" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="审核" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleAuditStudent(scope.row)" :disabled="scope.row.submitStatus === '0'">
|
||||
审核
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="Document" @click="handleViewDetail(scope.row)"> 详情 </el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template> -->
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生审核详情弹窗 -->
|
||||
<el-dialog v-model="auditDetailDialog.visible" :title="auditDetailDialog.title" width="800px" append-to-body>
|
||||
<div v-if="currentAuditStudent">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ currentAuditStudent.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ currentAuditStudent.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ currentAuditStudent.collegeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ currentAuditStudent.majorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ currentAuditStudent.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ currentAuditStudent.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交状态" :span="2">
|
||||
<el-tag :type="getSubmitStatusType(currentAuditStudent.submitStatus)">
|
||||
{{ getSubmitStatusLabel(currentAuditStudent.submitStatus) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间" :span="2">
|
||||
{{ parseTime(currentAuditStudent.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-form ref="auditFormRef" :model="auditForm" label-width="100px">
|
||||
<el-form-item label="审核结果" prop="auditResult">
|
||||
<el-radio-group v-model="auditForm.auditResult">
|
||||
<el-radio label="2">通过</el-radio>
|
||||
<el-radio label="3">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="auditRemark">
|
||||
<el-input v-model="auditForm.auditRemark" type="textarea" :rows="4" placeholder="请输入审核意见" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="auditLoading" type="primary" @click="submitAudit">确定审核</el-button>
|
||||
<el-button @click="auditDetailDialog.visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生信息详情弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDialogComponent"
|
||||
:visible="stuMsgDialogVisible"
|
||||
:title="stuMsgDialogTitle"
|
||||
:batchInfo="stuMsgBatchInfo"
|
||||
@update:visible="stuMsgDialogVisible = $event"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
v-if="StuMsgDialogComponent"
|
||||
/>
|
||||
|
||||
<!-- 学生详情查看弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDetailDialogComponent"
|
||||
:visible="stuMsgDetailDialogVisible"
|
||||
:title="stuMsgDetailDialogTitle"
|
||||
:batchInfo="stuMsgDetailBatchInfo"
|
||||
@update:visible="stuMsgDetailDialogVisible = $event"
|
||||
v-if="StuMsgDetailDialogComponent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, computed, watch, onMounted, nextTick, defineAsyncComponent } from 'vue';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
import ElMessageBox from 'element-plus/es/components/message-box/index';
|
||||
// @ts-ignore
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 动态加载StuMsgDialog组件
|
||||
const StuMsgDialogComponent = defineAsyncComponent(() => import('./stuMsgDialog.vue'));
|
||||
// 动态加载学生详情弹窗组件
|
||||
const StuMsgDetailDialogComponent = defineAsyncComponent(() => import('./stuMsgDetailDialog.vue'));
|
||||
|
||||
interface StudentAuditInfo {
|
||||
id: string | number;
|
||||
studentNo: string;
|
||||
name: string;
|
||||
collegeName: string;
|
||||
majorName: string;
|
||||
className: string;
|
||||
phone: string;
|
||||
submitStatus: string;
|
||||
submitTime: string;
|
||||
auditResult?: string;
|
||||
auditRemark?: string;
|
||||
auditTime?: string;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
name: string;
|
||||
studentNo: string;
|
||||
phone: string;
|
||||
submitStatus: string;
|
||||
deptId?: number;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
title: string;
|
||||
batchInfo: any;
|
||||
}>();
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
confirm: [data: any];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const visible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const studentList = ref<StudentAuditInfo[]>([]);
|
||||
const selectedStudents = ref<StudentAuditInfo[]>([]);
|
||||
const deptName = ref('');
|
||||
const deptOptions = ref<any[]>([]);
|
||||
const currentSelectedNode = ref<any>(null);
|
||||
|
||||
// 计算属性
|
||||
const hasSelected = computed(() => selectedStudents.value.length > 0);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<QueryParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
studentNo: '',
|
||||
phone: '',
|
||||
submitStatus: '',
|
||||
deptId: undefined
|
||||
});
|
||||
|
||||
// 审核详情弹窗
|
||||
const auditDetailDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息审核'
|
||||
});
|
||||
|
||||
const currentAuditStudent = ref<StudentAuditInfo | null>(null);
|
||||
const auditLoading = ref(false);
|
||||
|
||||
const auditForm = reactive({
|
||||
auditResult: '2',
|
||||
auditRemark: ''
|
||||
});
|
||||
|
||||
// Refs
|
||||
const deptTreeRef = ref();
|
||||
const queryFormRef = ref();
|
||||
const auditFormRef = ref();
|
||||
|
||||
// 学生信息详情弹窗相关数据
|
||||
const stuMsgDialogVisible = ref(false);
|
||||
const stuMsgDialogTitle = ref('学生信息审核');
|
||||
const stuMsgBatchInfo = ref<any>(null);
|
||||
|
||||
// 学生详情查看弹窗相关数据
|
||||
const stuMsgDetailDialogVisible = ref(false);
|
||||
const stuMsgDetailDialogTitle = ref('学生信息详情查看');
|
||||
const stuMsgDetailBatchInfo = ref<any>(null);
|
||||
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/** 获取提交状态标签 */
|
||||
const getSubmitStatusLabel = (status: string) => {
|
||||
// 3-审核中,4-已驳回,5-已锁定
|
||||
const statusMap: Record<string, string> = {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
/** 获取提交状态标签类型 */
|
||||
const getSubmitStatusType = (status: string): 'success' | 'info' | 'warning' | 'danger' => {
|
||||
const typeMap: Record<string, 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
'3': 'info',
|
||||
'5': 'success',
|
||||
'4': 'danger'
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
/** 格式化时间 */
|
||||
const parseTime = (time: any, pattern?: string): string => {
|
||||
if (!time) return '';
|
||||
|
||||
let date: Date;
|
||||
if (typeof time === 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
||||
time = parseInt(time);
|
||||
}
|
||||
if (typeof time === 'number' && time.toString().length === 10) {
|
||||
time = time * 1000;
|
||||
}
|
||||
date = new Date(time);
|
||||
}
|
||||
|
||||
const formatObj: Record<string, number> = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
|
||||
const time_str = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
return time_str.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||
const value = formatObj[key];
|
||||
if (key === 'a') {
|
||||
return ['日', '一', '二', '三', '四', '五', '六'][value];
|
||||
}
|
||||
return value.toString().padStart(2, '0');
|
||||
});
|
||||
};
|
||||
|
||||
/** 查询学生列表 */
|
||||
const getList = async () => {
|
||||
if (!props.batchInfo?.id) return;
|
||||
|
||||
// 如果没有选择部门,则不查询
|
||||
if (!queryParams.deptId) {
|
||||
ElMessage.warning('请先选择部门');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
batchId: props.batchInfo.id,
|
||||
deptId: queryParams.deptId,
|
||||
name: queryParams.name || undefined,
|
||||
studentNo: queryParams.studentNo || undefined,
|
||||
phone: queryParams.phone || undefined,
|
||||
infoStatus: queryParams.submitStatus || undefined,
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: queryParams.pageSize
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/listByDept',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
studentList.value = res.rows || [];
|
||||
total.value = res.total || 0;
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '获取学生列表失败');
|
||||
studentList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生列表失败:', error);
|
||||
ElMessage.error('获取学生列表失败');
|
||||
studentList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
if (!props.batchInfo?.id) {
|
||||
console.warn('批次ID不存在,无法获取部门树');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
url: `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/${props.batchInfo.id}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
deptOptions.value = res.data;
|
||||
// 设置根节点为默认选中节点
|
||||
setDefaultRootNode(res.data);
|
||||
} else {
|
||||
console.warn('部门树数据格式不符合预期:', res);
|
||||
deptOptions.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
deptOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 设置默认根节点并触发查询 */
|
||||
const setDefaultRootNode = (treeData: any[]) => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
// 找到第一个可用的根节点(不是disabled的节点)
|
||||
const findFirstAvailableNode = (nodes: any[]): any => {
|
||||
for (const node of nodes) {
|
||||
if (!node.disabled) {
|
||||
return node;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const childNode = findFirstAvailableNode(node.children);
|
||||
if (childNode) {
|
||||
return childNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const rootNode = findFirstAvailableNode(treeData);
|
||||
if (rootNode) {
|
||||
// 设置当前选中的节点
|
||||
currentSelectedNode.value = rootNode;
|
||||
queryParams.deptId = rootNode.id;
|
||||
|
||||
// 设置树形控件的当前选中项
|
||||
setTimeout(() => {
|
||||
deptTreeRef.value?.setCurrentKey(rootNode.id);
|
||||
}, 100);
|
||||
|
||||
// 触发数据查询
|
||||
getList();
|
||||
} else {
|
||||
// 如果没有找到可用节点,则直接查询全部数据
|
||||
getList();
|
||||
}
|
||||
} else {
|
||||
// 如果没有树数据,则直接查询全部数据
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 节点单击事件 */
|
||||
const handleNodeClick = (data: any) => {
|
||||
// alert(1);
|
||||
// console.log('handleNodeClick', data);
|
||||
// 只有班级节点才可以点击(disabled=false的节点)
|
||||
// if (data.disabled) {
|
||||
// ElMessage.warning('没有该节点权限,请联系管理员');
|
||||
// return;
|
||||
// }
|
||||
// 保存当前选中的节点信息
|
||||
currentSelectedNode.value = data;
|
||||
queryParams.deptId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
// /** 节点单击事件 */
|
||||
// const handleNodeClick = (data: any) => {
|
||||
// currentSelectedNode.value = data;
|
||||
// queryParams.deptId = data.id;
|
||||
// handleQuery();
|
||||
// };
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.name = '';
|
||||
queryParams.studentNo = '';
|
||||
queryParams.phone = '';
|
||||
queryParams.submitStatus = '';
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StudentAuditInfo[]) => {
|
||||
selectedStudents.value = selection;
|
||||
};
|
||||
|
||||
/** 审核学生 */
|
||||
const handleAuditStudent = (row: StudentAuditInfo) => {
|
||||
// 打开学生信息详情弹窗进行审核
|
||||
stuMsgBatchInfo.value = {
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDialogTitle.value = `学生信息审核 - ${row.name}`;
|
||||
stuMsgDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 查看学生详情 */
|
||||
const handleViewDetail = (row: StudentAuditInfo) => {
|
||||
// 设置详情弹窗数据
|
||||
stuMsgDetailBatchInfo.value = {
|
||||
id: props.batchInfo?.id,
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
studentId: row.id,
|
||||
batchName: props.batchInfo?.batchName,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDetailDialogTitle.value = `学生信息详情 - ${row.name}`;
|
||||
stuMsgDetailDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 批量通过 */
|
||||
const handleBatchApprove = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要审核的学生');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确认批量通过选中的学生信息?', '提示');
|
||||
|
||||
loading.value = true;
|
||||
const studentIds = selectedStudents.value.map((item) => item.id);
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/batchAudit',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentIds,
|
||||
auditResult: '2',
|
||||
auditRemark: '批量审核通过'
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('批量审核成功');
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '批量审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量审核失败:', error);
|
||||
ElMessage.error('批量审核失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 批量拒绝 */
|
||||
const handleBatchReject = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要审核的学生');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { value: remark } = await ElMessageBox.prompt('请输入拒绝理由', '批量拒绝', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: '请输入拒绝理由',
|
||||
inputValidator: (val) => {
|
||||
if (!val || val.trim() === '') {
|
||||
return '拒绝理由不能为空';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
loading.value = true;
|
||||
const studentIds = selectedStudents.value.map((item) => item.id);
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/batchAudit',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentIds,
|
||||
auditResult: '3',
|
||||
auditRemark: remark
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('批量审核成功');
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '批量审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量审核失败:', error);
|
||||
ElMessage.error('批量审核失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const submitAudit = async () => {
|
||||
if (!currentAuditStudent.value) return;
|
||||
|
||||
auditFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
try {
|
||||
auditLoading.value = true;
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/auditStudent',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentId: currentAuditStudent.value!.id,
|
||||
auditResult: auditForm.auditResult,
|
||||
auditRemark: auditForm.auditRemark
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('审核成功');
|
||||
auditDetailDialog.visible = false;
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error);
|
||||
ElMessage.error('审核失败');
|
||||
} finally {
|
||||
auditLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
// 重置数据
|
||||
studentList.value = [];
|
||||
selectedStudents.value = [];
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.name = '';
|
||||
queryParams.studentNo = '';
|
||||
queryParams.phone = '';
|
||||
queryParams.submitStatus = '';
|
||||
currentSelectedNode.value = null;
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal && props.batchInfo?.id) {
|
||||
// 弹窗打开时初始化数据
|
||||
getDeptTree().then(() => {
|
||||
getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听部门名称筛选
|
||||
watch(deptName, (val) => {
|
||||
deptTreeRef.value?.filter(val);
|
||||
});
|
||||
|
||||
/** 学生信息确认回调 */
|
||||
const handleStuMsgConfirm = (data: any) => {
|
||||
console.log('学生信息已更新:', data);
|
||||
ElMessage.success('学生信息已更新');
|
||||
// 刷新学生列表
|
||||
getList();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 审核弹窗样式 */
|
||||
.audit-dialog :deep(.el-dialog) {
|
||||
margin-top: 5vh !important;
|
||||
margin-bottom: 5vh !important;
|
||||
height: 700px;
|
||||
max-height: 700px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.audit-dialog :deep(.el-dialog__body) {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
|
||||
/* 主布局容器 */
|
||||
.layout-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧面板 - 固定宽度300px */
|
||||
.left-panel {
|
||||
width: 350px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧面板内的卡片容器 */
|
||||
.left-panel .el-card {
|
||||
min-width: 330px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-panel .el-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 部门树组件样式 */
|
||||
.dept-tree {
|
||||
height: 520px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 强制覆盖el-tree的默认样式 */
|
||||
.dept-tree :deep(.el-tree-node) {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__content) {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__content:hover) {
|
||||
background-color: var(--el-tree-node-hover-bg-color, #f5f7fa) !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__label) {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
/* 右侧面板 - 占据剩余空间 */
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 右侧面板内容区域样式 */
|
||||
.right-panel .mb-\[10px\] {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.right-panel .el-card:last-child {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-panel .el-card:last-child :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.right-panel .el-table {
|
||||
flex: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 右侧表格样式 */
|
||||
.right-table {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.right-table :deep(.el-table__body-wrapper) {
|
||||
overflow-y: auto;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
/* 卡片头部样式 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 1200px) {
|
||||
.layout-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.left-panel .el-card {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.dept-tree {
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,773 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['XgStuColBatch:stuColBatch:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['XgStuColBatch:stuColBatch:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="stuColBatchList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="批次ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="批次名称" align="center" prop="batchName" />
|
||||
<el-table-column label="批次描述" align="center" prop="description" />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收集类型" align="center" prop="collectionType">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ scope.row.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详细" placement="top">
|
||||
<el-button link type="info" plain icon="View" @click="handleView(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审核管理" placement="top">
|
||||
<el-button link type="warning" plain icon="Document" @click="handleAudit(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<!-- <el-tooltip content="信息完善" placement="top" v-if="isBatchValid(scope.row)">
|
||||
<el-button link type="success" plain icon="Edit" @click="handleInfoComplete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看提交历史" placement="top">
|
||||
<el-button link type="info" plain icon="Clock" @click="handleViewHistory(scope.row)"></el-button>
|
||||
</el-tooltip> -->
|
||||
|
||||
<!-- <el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
plain
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="收集范围" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
plain
|
||||
icon="Setting"
|
||||
@click="handleScope(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:scope']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
plain
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
></el-button>
|
||||
</el-tooltip> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改学生信息收集批次对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="stuColBatchFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="form.batchName" placeholder="请输入批次名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.startTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择开始时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.endTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择结束时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收集类型" prop="collectionType">
|
||||
<el-select v-model="form.collectionType" placeholder="请选择收集类型">
|
||||
<el-option label="新学期收集" value="1" />
|
||||
<el-option label="其他" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 收集范围设置对话框 -->
|
||||
<el-dialog title="设置收集范围" v-model="scopeDialog.visible" width="500px" append-to-body @close="handleScopeDialogClose">
|
||||
<div class="scope-content">
|
||||
<el-tree
|
||||
ref="scopeTreeRef"
|
||||
:data="deptTreeData"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:props="{ children: 'children', label: 'label' }"
|
||||
:check-strictly="false"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="scopeButtonLoading" type="primary" @click="submitScope">确 定</el-button>
|
||||
<el-button @click="cancelScope">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详细对话框 -->
|
||||
<el-dialog title="批次详细信息" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-tabs v-model="viewActiveTab" type="border-card">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="批次ID">{{ viewForm.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次名称">{{ viewForm.batchName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次描述" :span="2">{{ viewForm.description || '暂无描述' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewForm.status === '0' ? 'success' : 'danger'">
|
||||
{{ viewForm.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收集类型">
|
||||
<el-tag :type="viewForm.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ viewForm.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(viewForm.updateTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="权限范围" name="scope">
|
||||
<div class="scope-detail-content">
|
||||
<div v-if="scopeDetailList.length === 0" class="no-data">
|
||||
<el-empty description="暂无权限范围配置" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert title="权限范围说明" type="info" :closable="false" show-icon class="mb-3">
|
||||
<template #default> 该批次配置了 {{ scopeDetailList.length }} 个班级的收集权限,具体如下: </template>
|
||||
</el-alert>
|
||||
<el-table :data="scopeDetailList" border stripe>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="学院名称" prop="collegeName" align="center" />
|
||||
<el-table-column label="专业名称" prop="majorName" align="center" />
|
||||
<el-table-column label="班级名称" prop="className" align="center" />
|
||||
<el-table-column label="配置时间" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生信息完善弹窗 -->
|
||||
<StuMsgDialog
|
||||
v-model:visible="stuMsgDialog.visible"
|
||||
:title="stuMsgDialog.title"
|
||||
:batch-info="stuMsgDialog.batchInfo"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
@update:visible="handleStuMsgClose"
|
||||
/>
|
||||
|
||||
<!-- 学生信息提交历史详情弹窗 -->
|
||||
<StuMsgDetailDialog
|
||||
v-model:visible="historyDialog.visible"
|
||||
:title="historyDialog.title"
|
||||
:batch-info="historyDialog.batchInfo"
|
||||
@update:visible="handleHistoryDialogClose"
|
||||
/>
|
||||
|
||||
<!-- 审核管理弹窗 -->
|
||||
<AuditDialog
|
||||
v-model:visible="auditDialog.visible"
|
||||
:title="auditDialog.title"
|
||||
:batch-info="auditDialog.batchInfo"
|
||||
@update:visible="handleAuditDialogClose"
|
||||
@confirm="handleAuditConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StuColBatch" lang="ts">
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onMounted, nextTick } from 'vue';
|
||||
import { getStuColBatch, delStuColBatch, addStuColBatch, updateStuColBatch } from '@/api/xg/xsjcxx/stuMsgAudit';
|
||||
import { StuColBatchVO, StuColBatchQuery, StuColBatchForm } from '@/api/xg/xsjcxx/stuMsgAudit/types';
|
||||
import { setScope, getScope, getScopeDetail } from '@/api/xg/xsjcxx/stuColBatchClass/stuColBatchClass';
|
||||
import request from '@/utils/request';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import type { FormInstance as ElFormInstance } from 'element-plus';
|
||||
import StuMsgDialog from './stuMsgDialog.vue';
|
||||
import StuMsgDetailDialog from './stuMsgDetailDialog.vue';
|
||||
import AuditDialog from './auditDialog.vue';
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface PageData<T, Q> {
|
||||
form: T;
|
||||
queryParams: Q;
|
||||
rules: Record<string, any[]>;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const stuColBatchList = ref<StuColBatchVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const stuColBatchFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 收集范围弹窗相关数据
|
||||
const scopeDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const scopeButtonLoading = ref(false);
|
||||
const currentBatch = ref<StuColBatchVO | null>(null);
|
||||
const deptTreeData = ref<any[]>([]);
|
||||
const checkedDeptIds = ref<number[]>([]);
|
||||
const scopeTreeRef = ref();
|
||||
|
||||
// 查看详细弹窗相关数据
|
||||
const viewDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const viewForm = ref<StuColBatchVO>({} as StuColBatchVO);
|
||||
const viewActiveTab = ref('basic');
|
||||
const scopeDetailList = ref<any[]>([]);
|
||||
|
||||
// 学生信息完善弹窗相关数据
|
||||
const stuMsgDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息完善',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
// 学生信息提交历史详情弹窗相关数据
|
||||
const historyDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息提交历史',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
// 审核管理弹窗相关数据
|
||||
const auditDialog = reactive({
|
||||
visible: false,
|
||||
title: '审核管理',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
const initFormData: StuColBatchForm = {
|
||||
id: undefined,
|
||||
batchName: undefined,
|
||||
description: undefined,
|
||||
startTime: new Date()
|
||||
.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
.replace(/\//g, '-'),
|
||||
endTime: undefined,
|
||||
status: '0',
|
||||
collectionType: '1'
|
||||
};
|
||||
const data = reactive<PageData<StuColBatchForm, StuColBatchQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
batchName: undefined,
|
||||
status: undefined,
|
||||
collectionType: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '批次ID不能为空', trigger: 'blur' }],
|
||||
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
collectionType: [{ required: true, message: '收集类型 1 - 新学期收集 2 - 其他 不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询学生信息收集批次列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
console.log('queryParams.value.batchName', queryParams.value.batchName);
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getTeacherRelatedBatches',
|
||||
method: 'get',
|
||||
params: {
|
||||
batchName: queryParams.value.batchName || ''
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
// 将返回的数据转换为表格需要的格式
|
||||
stuColBatchList.value = res.data || [];
|
||||
total.value = res.data ? res.data.length : 0;
|
||||
} else {
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError(res?.msg || '获取数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生可参与批次信息失败:', error);
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
stuColBatchFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StuColBatchVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StuColBatchVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getStuColBatch(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
stuColBatchFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StuColBatchVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除学生信息收集批次编号为"' + row?.batchName + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delStuColBatch(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'XgStuColBatch/stuColBatch/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`stuColBatch_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 收集范围按钮操作 */
|
||||
const handleScope = async (row: StuColBatchVO) => {
|
||||
currentBatch.value = row;
|
||||
// 先清空勾选状态
|
||||
checkedDeptIds.value = [];
|
||||
|
||||
await loadDeptTree();
|
||||
await loadCurrentScope(row.id);
|
||||
scopeDialog.visible = true;
|
||||
|
||||
// 使用nextTick确保DOM更新完成后再设置勾选状态
|
||||
await nextTick();
|
||||
if (scopeTreeRef.value && checkedDeptIds.value.length > 0) {
|
||||
scopeTreeRef.value.setCheckedKeys(checkedDeptIds.value);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载部门树数据 */
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId: 101
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
deptTreeData.value = response.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树数据失败:', error);
|
||||
ElMessage.error('获取范围选项失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载当前批次的收集范围 */
|
||||
const loadCurrentScope = async (batchId: string | number) => {
|
||||
try {
|
||||
checkedDeptIds.value = [];
|
||||
const response = await getScope(batchId);
|
||||
if (response && response.code === 200) {
|
||||
checkedDeptIds.value = response.data || [];
|
||||
} else {
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收集范围失败:', error);
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 构建树形数据 */
|
||||
const buildTree = (data: any[]) => {
|
||||
// 如果数据已经是树形结构,直接转换格式
|
||||
const convertToTreeNode = (item: any): any => {
|
||||
return {
|
||||
id: item.deptId,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map(convertToTreeNode) : []
|
||||
};
|
||||
};
|
||||
|
||||
return data.map(convertToTreeNode);
|
||||
};
|
||||
|
||||
/** 提交收集范围 */
|
||||
const submitScope = async () => {
|
||||
if (!currentBatch.value) return;
|
||||
|
||||
try {
|
||||
scopeButtonLoading.value = true;
|
||||
const checkedNodes = scopeTreeRef.value.getCheckedNodes();
|
||||
const checkedIds = checkedNodes.map((node: any) => node.id);
|
||||
|
||||
// 调用后端API设置收集范围
|
||||
const response = await setScope(currentBatch.value.id, checkedIds);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
ElMessage.success(response.msg || '收集范围设置成功');
|
||||
scopeDialog.visible = false;
|
||||
// 提交成功后清空状态
|
||||
handleScopeDialogClose();
|
||||
} else {
|
||||
ElMessage.error(response?.msg || '设置收集范围失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置收集范围失败:', error);
|
||||
ElMessage.error('设置收集范围失败');
|
||||
} finally {
|
||||
scopeButtonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消收集范围设置 */
|
||||
const cancelScope = () => {
|
||||
scopeDialog.visible = false;
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理收集范围弹窗关闭 */
|
||||
const handleScopeDialogClose = () => {
|
||||
// 清空相关状态
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详细按钮操作 */
|
||||
const handleView = async (row: StuColBatchVO) => {
|
||||
try {
|
||||
// 获取批次基本信息
|
||||
const res = await getStuColBatch(row?.id);
|
||||
viewForm.value = res.data;
|
||||
|
||||
// 获取权限范围详细信息
|
||||
// await loadScopeDetail(row.id);
|
||||
|
||||
viewDialog.visible = true;
|
||||
viewActiveTab.value = 'basic';
|
||||
} catch (error) {
|
||||
console.error('获取批次详情失败:', error);
|
||||
proxy?.$modal.msgError('获取批次详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载权限范围详细信息 */
|
||||
const loadScopeDetail = async (batchId: string | number) => {
|
||||
try {
|
||||
const response = await getScopeDetail(batchId);
|
||||
if (response && response.code === 200) {
|
||||
scopeDetailList.value = response.data || [];
|
||||
} else {
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载权限范围详情失败:', error);
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消查看详细 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
viewForm.value = {} as StuColBatchVO;
|
||||
scopeDetailList.value = [];
|
||||
viewActiveTab.value = 'basic';
|
||||
};
|
||||
|
||||
/** 信息完善按钮操作 */
|
||||
const handleInfoComplete = (row: StuColBatchVO) => {
|
||||
console.log('信息完善按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
stuMsgDialog.batchInfo = row;
|
||||
stuMsgDialog.title = `信息完善 - ${row.batchName}`;
|
||||
stuMsgDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 学生信息完善弹窗确认 */
|
||||
const handleStuMsgConfirm = (formData: any) => {
|
||||
console.log('学生信息完善提交:', formData);
|
||||
// TODO: 调用API保存学生信息
|
||||
ElMessage.success('学生信息完善成功');
|
||||
};
|
||||
|
||||
/** 关闭学生信息完善弹窗 */
|
||||
const handleStuMsgClose = () => {
|
||||
stuMsgDialog.visible = false;
|
||||
stuMsgDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 查看提交历史按钮操作 */
|
||||
const handleViewHistory = (row: StuColBatchVO) => {
|
||||
console.log('查看提交历史按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
|
||||
historyDialog.batchInfo = row;
|
||||
historyDialog.title = `学生信息提交历史 - ${row.batchName}`;
|
||||
historyDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 关闭学生信息提交历史详情弹窗 */
|
||||
const handleHistoryDialogClose = () => {
|
||||
historyDialog.visible = false;
|
||||
historyDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 审核管理按钮操作 */
|
||||
const handleAudit = (row: StuColBatchVO) => {
|
||||
console.log('审核管理按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
|
||||
auditDialog.batchInfo = row;
|
||||
auditDialog.title = `审核管理 - ${row.batchName}`;
|
||||
auditDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 审核弹窗确认 */
|
||||
const handleAuditConfirm = (data: any) => {
|
||||
console.log('审核弹窗确认:', data);
|
||||
ElMessage.success('审核操作成功');
|
||||
};
|
||||
|
||||
/** 关闭审核管理弹窗 */
|
||||
const handleAuditDialogClose = () => {
|
||||
auditDialog.visible = false;
|
||||
auditDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 检查批次是否有效(在有效时间内且状态正常) */
|
||||
const isBatchValid = (row: StuColBatchVO): boolean => {
|
||||
// 检查状态是否为正常
|
||||
if (row.status !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在有效时间内
|
||||
const now = new Date();
|
||||
const startTime = new Date(row.startTime);
|
||||
const endTime = new Date(row.endTime);
|
||||
|
||||
return now >= startTime && now <= endTime;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-detail-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,732 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['XgStuColBatch:stuColBatch:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['XgStuColBatch:stuColBatch:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="stuColBatchList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="批次ID" align="center" prop="batchId" v-if="true" />
|
||||
<el-table-column label="批次名称" align="center" prop="batchName" />
|
||||
<el-table-column label="批次描述" align="center" prop="description" />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收集类型" align="center" prop="collectionType">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ scope.row.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详细" placement="top">
|
||||
<el-button link type="info" plain icon="View" @click="handleView(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="信息完善" placement="top" v-if="isBatchValid(scope.row)">
|
||||
<el-button link type="success" plain icon="Edit" @click="handleInfoComplete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看提交历史" placement="top">
|
||||
<el-button link type="info" plain icon="Clock" @click="handleViewHistory(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- <el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
plain
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="收集范围" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
plain
|
||||
icon="Setting"
|
||||
@click="handleScope(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:scope']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
plain
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
></el-button>
|
||||
</el-tooltip> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改学生信息收集批次对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="stuColBatchFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="form.batchName" placeholder="请输入批次名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.startTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择开始时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.endTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择结束时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收集类型" prop="collectionType">
|
||||
<el-select v-model="form.collectionType" placeholder="请选择收集类型">
|
||||
<el-option label="新学期收集" value="1" />
|
||||
<el-option label="其他" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 收集范围设置对话框 -->
|
||||
<el-dialog title="设置收集范围" v-model="scopeDialog.visible" width="500px" append-to-body @close="handleScopeDialogClose">
|
||||
<div class="scope-content">
|
||||
<el-tree
|
||||
ref="scopeTreeRef"
|
||||
:data="deptTreeData"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:props="{ children: 'children', label: 'label' }"
|
||||
:check-strictly="false"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="scopeButtonLoading" type="primary" @click="submitScope">确 定</el-button>
|
||||
<el-button @click="cancelScope">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详细对话框 -->
|
||||
<el-dialog title="批次详细信息" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-tabs v-model="viewActiveTab" type="border-card">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="批次ID">{{ viewForm.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次名称">{{ viewForm.batchName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次描述" :span="2">{{ viewForm.description || '暂无描述' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewForm.status === '0' ? 'success' : 'danger'">
|
||||
{{ viewForm.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收集类型">
|
||||
<el-tag :type="viewForm.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ viewForm.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(viewForm.updateTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="权限范围" name="scope">
|
||||
<div class="scope-detail-content">
|
||||
<div v-if="scopeDetailList.length === 0" class="no-data">
|
||||
<el-empty description="暂无权限范围配置" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert title="权限范围说明" type="info" :closable="false" show-icon class="mb-3">
|
||||
<template #default> 该批次配置了 {{ scopeDetailList.length }} 个班级的收集权限,具体如下: </template>
|
||||
</el-alert>
|
||||
<el-table :data="scopeDetailList" border stripe>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="学院名称" prop="collegeName" align="center" />
|
||||
<el-table-column label="专业名称" prop="majorName" align="center" />
|
||||
<el-table-column label="班级名称" prop="className" align="center" />
|
||||
<el-table-column label="配置时间" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生信息完善弹窗 -->
|
||||
<StuMsgDialog
|
||||
v-model:visible="stuMsgDialog.visible"
|
||||
:title="stuMsgDialog.title"
|
||||
:batch-info="stuMsgDialog.batchInfo"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
@update:visible="handleStuMsgClose"
|
||||
/>
|
||||
|
||||
<!-- 学生信息提交历史详情弹窗 -->
|
||||
<StuMsgDetailDialog
|
||||
v-model:visible="historyDialog.visible"
|
||||
:title="historyDialog.title"
|
||||
:batch-info="historyDialog.batchInfo"
|
||||
@update:visible="handleHistoryDialogClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StuColBatch" lang="ts">
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onMounted, nextTick } from 'vue';
|
||||
import { getStuColBatch, delStuColBatch, addStuColBatch, updateStuColBatch } from '@/api/xg/xsjcxx/stuMsgSupply';
|
||||
import { StuColBatchVO, StuColBatchQuery, StuColBatchForm } from '@/api/xg/xsjcxx/stuMsgSupply/types';
|
||||
import { setScope, getScope, getScopeDetail } from '@/api/xg/xsjcxx/stuColBatchClass/stuColBatchClass';
|
||||
import request from '@/utils/request';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance as ElFormInstance } from 'element-plus';
|
||||
import StuMsgDialog from './stuMsgDialog.vue';
|
||||
import StuMsgDetailDialog from './stuMsgDetailDialog.vue';
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface PageData<T, Q> {
|
||||
form: T;
|
||||
queryParams: Q;
|
||||
rules: Record<string, any[]>;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const stuColBatchList = ref<StuColBatchVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const stuColBatchFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 收集范围弹窗相关数据
|
||||
const scopeDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const scopeButtonLoading = ref(false);
|
||||
const currentBatch = ref<StuColBatchVO | null>(null);
|
||||
const deptTreeData = ref<any[]>([]);
|
||||
const checkedDeptIds = ref<number[]>([]);
|
||||
const scopeTreeRef = ref();
|
||||
|
||||
// 查看详细弹窗相关数据
|
||||
const viewDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const viewForm = ref<StuColBatchVO>({} as StuColBatchVO);
|
||||
const viewActiveTab = ref('basic');
|
||||
const scopeDetailList = ref<any[]>([]);
|
||||
|
||||
// 学生信息完善弹窗相关数据
|
||||
const stuMsgDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息完善',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
// 学生信息提交历史详情弹窗相关数据
|
||||
const historyDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息提交历史',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
const initFormData: StuColBatchForm = {
|
||||
id: undefined,
|
||||
batchName: undefined,
|
||||
description: undefined,
|
||||
startTime: new Date()
|
||||
.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
.replace(/\//g, '-'),
|
||||
endTime: undefined,
|
||||
status: '0',
|
||||
collectionType: '1'
|
||||
};
|
||||
const data = reactive<PageData<StuColBatchForm, StuColBatchQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
batchName: undefined,
|
||||
status: undefined,
|
||||
collectionType: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '批次ID不能为空', trigger: 'blur' }],
|
||||
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
collectionType: [{ required: true, message: '收集类型 1 - 新学期收集 2 - 其他 不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询学生信息收集批次列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
console.log('queryParams.value.batchName', queryParams.value.batchName);
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getStudentAvailableBatches',
|
||||
method: 'get',
|
||||
params: {
|
||||
batchName: queryParams.value.batchName || ''
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
// 将返回的数据转换为表格需要的格式
|
||||
stuColBatchList.value = res.data || [];
|
||||
total.value = res.data ? res.data.length : 0;
|
||||
} else {
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError(res?.msg || '获取数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生可参与批次信息失败:', error);
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
stuColBatchFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StuColBatchVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StuColBatchVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getStuColBatch(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
stuColBatchFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StuColBatchVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除学生信息收集批次编号为"' + row?.batchName + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delStuColBatch(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'XgStuColBatch/stuColBatch/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`stuColBatch_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 收集范围按钮操作 */
|
||||
const handleScope = async (row: StuColBatchVO) => {
|
||||
currentBatch.value = row;
|
||||
// 先清空勾选状态
|
||||
checkedDeptIds.value = [];
|
||||
|
||||
await loadDeptTree();
|
||||
await loadCurrentScope(row.id);
|
||||
scopeDialog.visible = true;
|
||||
|
||||
// 使用nextTick确保DOM更新完成后再设置勾选状态
|
||||
await nextTick();
|
||||
if (scopeTreeRef.value && checkedDeptIds.value.length > 0) {
|
||||
scopeTreeRef.value.setCheckedKeys(checkedDeptIds.value);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载部门树数据 */
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId: 101
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
deptTreeData.value = response.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树数据失败:', error);
|
||||
ElMessage.error('获取范围选项失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载当前批次的收集范围 */
|
||||
const loadCurrentScope = async (batchId: string | number) => {
|
||||
try {
|
||||
checkedDeptIds.value = [];
|
||||
const response = await getScope(batchId);
|
||||
if (response && response.code === 200) {
|
||||
checkedDeptIds.value = response.data || [];
|
||||
} else {
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收集范围失败:', error);
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 构建树形数据 */
|
||||
const buildTree = (data: any[]) => {
|
||||
// 如果数据已经是树形结构,直接转换格式
|
||||
const convertToTreeNode = (item: any): any => {
|
||||
return {
|
||||
id: item.deptId,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map(convertToTreeNode) : []
|
||||
};
|
||||
};
|
||||
|
||||
return data.map(convertToTreeNode);
|
||||
};
|
||||
|
||||
/** 提交收集范围 */
|
||||
const submitScope = async () => {
|
||||
if (!currentBatch.value) return;
|
||||
|
||||
try {
|
||||
scopeButtonLoading.value = true;
|
||||
const checkedNodes = scopeTreeRef.value.getCheckedNodes();
|
||||
const checkedIds = checkedNodes.map((node: any) => node.id);
|
||||
|
||||
// 调用后端API设置收集范围
|
||||
const response = await setScope(currentBatch.value.id, checkedIds);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
ElMessage.success(response.msg || '收集范围设置成功');
|
||||
scopeDialog.visible = false;
|
||||
// 提交成功后清空状态
|
||||
handleScopeDialogClose();
|
||||
} else {
|
||||
ElMessage.error(response?.msg || '设置收集范围失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置收集范围失败:', error);
|
||||
ElMessage.error('设置收集范围失败');
|
||||
} finally {
|
||||
scopeButtonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消收集范围设置 */
|
||||
const cancelScope = () => {
|
||||
scopeDialog.visible = false;
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理收集范围弹窗关闭 */
|
||||
const handleScopeDialogClose = () => {
|
||||
// 清空相关状态
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详细按钮操作 */
|
||||
const handleView = async (row: StuColBatchVO) => {
|
||||
try {
|
||||
// 获取批次基本信息
|
||||
const res = await getStuColBatch(row?.batchId);
|
||||
viewForm.value = res.data;
|
||||
|
||||
// 获取权限范围详细信息
|
||||
// await loadScopeDetail(row.id);
|
||||
|
||||
viewDialog.visible = true;
|
||||
viewActiveTab.value = 'basic';
|
||||
} catch (error) {
|
||||
console.error('获取批次详情失败:', error);
|
||||
proxy?.$modal.msgError('获取批次详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载权限范围详细信息 */
|
||||
const loadScopeDetail = async (batchId: string | number) => {
|
||||
try {
|
||||
const response = await getScopeDetail(batchId);
|
||||
if (response && response.code === 200) {
|
||||
scopeDetailList.value = response.data || [];
|
||||
} else {
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载权限范围详情失败:', error);
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消查看详细 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
viewForm.value = {} as StuColBatchVO;
|
||||
scopeDetailList.value = [];
|
||||
viewActiveTab.value = 'basic';
|
||||
};
|
||||
|
||||
/** 信息完善按钮操作 */
|
||||
const handleInfoComplete = (row: StuColBatchVO) => {
|
||||
console.log('信息完善按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
stuMsgDialog.batchInfo = row;
|
||||
stuMsgDialog.title = `信息完善 - ${row.batchName}`;
|
||||
stuMsgDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 学生信息完善弹窗确认 */
|
||||
const handleStuMsgConfirm = (formData: any) => {
|
||||
console.log('学生信息完善提交:', formData);
|
||||
// TODO: 调用API保存学生信息
|
||||
ElMessage.success('学生信息完善成功');
|
||||
};
|
||||
|
||||
/** 关闭学生信息完善弹窗 */
|
||||
const handleStuMsgClose = () => {
|
||||
stuMsgDialog.visible = false;
|
||||
stuMsgDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 查看提交历史按钮操作 */
|
||||
const handleViewHistory = (row: StuColBatchVO) => {
|
||||
console.log('查看提交历史按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
|
||||
historyDialog.batchInfo = row;
|
||||
historyDialog.title = `学生信息提交历史 - ${row.batchName}`;
|
||||
historyDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 关闭学生信息提交历史详情弹窗 */
|
||||
const handleHistoryDialogClose = () => {
|
||||
historyDialog.visible = false;
|
||||
historyDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 检查批次是否有效(在有效时间内且状态正常) */
|
||||
const isBatchValid = (row: StuColBatchVO): boolean => {
|
||||
// 检查状态是否为正常
|
||||
if (row.status !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在有效时间内
|
||||
const now = new Date();
|
||||
const startTime = new Date(row.startTime);
|
||||
const endTime = new Date(row.endTime);
|
||||
|
||||
return now >= startTime && now <= endTime;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-detail-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,202 @@
|
||||
# 学生花名册管理功能实现说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
学生花名册管理界面用于展示和管理学生信息,支持高级复杂检索、导出、查看等操作。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
web/src/views/xg/xsjcxx/stuRoster/
|
||||
├── index.vue # 主界面文件
|
||||
├── components/ # 组件目录
|
||||
│ ├── AdvancedSearchDialog.vue # 高级检索弹窗
|
||||
│ └── StudentDetailDialog.vue # 学生详情弹窗
|
||||
├── test.vue # 测试页面
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 主界面 (index.vue)
|
||||
|
||||
#### 布局设计
|
||||
- **左右布局模式**:左侧固定宽高的部门树,右侧内容区域
|
||||
- **部门树功能**:
|
||||
- 支持纵向和横向滚动条
|
||||
- 支持多选功能
|
||||
- 支持搜索过滤
|
||||
- 清空选择功能
|
||||
|
||||
#### 检索功能
|
||||
- **基础检索**:学号、学生姓名、手机号码、状态四个检索条件
|
||||
- **高级检索**:弹出专门弹窗配置检索条件
|
||||
- **检索按钮**:搜索、重置、高级检索
|
||||
|
||||
#### 数据展示
|
||||
- **双视图模式**:列表视图和卡片视图
|
||||
- **列表视图**:标准表格展示,支持列显示控制
|
||||
- **卡片视图**:卡片式布局,每行4个卡片
|
||||
- **分页功能**:支持分页显示
|
||||
|
||||
#### 操作功能
|
||||
- **导出数据**:支持数据导出功能
|
||||
- **查看详情**:查看学生完整信息
|
||||
- **列控制**:通过right-toolbar控制列显示
|
||||
|
||||
### 2. 高级检索弹窗 (AdvancedSearchDialog.vue)
|
||||
|
||||
#### 检索字段覆盖
|
||||
支持 `xg_student_msg` 表中所有字段的检索功能:
|
||||
|
||||
**基础信息**
|
||||
- 身份证号、英文姓名、曾用名、性别
|
||||
- 出生日期(时间范围)、民族、籍贯、政治面貌
|
||||
- 户籍所在地、家庭地址、现住址、邮政编码
|
||||
- 电子邮箱、QQ号码、微信号、婚姻状况
|
||||
|
||||
**学籍信息**
|
||||
- 入学年份、入学日期(时间范围)、录取类别
|
||||
- 学制、学历层次、培养方式、年级、户口类型
|
||||
|
||||
**家庭信息**
|
||||
- 父亲信息:姓名、电话、工作单位
|
||||
- 母亲信息:姓名、电话、工作单位
|
||||
- 监护人信息:姓名、电话、关系
|
||||
- 经济信息:家庭年收入、人均收入、收入来源、经济状况、成员数量
|
||||
- 紧急联系人信息
|
||||
|
||||
**特殊标识**
|
||||
- 贫困生、少数民族、残疾、孤儿、留守儿童、独生子女、低保、助学贷款等标识
|
||||
- 宗教信仰、生源地区
|
||||
|
||||
**健康信息**
|
||||
- 身高、体重(数值范围)
|
||||
- 血型、健康状况、特殊体质、病史、过敏史
|
||||
- 残疾类型、残疾等级
|
||||
- 特长爱好、获奖情况、违纪记录、社会活动
|
||||
|
||||
**时间范围**
|
||||
- 创建时间、更新时间(时间范围)
|
||||
|
||||
#### 检索特性
|
||||
- **文本检索**:采用like模式
|
||||
- **下拉选择**:支持多选
|
||||
- **时间范围**:支持开始时间到结束时间的跨度检索
|
||||
- **数值范围**:支持最小值和最大值范围检索
|
||||
- **每行4个**:检索条件每4个放一行
|
||||
|
||||
### 3. 学生详情弹窗 (StudentDetailDialog.vue)
|
||||
|
||||
#### Tab页设计
|
||||
参考 `stuMsgDetailDialog.vue` 结构,分为5个Tab页:
|
||||
|
||||
**基础信息**
|
||||
- 学生照片和基本信息布局
|
||||
- 学号、身份证号、姓名、英文姓名、曾用名、性别
|
||||
- 出生日期、民族、籍贯、政治面貌
|
||||
- 手机号码、电子邮箱、QQ号码、微信号、婚姻状况
|
||||
- 宗教信仰、户籍所在地、家庭地址、现住址、邮政编码
|
||||
- 生源地区、毕业院校、户口类型
|
||||
|
||||
**学籍信息**
|
||||
- 学籍状态、入学年份、入学日期、录取类别
|
||||
- 学制、学历层次、培养方式、年级、是否住校
|
||||
- 学院、专业、班级、辅导员、班主任
|
||||
- 宿舍楼栋、宿舍房间号、银行卡号、开户银行
|
||||
|
||||
**家庭信息**
|
||||
- 父亲信息:姓名、电话、工作单位
|
||||
- 母亲信息:姓名、电话、工作单位
|
||||
- 监护人信息:姓名、电话、关系
|
||||
- 经济信息:家庭年收入、人均收入、收入来源、经济状况、成员数量
|
||||
- 紧急联系人信息
|
||||
|
||||
**特殊标识**
|
||||
- 各种特殊标识的状态展示(使用Tag标签)
|
||||
- 助学贷款金额、残疾类型、残疾等级
|
||||
|
||||
**健康信息**
|
||||
- 身高、体重、血型
|
||||
- 健康状况、特殊体质或疾病、病史、过敏史
|
||||
- 特长爱好、获奖情况、违纪记录、社会活动
|
||||
|
||||
#### 展示特性
|
||||
- **只读模式**:所有字段都是禁用状态,仅用于查看
|
||||
- **状态标签**:使用不同颜色的Tag标签展示状态
|
||||
- **照片展示**:学生照片展示
|
||||
- **加载状态**:数据加载时显示loading状态
|
||||
|
||||
### 4. 测试页面 (test.vue)
|
||||
|
||||
#### 测试功能
|
||||
- **基础功能测试**:查询条件输入和查询功能
|
||||
- **模拟数据展示**:表格形式展示模拟数据
|
||||
- **弹窗测试**:高级检索和详情弹窗的测试
|
||||
- **状态展示**:学生状态的Tag标签展示
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 数据模型
|
||||
基于 `xg_student_msg` 表结构,包含:
|
||||
- 基础信息字段(学号、姓名、性别等)
|
||||
- 学籍信息字段(状态、入学年份等)
|
||||
- 家庭信息字段(父母信息、经济状况等)
|
||||
- 特殊标识字段(贫困生、少数民族等)
|
||||
- 健康信息字段(身高、体重、病史等)
|
||||
|
||||
### 组件设计
|
||||
- **主界面组件**:负责整体布局和主要功能
|
||||
- **高级检索组件**:独立的弹窗组件,负责复杂检索
|
||||
- **详情展示组件**:独立的弹窗组件,负责信息展示
|
||||
- **测试组件**:用于功能测试和验证
|
||||
|
||||
### 状态管理
|
||||
- **查询参数**:基础查询参数和高级查询参数
|
||||
- **视图状态**:列表视图和卡片视图切换
|
||||
- **弹窗状态**:各种弹窗的显示控制
|
||||
- **选择状态**:部门树的多选状态
|
||||
|
||||
### 模拟数据
|
||||
目前使用模拟数据进行展示,包含:
|
||||
- 3个示例学生数据
|
||||
- 完整的字段信息
|
||||
- 不同的状态值
|
||||
|
||||
## 待完善功能
|
||||
|
||||
### API对接
|
||||
- 需要对接真实的学生信息API
|
||||
- 实现数据的增删改查功能
|
||||
- 实现导出功能
|
||||
|
||||
### 权限控制
|
||||
- 根据用户权限控制功能访问
|
||||
- 实现数据权限过滤
|
||||
|
||||
### 性能优化
|
||||
- 大数据量时的分页优化
|
||||
- 检索性能优化
|
||||
- 组件懒加载
|
||||
|
||||
### 用户体验
|
||||
- 更多的交互动画
|
||||
- 响应式布局优化
|
||||
- 移动端适配
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **访问路径**:`/xg/xsjcxx/stuRoster`
|
||||
2. **测试页面**:`/xg/xsjcxx/stuRoster/test`
|
||||
3. **主要功能**:
|
||||
- 左侧选择部门
|
||||
- 右侧进行检索
|
||||
- 查看学生详情
|
||||
- 导出数据
|
||||
|
||||
## 开发规范
|
||||
|
||||
- 遵循Vue 3 Composition API规范
|
||||
- 使用TypeScript进行类型检查
|
||||
- 遵循Element Plus设计规范
|
||||
- 组件化开发,保持代码可维护性
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,891 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="1100px" append-to-body @close="handleClose">
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
type="border-card"
|
||||
v-loading="dialogLoading"
|
||||
element-loading-text="正在加载数据,请稍候..."
|
||||
element-loading-background="rgba(0, 0, 0, 0.3)"
|
||||
>
|
||||
<!-- 基础信息Tab -->
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form ref="studentFormRef" :model="form" label-width="100px">
|
||||
<!-- 学生照片和基本信息布局 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="请输入学号" maxlength="32" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" maxlength="50" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="英文姓名" prop="nameEnglish">
|
||||
<el-input v-model="form.nameEnglish" placeholder="请输入英文姓名" maxlength="100" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="曾用名" prop="formerName">
|
||||
<el-input v-model="form.formerName" placeholder="请输入曾用名" maxlength="50" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="form.gender" placeholder="请选择性别" disabled>
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="form.birthDate"
|
||||
type="date"
|
||||
placeholder="请选择出生日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="民族" prop="nationality">
|
||||
<el-input v-model="form.nationality" placeholder="请输入民族" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="籍贯" prop="nativePlace">
|
||||
<el-input v-model="form.nativePlace" placeholder="请输入籍贯" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="政治面貌" prop="politicalStatus">
|
||||
<el-input v-model="form.politicalStatus" placeholder="请输入政治面貌" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="电子邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入电子邮箱" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="QQ号码" prop="qq">
|
||||
<el-input v-model="form.qq" placeholder="请输入QQ号码" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信号" prop="wechat">
|
||||
<el-input v-model="form.wechat" placeholder="请输入微信号" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="婚姻状况" prop="maritalStatus">
|
||||
<el-select v-model="form.maritalStatus" placeholder="请选择婚姻状况" disabled>
|
||||
<el-option v-for="dict in marital_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="宗教信仰" prop="religion">
|
||||
<el-input v-model="form.religion" placeholder="请输入宗教信仰" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item prop="photoUrl">
|
||||
<template #label>
|
||||
<div class="photo-label-container">
|
||||
<span>学生照片</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="photo-container">
|
||||
<el-avatar :size="120" :src="form.photoUrl || '/src/assets/images/profile.jpg'" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 地址信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="户籍所在地" prop="hukouLocation">
|
||||
<el-input v-model="form.hukouLocation" type="textarea" :rows="2" placeholder="请输入户籍所在地" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭地址" prop="homeAddress">
|
||||
<el-input v-model="form.homeAddress" type="textarea" :rows="2" placeholder="请输入家庭地址" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="现住址" prop="currentAddress">
|
||||
<el-input v-model="form.currentAddress" type="textarea" :rows="2" placeholder="请输入现住址" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="邮政编码" prop="postalCode">
|
||||
<el-input v-model="form.postalCode" placeholder="请输入邮政编码" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="生源地区" prop="sourceRegion">
|
||||
<el-input v-model="form.sourceRegion" placeholder="请输入生源地区" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="毕业院校" prop="graduateSchool">
|
||||
<el-input v-model="form.graduateSchool" placeholder="请输入毕业院校" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="户口类型" prop="householdRegisterType">
|
||||
<el-input v-model="form.householdRegisterType" placeholder="请输入户口类型" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 学籍信息Tab -->
|
||||
<el-tab-pane label="学籍信息" name="academic">
|
||||
<el-form ref="academicFormRef" :model="form" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="学籍状态" prop="studentStatus">
|
||||
<el-tag :type="getStatusType(form.studentStatus)">
|
||||
{{ getStudentStatusLabel(form.studentStatus) }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入学年份" prop="enrollmentYear">
|
||||
<el-input v-model="form.enrollmentYear" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入学日期" prop="enrollmentDate">
|
||||
<el-date-picker
|
||||
v-model="form.enrollmentDate"
|
||||
type="date"
|
||||
placeholder="请选择入学日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="录取类别" prop="admissionCategory">
|
||||
<el-input v-model="form.admissionCategory" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="学制" prop="schoolSystem">
|
||||
<el-input v-model="form.schoolSystem" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="学历层次" prop="academicLevel">
|
||||
<el-input v-model="form.academicLevel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="培养方式" prop="trainingType">
|
||||
<el-input v-model="form.trainingType" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="年级" prop="grade">
|
||||
<el-input v-model="form.grade" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否住校" prop="isBoarder">
|
||||
<el-tag :type="form.isBoarder === '1' ? 'success' : 'info'">
|
||||
{{ form.isBoarder === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="学院" prop="collegeName">
|
||||
<el-input v-model="form.collegeName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-input v-model="form.majorName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="form.className" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="辅导员" prop="counselorName">
|
||||
<el-input v-model="form.counselorName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="班主任" prop="classTeacherName">
|
||||
<el-input v-model="form.classTeacherName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="宿舍楼栋" prop="dormitoryBuilding">
|
||||
<el-input v-model="form.dormitoryBuilding" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="宿舍房间号" prop="dormitoryRoom">
|
||||
<el-input v-model="form.dormitoryRoom" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="银行卡号" prop="bankCardNo">
|
||||
<el-input v-model="form.bankCardNo" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="开户银行" prop="bankName">
|
||||
<el-input v-model="form.bankName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 家庭信息Tab -->
|
||||
<el-tab-pane label="家庭信息" name="family">
|
||||
<el-form ref="familyFormRef" :model="form" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="父亲姓名" prop="fatherName">
|
||||
<el-input v-model="form.fatherName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="父亲电话" prop="fatherPhone">
|
||||
<el-input v-model="form.fatherPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="父亲工作单位" prop="fatherWorkUnit">
|
||||
<el-input v-model="form.fatherWorkUnit" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="母亲姓名" prop="motherName">
|
||||
<el-input v-model="form.motherName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="母亲电话" prop="motherPhone">
|
||||
<el-input v-model="form.motherPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="母亲工作单位" prop="motherWorkUnit">
|
||||
<el-input v-model="form.motherWorkUnit" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="监护人姓名" prop="guardianName">
|
||||
<el-input v-model="form.guardianName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="监护人电话" prop="guardianPhone">
|
||||
<el-input v-model="form.guardianPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="监护人关系" prop="guardianRelation">
|
||||
<el-input v-model="form.guardianRelation" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="家庭年收入" prop="familyIncome">
|
||||
<el-input v-model="form.familyIncome" disabled>
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="人均收入" prop="perCapitaIncome">
|
||||
<el-input v-model="form.perCapitaIncome" disabled>
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="收入来源" prop="incomeSource">
|
||||
<el-input v-model="form.incomeSource" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="家庭经济状况" prop="familyEconomicStatus">
|
||||
<el-input v-model="form.familyEconomicStatus" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="家庭成员数量" prop="familyMembers">
|
||||
<el-input v-model="form.familyMembers" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="紧急联系人" prop="emergencyContactName">
|
||||
<el-input v-model="form.emergencyContactName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="紧急联系人电话" prop="emergencyContactPhone">
|
||||
<el-input v-model="form.emergencyContactPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="紧急联系人关系" prop="emergencyContactRelation">
|
||||
<el-input v-model="form.emergencyContactRelation" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 特殊标识Tab -->
|
||||
<el-tab-pane label="特殊标识" name="special">
|
||||
<el-form ref="specialFormRef" :model="form" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否贫困生" prop="isPoorStudent">
|
||||
<el-tag :type="form.isPoorStudent === '1' ? 'warning' : 'info'">
|
||||
{{ form.isPoorStudent === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否少数民族" prop="isMinority">
|
||||
<el-tag :type="form.isMinority === '1' ? 'success' : 'info'">
|
||||
{{ form.isMinority === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否残疾" prop="isDisabled">
|
||||
<el-tag :type="form.isDisabled === '1' ? 'danger' : 'info'">
|
||||
{{ form.isDisabled === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否孤儿" prop="isOrphan">
|
||||
<el-tag :type="form.isOrphan === '1' ? 'danger' : 'info'">
|
||||
{{ form.isOrphan === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否留守儿童" prop="isLeftBehindChild">
|
||||
<el-tag :type="form.isLeftBehindChild === '1' ? 'warning' : 'info'">
|
||||
{{ form.isLeftBehindChild === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否独生子女" prop="isSingleChild">
|
||||
<el-tag :type="form.isSingleChild === '1' ? 'success' : 'info'">
|
||||
{{ form.isSingleChild === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否低保" prop="isLowIncome">
|
||||
<el-tag :type="form.isLowIncome === '1' ? 'warning' : 'info'">
|
||||
{{ form.isLowIncome === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="是否有助学贷款" prop="hasLoan">
|
||||
<el-tag :type="form.hasLoan === '1' ? 'warning' : 'info'">
|
||||
{{ form.hasLoan === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="助学贷款金额" prop="loanAmount">
|
||||
<el-input v-model="form.loanAmount" disabled>
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="残疾类型" prop="disabilityType">
|
||||
<el-input v-model="form.disabilityType" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="残疾等级" prop="disabilityLevel">
|
||||
<el-input v-model="form.disabilityLevel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 健康信息Tab -->
|
||||
<el-tab-pane label="健康信息" name="health">
|
||||
<el-form ref="healthFormRef" :model="form" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="身高" prop="height">
|
||||
<el-input v-model="form.height" disabled>
|
||||
<template #append>cm</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="体重" prop="weight">
|
||||
<el-input v-model="form.weight" disabled>
|
||||
<template #append>kg</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="血型" prop="bloodType">
|
||||
<el-input v-model="form.bloodType" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="健康状况" prop="healthStatus">
|
||||
<el-input v-model="form.healthStatus" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="特殊体质或疾病" prop="specialCondition">
|
||||
<el-input v-model="form.specialCondition" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="病史" prop="medicalHistory">
|
||||
<el-input v-model="form.medicalHistory" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="过敏史" prop="allergies">
|
||||
<el-input v-model="form.allergies" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="特长爱好" prop="specialties">
|
||||
<el-input v-model="form.specialties" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="获奖情况" prop="awards">
|
||||
<el-input v-model="form.awards" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="违纪记录" prop="violations">
|
||||
<el-input v-model="form.violations" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="社会活动" prop="socialActivities">
|
||||
<el-input v-model="form.socialActivities" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
studentId: number | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
studentId: null
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
const dialogTitle = ref('学生详情');
|
||||
const activeTab = ref('basic');
|
||||
const dialogLoading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
// 基础信息
|
||||
id: null as number | null,
|
||||
studentNo: '',
|
||||
idCard: '',
|
||||
name: '',
|
||||
nameEnglish: '',
|
||||
formerName: '',
|
||||
gender: '',
|
||||
birthDate: '',
|
||||
nationality: '',
|
||||
nativePlace: '',
|
||||
politicalStatus: '',
|
||||
hukouLocation: '',
|
||||
homeAddress: '',
|
||||
currentAddress: '',
|
||||
postalCode: '',
|
||||
photoUrl: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
qq: '',
|
||||
wechat: '',
|
||||
maritalStatus: '',
|
||||
religion: '',
|
||||
sourceRegion: '',
|
||||
graduateSchool: '',
|
||||
householdRegisterType: '',
|
||||
|
||||
// 学籍信息
|
||||
studentStatus: '',
|
||||
enrollmentYear: '',
|
||||
enrollmentDate: '',
|
||||
admissionCategory: '',
|
||||
schoolSystem: '',
|
||||
academicLevel: '',
|
||||
trainingType: '',
|
||||
grade: '',
|
||||
isBoarder: '',
|
||||
collegeName: '',
|
||||
majorName: '',
|
||||
className: '',
|
||||
counselorName: '',
|
||||
classTeacherName: '',
|
||||
dormitoryBuilding: '',
|
||||
dormitoryRoom: '',
|
||||
bankCardNo: '',
|
||||
bankName: '',
|
||||
|
||||
// 家庭信息
|
||||
fatherName: '',
|
||||
fatherPhone: '',
|
||||
fatherWorkUnit: '',
|
||||
motherName: '',
|
||||
motherPhone: '',
|
||||
motherWorkUnit: '',
|
||||
guardianName: '',
|
||||
guardianPhone: '',
|
||||
guardianRelation: '',
|
||||
familyIncome: '',
|
||||
perCapitaIncome: '',
|
||||
incomeSource: '',
|
||||
familyEconomicStatus: '',
|
||||
familyMembers: '',
|
||||
emergencyContactName: '',
|
||||
emergencyContactPhone: '',
|
||||
emergencyContactRelation: '',
|
||||
|
||||
// 特殊标识
|
||||
isPoorStudent: '',
|
||||
isMinority: '',
|
||||
isDisabled: '',
|
||||
isOrphan: '',
|
||||
isLeftBehindChild: '',
|
||||
isSingleChild: '',
|
||||
isLowIncome: '',
|
||||
hasLoan: '',
|
||||
loanAmount: '',
|
||||
disabilityType: '',
|
||||
disabilityLevel: '',
|
||||
|
||||
// 健康信息
|
||||
height: '',
|
||||
weight: '',
|
||||
bloodType: '',
|
||||
healthStatus: '',
|
||||
specialCondition: '',
|
||||
medicalHistory: '',
|
||||
allergies: '',
|
||||
specialties: '',
|
||||
awards: '',
|
||||
violations: '',
|
||||
socialActivities: ''
|
||||
});
|
||||
|
||||
// 字典数据
|
||||
const sys_user_sex = ref([
|
||||
{ value: '0', label: '男' },
|
||||
{ value: '1', label: '女' }
|
||||
]);
|
||||
|
||||
const marital_status = ref([
|
||||
{ value: '1', label: '未婚' },
|
||||
{ value: '2', label: '已婚' },
|
||||
{ value: '3', label: '离异' },
|
||||
{ value: '4', label: '丧偶' }
|
||||
]);
|
||||
|
||||
// 模拟数据
|
||||
const mockStudentData = {
|
||||
id: 1,
|
||||
studentNo: '2021001',
|
||||
idCard: '110101200001011234',
|
||||
name: '张三',
|
||||
nameEnglish: 'Zhang San',
|
||||
formerName: '',
|
||||
gender: '0',
|
||||
birthDate: '2000-01-01',
|
||||
nationality: '汉族',
|
||||
nativePlace: '北京市',
|
||||
politicalStatus: '共青团员',
|
||||
hukouLocation: '北京市朝阳区',
|
||||
homeAddress: '北京市朝阳区某某街道某某小区',
|
||||
currentAddress: '北京市海淀区某某大学宿舍',
|
||||
postalCode: '100000',
|
||||
photoUrl: '',
|
||||
phone: '13800138001',
|
||||
email: 'zhangsan@example.com',
|
||||
qq: '123456789',
|
||||
wechat: 'zhangsan123',
|
||||
maritalStatus: '1',
|
||||
religion: '',
|
||||
sourceRegion: '北京市',
|
||||
graduateSchool: '北京市第一中学',
|
||||
householdRegisterType: '非农业',
|
||||
|
||||
// 学籍信息
|
||||
studentStatus: '1',
|
||||
enrollmentYear: '2021',
|
||||
enrollmentDate: '2021-09-01',
|
||||
admissionCategory: '统招',
|
||||
schoolSystem: '4',
|
||||
academicLevel: '本科',
|
||||
trainingType: '全日制',
|
||||
grade: '2021',
|
||||
isBoarder: '1',
|
||||
collegeName: '计算机学院',
|
||||
majorName: '软件工程',
|
||||
className: '软件2101班',
|
||||
counselorName: '李老师',
|
||||
classTeacherName: '王老师',
|
||||
dormitoryBuilding: '1号楼',
|
||||
dormitoryRoom: '101',
|
||||
bankCardNo: '6222021234567890123',
|
||||
bankName: '中国工商银行',
|
||||
|
||||
// 家庭信息
|
||||
fatherName: '张父',
|
||||
fatherPhone: '13900139001',
|
||||
fatherWorkUnit: '某某公司',
|
||||
motherName: '张母',
|
||||
motherPhone: '13900139002',
|
||||
motherWorkUnit: '某某单位',
|
||||
guardianName: '张父',
|
||||
guardianPhone: '13900139001',
|
||||
guardianRelation: '父亲',
|
||||
familyIncome: '120000',
|
||||
perCapitaIncome: '40000',
|
||||
incomeSource: '工资收入',
|
||||
familyEconomicStatus: '一般',
|
||||
familyMembers: '3',
|
||||
emergencyContactName: '张父',
|
||||
emergencyContactPhone: '13900139001',
|
||||
emergencyContactRelation: '父亲',
|
||||
|
||||
// 特殊标识
|
||||
isPoorStudent: '0',
|
||||
isMinority: '0',
|
||||
isDisabled: '0',
|
||||
isOrphan: '0',
|
||||
isLeftBehindChild: '0',
|
||||
isSingleChild: '1',
|
||||
isLowIncome: '0',
|
||||
hasLoan: '0',
|
||||
loanAmount: '0',
|
||||
disabilityType: '',
|
||||
disabilityLevel: '',
|
||||
|
||||
// 健康信息
|
||||
height: '175',
|
||||
weight: '65',
|
||||
bloodType: 'A型',
|
||||
healthStatus: '良好',
|
||||
specialCondition: '',
|
||||
medicalHistory: '',
|
||||
allergies: '',
|
||||
specialties: '篮球、编程',
|
||||
awards: '校级优秀学生',
|
||||
violations: '',
|
||||
socialActivities: '学生会成员、志愿者'
|
||||
};
|
||||
|
||||
// 方法
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'1': 'success',
|
||||
'2': 'warning',
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
};
|
||||
|
||||
const getStudentStatusLabel = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'1': '在籍',
|
||||
'2': '休学',
|
||||
'3': '复学',
|
||||
'4': '退学',
|
||||
'5': '毕业'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
const loadStudentData = async (studentId: number) => {
|
||||
dialogLoading.value = true;
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 使用模拟数据
|
||||
Object.assign(form, mockStudentData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载学生数据失败:', error);
|
||||
} finally {
|
||||
dialogLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听studentId变化
|
||||
watch(() => props.studentId, (newId) => {
|
||||
if (newId && dialogVisible.value) {
|
||||
loadStudentData(newId);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible && props.studentId) {
|
||||
loadStudentData(props.studentId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.photo-label-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.photo-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 120px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="mode === 'add' ? '新增' + getTypeLabel(type) : '编辑' + getTypeLabel(type)"
|
||||
v-model="visible"
|
||||
width="500px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-select
|
||||
v-model="form.name"
|
||||
filterable
|
||||
placeholder="请选择教师姓名"
|
||||
@change="handleTeacherChange"
|
||||
:disabled="mode === 'edit'"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="item in teacherOptions" :key="item.id" :label="item.managerName" :value="item.managerName">
|
||||
<div>
|
||||
<span> {{ item.managerName }} </span>
|
||||
<span style="color: #8492a6; font-size: 13px"> ({{ item.phone }}) ({{ item.userId }}) </span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="form.sex">
|
||||
<el-radio :label="0">男</el-radio>
|
||||
<el-radio :label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watchEffect, computed, onMounted } from 'vue';
|
||||
import { ElMessage, type FormInstance } from 'element-plus';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 教师信息数据结构
|
||||
*/
|
||||
interface TeacherData {
|
||||
/** 教师ID */
|
||||
userId: string | number;
|
||||
/** 管理员姓名 */
|
||||
managerName: string;
|
||||
/** 手机号码 */
|
||||
phone: string;
|
||||
/** 性别(0男 1女) */
|
||||
gender: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的教职工信息列表
|
||||
* @param managerType 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
|
||||
*/
|
||||
const listAvailableTeachers = (managerType: string) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/listAvailableTeachers',
|
||||
method: 'get',
|
||||
params: { managerType }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增宿舍管理员信息
|
||||
*/
|
||||
const addStudentManager = (data: any) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改宿舍管理员信息
|
||||
*/
|
||||
const updateStudentManager = (data: any) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 在setup函数中引入这种方式提示弹窗
|
||||
const message = (type: 'success' | 'error' | 'info', text: string) => {
|
||||
if (type === 'success') {
|
||||
ElMessage.success(text);
|
||||
} else if (type === 'error') {
|
||||
ElMessage.error(text);
|
||||
} else {
|
||||
ElMessage.info(text);
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'school'
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit']);
|
||||
|
||||
// 教师选项列表
|
||||
const teacherOptions = ref<TeacherData[]>([]);
|
||||
|
||||
// 计算属性:对话框可见性
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
// 表单和校验
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
id: null,
|
||||
userId: null,
|
||||
name: '',
|
||||
phone: '',
|
||||
sex: 0,
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请选择姓名', trigger: 'change' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
// 获取管理员类型对应的值
|
||||
const getManagerType = (type: string): string => {
|
||||
switch (type) {
|
||||
case 'school':
|
||||
return '1'; // 全校管理员
|
||||
case 'custom':
|
||||
return '2'; // 自定义管理员
|
||||
case 'teacher':
|
||||
return '3'; // 班主任
|
||||
default:
|
||||
return '1';
|
||||
}
|
||||
};
|
||||
|
||||
// 加载可用教师列表
|
||||
const loadTeachers = async () => {
|
||||
try {
|
||||
const managerType = getManagerType(props.type);
|
||||
const res = await listAvailableTeachers(managerType);
|
||||
if (res.data) {
|
||||
teacherOptions.value = res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取可用教师列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 选择教师后的处理
|
||||
const handleTeacherChange = (value: string) => {
|
||||
const selectedTeacher = teacherOptions.value.find((item) => item.managerName === value);
|
||||
if (selectedTeacher) {
|
||||
console.log(selectedTeacher.gender === '0');
|
||||
// 自动填充手机号码和性别
|
||||
form.phone = selectedTeacher.phone || '';
|
||||
form.sex = selectedTeacher.gender === '0' ? 0 : 1; // 0男 1女
|
||||
form.userId = selectedTeacher.userId;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听传入的admin数据,更新表单
|
||||
watchEffect(() => {
|
||||
if (props.admin) {
|
||||
form.id = props.admin.id;
|
||||
form.userId = props.admin.userId;
|
||||
form.name = props.admin.managerName;
|
||||
form.phone = props.admin.phone;
|
||||
form.sex = props.admin.gender === '0' ? 0 : 1;
|
||||
form.remark = props.admin.remark;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听对话框可见性变化,当打开时加载教师列表
|
||||
watchEffect(() => {
|
||||
if (visible.value && props.mode === 'add') {
|
||||
loadTeachers();
|
||||
}
|
||||
});
|
||||
|
||||
// 组件挂载时加载教师列表
|
||||
onMounted(() => {
|
||||
if (props.mode === 'add') {
|
||||
loadTeachers();
|
||||
}
|
||||
});
|
||||
|
||||
// 根据类型获取标签文本
|
||||
function getTypeLabel(type: string): string {
|
||||
switch (type) {
|
||||
case 'school':
|
||||
return '全校管理员';
|
||||
case 'custom':
|
||||
return '自定义管理员';
|
||||
case 'teacher':
|
||||
return '班主任';
|
||||
default:
|
||||
return '管理员';
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
formRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const formData = {
|
||||
id: form.id,
|
||||
managerName: form.name,
|
||||
userId: form.userId,
|
||||
phone: form.phone,
|
||||
gender: form.sex,
|
||||
remark: form.remark,
|
||||
status: '0',
|
||||
// scope: props.admin?.scope || '',
|
||||
managerType: getManagerType(props.type),
|
||||
managerLevel: getManagerType(props.type)
|
||||
};
|
||||
|
||||
let result;
|
||||
if (props.mode === 'add') {
|
||||
// 调用添加接口
|
||||
result = await addStudentManager(formData);
|
||||
} else {
|
||||
// 调用更新接口
|
||||
result = await updateStudentManager(formData);
|
||||
}
|
||||
|
||||
if (result.code === 200) {
|
||||
message('success', props.mode === 'add' ? '添加成功' : '更新成功');
|
||||
// 通知父组件处理成功
|
||||
emit('submit', formData);
|
||||
visible.value = false;
|
||||
} else {
|
||||
message('error', result.msg || '操作失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message('error', '操作失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 取消
|
||||
function cancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({
|
||||
submitForm,
|
||||
cancel
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<el-dialog :title="getScopeDialogTitle(type)" v-model="visible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item :label="getScopeName(type)">
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="type === 'custom'">
|
||||
<el-tree
|
||||
:data="scopeOptions.custom"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
:default-checked-keys="form.selectedScopes"
|
||||
:props="{ label: 'label', children: 'children' }"
|
||||
@check="onTreeCheck"
|
||||
default-expand-all
|
||||
style="
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
/* border: 1px solid #ebeef5; */
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="type === 'teacher'">
|
||||
<el-checkbox-group v-model="form.selectedScopes">
|
||||
<el-checkbox v-for="item in scopeOptions.teacher" :key="item.id" :label="item.id">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="loading">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import axios from 'axios';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'custom'
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit']);
|
||||
|
||||
// 计算属性:对话框可见性
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
// 范围选项(从API获取)
|
||||
const scopeOptions = reactive({
|
||||
custom: [] as any[],
|
||||
teacher: [] as any[]
|
||||
});
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
selectedScopes: [] as number[]
|
||||
});
|
||||
|
||||
// 获取部门树数据
|
||||
async function fetchDeptTree() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId: 101
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
// custom类型直接使用树形结构
|
||||
if (props.type === 'custom') {
|
||||
scopeOptions.custom = response.data || [];
|
||||
} else if (props.type === 'teacher') {
|
||||
// teacher类型仍可用扁平化
|
||||
scopeOptions.teacher = flattenTreeData(response.data || []);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树数据失败:', error);
|
||||
ElMessage.error('获取范围选项失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 将树形数据扁平化
|
||||
function flattenTreeData(treeData: any[]): any[] {
|
||||
const result: any[] = [];
|
||||
|
||||
function traverse(nodes: any[]) {
|
||||
nodes.forEach((node) => {
|
||||
result.push({
|
||||
id: node.id,
|
||||
label: node.label
|
||||
});
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
traverse(node.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
traverse(treeData);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 新增:获取历史范围数据
|
||||
async function fetchSelectedScopes(managerId: number | string) {
|
||||
if (!managerId) return;
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/list',
|
||||
method: 'get',
|
||||
params: { managerId }
|
||||
});
|
||||
if (res && res.code === 200) {
|
||||
// 提取rows中的authSource字段作为已选范围ID
|
||||
form.selectedScopes = (res.rows || []).map((item: any) => item.authSource);
|
||||
}
|
||||
} catch (e) {
|
||||
form.selectedScopes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 监听对话框显示状态,自动加载数据
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
const managerId = props.admin?.managerId || props.admin?.id;
|
||||
await fetchDeptTree();
|
||||
await fetchSelectedScopes(managerId);
|
||||
} else {
|
||||
// 对话框关闭时清空选择
|
||||
form.selectedScopes = [];
|
||||
}
|
||||
});
|
||||
|
||||
// el-tree勾选事件,确保能正确获取所有勾选节点的id
|
||||
function onTreeCheck(checkedNodes: any, info: { checkedKeys: any[]; halfCheckedKeys: any[] }) {
|
||||
form.selectedScopes = info.checkedKeys;
|
||||
}
|
||||
|
||||
// 获取范围对话框标题
|
||||
function getScopeDialogTitle(type: string) {
|
||||
const name = props.admin?.managerName || '';
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return `为【${name}】分配管理范围`;
|
||||
case 'teacher':
|
||||
return `为【${name}】分配班级`;
|
||||
default:
|
||||
return `为【${name}】分配范围`;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取范围名称
|
||||
function getScopeName(type: string) {
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return '管理范围';
|
||||
case 'teacher':
|
||||
return '所管理班级';
|
||||
default:
|
||||
return '管理范围';
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function submitForm() {
|
||||
// 组装要提交的数据
|
||||
const selectedIds = form.selectedScopes;
|
||||
console.log(selectedIds);
|
||||
const managerId = props.admin?.managerId || props.admin?.id; // 兼容不同字段
|
||||
if (!managerId) {
|
||||
ElMessage.error('管理员ID缺失,无法提交');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await request({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/grantDeptAuth',
|
||||
method: 'post',
|
||||
params: { managerId },
|
||||
data: selectedIds
|
||||
});
|
||||
ElMessage.success('分配成功');
|
||||
visible.value = false;
|
||||
emit('submit', {
|
||||
sourceIds: selectedIds
|
||||
});
|
||||
} catch (error) {
|
||||
ElMessage.error('分配失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
function cancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 从props获取类型的响应式引用
|
||||
const type = computed(() => props.type);
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({
|
||||
submitForm,
|
||||
cancel,
|
||||
fetchDeptTree
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.el-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.el-checkbox {
|
||||
margin-right: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #909399;
|
||||
}
|
||||
.loading-container .el-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
+141
-88
@@ -8,7 +8,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form :inline="true" :model="schoolSearchForm" class="search-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="schoolSearchForm.name" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('school')" />
|
||||
<el-input v-model="schoolSearchForm.managerName" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('school')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码">
|
||||
<el-input v-model="schoolSearchForm.phone" placeholder="请输入手机号码" clearable @keyup.enter="handleSearch('school')" />
|
||||
@@ -30,11 +30,11 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table :data="filteredAdmins('school')" border>
|
||||
<el-table-column prop="name" label="姓名" align="center" />
|
||||
<el-table-column prop="managerName" label="姓名" align="center" />
|
||||
<el-table-column prop="phone" label="手机号码" align="center" />
|
||||
<el-table-column prop="sex" label="性别" align="center">
|
||||
<el-table-column prop="gender" label="性别" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.sex === 0 ? '男' : '女' }}
|
||||
{{ scope.row.gender == 0 ? '男' : '女' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" align="center" />
|
||||
@@ -57,13 +57,13 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form :inline="true" :model="customSearchForm" class="search-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="customSearchForm.name" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('custom')" />
|
||||
<el-input v-model="customSearchForm.managerName" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('custom')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码">
|
||||
<el-input v-model="customSearchForm.phone" placeholder="请输入手机号码" clearable @keyup.enter="handleSearch('custom')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleSearch('custom')">搜索</el-button>
|
||||
<el-button type="primary" icon="Search" @click="fetchCustomAdminsWithScope('custom')">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset('custom')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -78,14 +78,30 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table :data="filteredAdmins('custom')" border>
|
||||
<el-table-column prop="name" label="姓名" align="center" />
|
||||
<el-table-column prop="managerName" label="姓名" align="center" />
|
||||
<el-table-column prop="phone" label="手机号码" align="center" />
|
||||
<el-table-column prop="sex" label="性别" align="center">
|
||||
<el-table-column prop="gender" label="性别" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.sex === 0 ? '男' : '女' }}
|
||||
{{ scope.row.gender == 0 ? '男' : '女' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scope" label="管理范围" align="center">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
v-if="scope.row.authSourceNames && scope.row.authSourceNames.length > 2"
|
||||
:content="scope.row.authSourceNames.join(',')"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
{{ scope.row.authSourceNames.slice(0, 2).join(',')
|
||||
}}{{ scope.row.authSourceNames.length > 2 ? ' 等' + scope.row.authSourceNames.length + '个' : '' }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
{{ scope.row.authSourceNames?.join(',') || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scope" label="管理范围" align="center" />
|
||||
<el-table-column prop="remark" label="备注" align="center" />
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
@@ -109,7 +125,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form :inline="true" :model="teacherSearchForm" class="search-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="teacherSearchForm.name" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('teacher')" />
|
||||
<el-input v-model="teacherSearchForm.managerName" placeholder="请输入姓名" clearable @keyup.enter="handleSearch('teacher')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码">
|
||||
<el-input v-model="teacherSearchForm.phone" placeholder="请输入手机号码" clearable @keyup.enter="handleSearch('teacher')" />
|
||||
@@ -122,13 +138,13 @@
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<!-- <template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="onAdd('teacher')">新增班主任</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</template> -->
|
||||
<el-table :data="filteredAdmins('teacher')" border>
|
||||
<el-table-column prop="name" label="姓名" align="center" />
|
||||
<el-table-column prop="phone" label="手机号码" align="center" />
|
||||
@@ -137,9 +153,9 @@
|
||||
{{ scope.row.sex === 0 ? '男' : '女' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scope" label="所管理班级" align="center" />
|
||||
<el-table-column prop="className" label="所管理班级" align="center" />
|
||||
<el-table-column prop="remark" label="备注" align="center" />
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
|
||||
<!-- <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button link type="primary" plain icon="Edit" @click="onEdit('teacher', scope.row)" />
|
||||
@@ -151,7 +167,7 @@
|
||||
<el-button link type="danger" plain icon="Delete" @click="onDelete('teacher', scope.row)" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
@@ -163,23 +179,35 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import AdminDialog from './components/AdminDialog.vue';
|
||||
import ScopeDialog from './components/ScopeDialog.vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { listStudentManager, delStudentManager, listActiveClassTeachers } from '@/api/xg/xsjcxx/xgStudentManager/studentManager';
|
||||
import type { StudentManagerQuery } from '@/api/xg/xsjcxx/xgStudentManager/studentManager/types';
|
||||
import service from '@/utils/request';
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({});
|
||||
|
||||
// 导入改为使用模拟数据
|
||||
// import { getAdminList, deleteAdmin, assignScope, getScopeNames } from '@/api/studentMsg/authmsg';
|
||||
|
||||
const activeTab = ref('school');
|
||||
|
||||
// 各tab独立检索表单
|
||||
const schoolSearchForm = reactive({ name: '', phone: '' });
|
||||
const customSearchForm = reactive({ name: '', phone: '' });
|
||||
const teacherSearchForm = reactive({ name: '', phone: '' });
|
||||
const schoolSearchForm = reactive<StudentManagerQuery>({
|
||||
managerName: '',
|
||||
phone: '',
|
||||
managerType: '1',
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
const customSearchForm = reactive<StudentManagerQuery>({
|
||||
managerName: '',
|
||||
phone: '',
|
||||
managerType: '2',
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
const teacherSearchForm = reactive({ managerName: '', phone: '' });
|
||||
|
||||
// 管理员数据
|
||||
const schoolAdmins = ref([]);
|
||||
@@ -192,46 +220,38 @@ const currentAdmin = ref<any>(null);
|
||||
const scopeDialogVisible = ref(false);
|
||||
const scopeType = ref('');
|
||||
|
||||
// 模拟数据
|
||||
const mockData = {
|
||||
school: [
|
||||
{ id: 1, name: '张校长', phone: '13800138001', sex: 0, remark: '全校管理员' },
|
||||
{ id: 2, name: '李主任', phone: '13800138002', sex: 1, remark: '教务处负责人' }
|
||||
],
|
||||
custom: [
|
||||
{ id: 3, name: '王组长', phone: '13800138003', sex: 0, scope: '高一年级', remark: '年级组长' },
|
||||
{ id: 4, name: '赵主管', phone: '13800138004', sex: 1, scope: '高二年级', remark: '年级负责人' }
|
||||
],
|
||||
teacher: [
|
||||
{ id: 5, name: '刘老师', phone: '13800138005', sex: 0, scope: '高一(1)班', remark: '数学教师' },
|
||||
{ id: 6, name: '陈老师', phone: '13800138006', sex: 1, scope: '高一(2)班', remark: '语文教师' }
|
||||
]
|
||||
};
|
||||
// 班主任模拟数据 (预留,后续完善)
|
||||
const mockTeacherData = [
|
||||
{ id: 5, name: '刘老师', phone: '13800138005', sex: 0, scope: '高一(1)班', remark: '数学教师' },
|
||||
{ id: 6, name: '陈老师', phone: '13800138006', sex: 1, scope: '高一(2)班', remark: '语文教师' }
|
||||
];
|
||||
|
||||
// 获取管理员列表
|
||||
async function fetchAdmins(type: string) {
|
||||
// 实际项目中这里会调用API
|
||||
// 此处使用模拟数据
|
||||
try {
|
||||
// 模拟API检索过滤
|
||||
const searchForm = getSearchForm(type);
|
||||
const filteredData = mockData[type].filter((item) => {
|
||||
const nameMatch = !searchForm.name || item.name.includes(searchForm.name);
|
||||
const phoneMatch = !searchForm.phone || item.phone.includes(searchForm.phone);
|
||||
return nameMatch && phoneMatch;
|
||||
});
|
||||
|
||||
// 更新对应类型的管理员数据
|
||||
switch (type) {
|
||||
case 'school':
|
||||
schoolAdmins.value = filteredData;
|
||||
break;
|
||||
case 'custom':
|
||||
customAdmins.value = filteredData;
|
||||
break;
|
||||
case 'teacher':
|
||||
teacherAdmins.value = filteredData;
|
||||
break;
|
||||
if (type === 'school' || type === 'custom') {
|
||||
// 调用API获取全校管理员或自定义管理员数据
|
||||
const managerType = type === 'school' ? '1' : '2';
|
||||
const res = await listStudentManager({
|
||||
...searchForm,
|
||||
managerType
|
||||
});
|
||||
// 更新对应类型的管理员数据
|
||||
if (type === 'school') {
|
||||
schoolAdmins.value = res.rows || [];
|
||||
} else {
|
||||
customAdmins.value = res.rows || [];
|
||||
}
|
||||
} else if (type === 'teacher') {
|
||||
// 通过API获取班主任数据
|
||||
const params = {
|
||||
phone: searchForm.phone,
|
||||
name: searchForm.managerName // teacherSearchForm 实际只有 managerName 字段
|
||||
};
|
||||
const res = await listActiveClassTeachers(params);
|
||||
teacherAdmins.value = res.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取管理员列表失败:', error);
|
||||
@@ -252,9 +272,34 @@ function getSearchForm(type: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取自定义管理员及其管理范围
|
||||
async function fetchCustomAdminsWithScope() {
|
||||
await fetchAdmins('custom');
|
||||
// 遍历 customAdmins,异步获取每个管理员的authSource
|
||||
for (const admin of customAdmins.value) {
|
||||
try {
|
||||
const res = await service({
|
||||
url: '/xgStudentManagerSource/studentManagerSource/authSourceNames',
|
||||
method: 'get',
|
||||
params: { managerId: admin.id }
|
||||
});
|
||||
admin.authSourceNames = res?.data || [];
|
||||
} catch (e) {
|
||||
admin.authSourceNames = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听tab切换,切到custom时自动加载管理范围
|
||||
watch(activeTab, (val) => {
|
||||
if (val === 'custom') {
|
||||
fetchCustomAdminsWithScope();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
fetchAdmins('school');
|
||||
fetchAdmins('custom');
|
||||
fetchCustomAdminsWithScope();
|
||||
fetchAdmins('teacher');
|
||||
});
|
||||
|
||||
@@ -274,17 +319,31 @@ function onEdit(type: string, admin: any) {
|
||||
|
||||
async function onDelete(type: string, admin: any) {
|
||||
// 二次确认
|
||||
ElMessageBox.confirm(`确定要删除管理员【${admin.name}】吗?`, '提示', {
|
||||
ElMessageBox.confirm(`确定要删除管理员【${admin.name || admin.managerName}】吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
// 模拟删除成功
|
||||
mockData[type] = mockData[type].filter((item) => item.id !== admin.id);
|
||||
ElMessage.success('删除成功');
|
||||
fetchAdmins(type); // 删除后刷新
|
||||
if (type === 'school' || type === 'custom') {
|
||||
// 调用删除API
|
||||
const res = await delStudentManager(admin.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功');
|
||||
fetchAdmins(type);
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败');
|
||||
}
|
||||
} else {
|
||||
// 班主任暂时使用模拟数据
|
||||
ElMessage.success('删除成功');
|
||||
mockTeacherData.splice(
|
||||
mockTeacherData.findIndex((item) => item.id === admin.id),
|
||||
1
|
||||
);
|
||||
fetchAdmins(type);
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
@@ -301,36 +360,23 @@ function onScope(type: string, admin: any) {
|
||||
}
|
||||
|
||||
function onDialogSubmit(admin: any) {
|
||||
// 模拟添加或编辑操作
|
||||
if (dialogMode.value === 'add') {
|
||||
// 生成新ID
|
||||
const newId = Math.max(...mockData[activeTab.value].map((item) => item.id), 0) + 1;
|
||||
admin.id = newId;
|
||||
mockData[activeTab.value].push(admin);
|
||||
} else {
|
||||
// 编辑
|
||||
const index = mockData[activeTab.value].findIndex((item) => item.id === admin.id);
|
||||
if (index !== -1) {
|
||||
mockData[activeTab.value][index] = admin;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单已在对话框组件内处理API调用
|
||||
// 刷新列表数据
|
||||
fetchAdmins(activeTab.value);
|
||||
if (activeTab.value === 'custom') {
|
||||
fetchCustomAdminsWithScope();
|
||||
} else {
|
||||
fetchAdmins(activeTab.value);
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
|
||||
async function onScopeSubmit(scope: any) {
|
||||
try {
|
||||
// 模拟分配范围
|
||||
const index = mockData[scopeType.value].findIndex((item) => item.id === currentAdmin.value.id);
|
||||
if (index !== -1) {
|
||||
mockData[scopeType.value][index].scope = scope.scopeName;
|
||||
}
|
||||
|
||||
// 范围分配已在对话框组件内处理API调用
|
||||
// 成功后刷新数据并提示
|
||||
ElMessage.success('管理范围分配成功');
|
||||
fetchAdmins(scopeType.value);
|
||||
fetchCustomAdminsWithScope();
|
||||
scopeDialogVisible.value = false;
|
||||
} catch (error) {
|
||||
ElMessage.error('管理范围分配失败');
|
||||
@@ -353,6 +399,9 @@ function getListByType(type: string) {
|
||||
|
||||
// 过滤
|
||||
function filteredAdmins(type: string) {
|
||||
if (type === 'custom') {
|
||||
return customAdmins.value;
|
||||
}
|
||||
return getListByType(type).value;
|
||||
}
|
||||
|
||||
@@ -363,19 +412,23 @@ function handleSearch(type: string) {
|
||||
function handleReset(type: string) {
|
||||
switch (type) {
|
||||
case 'school':
|
||||
schoolSearchForm.name = '';
|
||||
schoolSearchForm.managerName = '';
|
||||
schoolSearchForm.phone = '';
|
||||
break;
|
||||
case 'custom':
|
||||
customSearchForm.name = '';
|
||||
customSearchForm.managerName = '';
|
||||
customSearchForm.phone = '';
|
||||
break;
|
||||
case 'teacher':
|
||||
teacherSearchForm.name = '';
|
||||
teacherSearchForm.managerName = '';
|
||||
teacherSearchForm.phone = '';
|
||||
break;
|
||||
}
|
||||
fetchAdmins(type);
|
||||
if (type === 'custom') {
|
||||
fetchCustomAdminsWithScope();
|
||||
} else {
|
||||
fetchAdmins(type);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,490 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted" v-if="false">
|
||||
<el-input v-model="queryParams.deleted" placeholder="请输入删除标志" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务编码" prop="positionCode" v-if="false">
|
||||
<el-input v-model="queryParams.positionCode" placeholder="请输入职务编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="职务名称" prop="positionName">
|
||||
<el-input v-model="queryParams.positionName" placeholder="请输入职务名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生职务类别" prop="positionCategory">
|
||||
<el-select v-model="queryParams.positionCategory" placeholder="请选择学生职务类别" clearable>
|
||||
<el-option v-for="dict in stu_position_category" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生职务级别" prop="positionLevel">
|
||||
<el-select v-model="queryParams.positionLevel" placeholder="请选择学生职务级别" clearable>
|
||||
<el-option v-for="dict in stu_position_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生职务类型" prop="positionType">
|
||||
<el-select v-model="queryParams.positionType" placeholder="请选择学生职务类型" clearable>
|
||||
<el-option v-for="dict in stu_position_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<!-- <el-form-item label="职责描述" prop="responsibilities" v-if="false">
|
||||
<el-input v-model="queryParams.responsibilities" placeholder="请输入职责描述" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="排序顺序" prop="sortOrder" v-if="false">
|
||||
<el-input v-model="queryParams.sortOrder" placeholder="请输入排序顺序" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xgClassPosDict:xgClassPosDict:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xgClassPosDict:xgClassPosDict:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['xgClassPosDict:xgClassPosDict:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xgClassPosDict:xgClassPosDict:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="xgClassPosDictList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted" /> -->
|
||||
<el-table-column label="职务编码" align="center" prop="positionCode" />
|
||||
<el-table-column label="职务名称" align="center" prop="positionName" />
|
||||
<el-table-column label="学生职务类别" align="center" prop="positionCategory">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stu_position_category" :value="scope.row.positionCategory" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学生职务级别" align="center" prop="positionLevel">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stu_position_level" :value="scope.row.positionLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学生职务类型" align="center" prop="positionType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stu_position_type" :value="scope.row.positionType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="职责描述" align="center" prop="responsibilities" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_enable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="排序顺序" align="center" prop="sortOrder" /> -->
|
||||
<el-table-column label="备注信息" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['xgClassPosDict:xgClassPosDict:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['xgClassPosDict:xgClassPosDict:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改班级干部职务配置对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="xgClassPosDictFormRef" :model="form" :rules="rules" label-width="110px">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted">
|
||||
<el-input v-model="form.deleted" placeholder="请输入删除标志" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="职务名称" prop="positionName">
|
||||
<el-input v-model="form.positionName" placeholder="请输入职务名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务编码" prop="positionCode">
|
||||
<el-input
|
||||
v-model="form.positionCode"
|
||||
:placeholder="form.id ? '职务编码不可修改' : '请输入职务编码后缀(如:001、班长、副班长等)'"
|
||||
:disabled="!!form.id"
|
||||
@input="handlePositionCodeInput"
|
||||
>
|
||||
<template #prepend>XT-BJZW-</template>
|
||||
</el-input>
|
||||
<div class="form-tip">
|
||||
<el-text type="info" size="small">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span v-if="form.id">职务编码创建后不可修改</span>
|
||||
<span v-else>编码格式:XT-BJZW- + 自定义后缀(如:XT-BJZW-001、XT-BJZW-班长)</span>
|
||||
</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生职务类别" prop="positionCategory">
|
||||
<el-select v-model="form.positionCategory" placeholder="请选择学生职务类别">
|
||||
<el-option v-for="dict in stu_position_category" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生职务级别" prop="positionLevel">
|
||||
<el-select v-model="form.positionLevel" placeholder="请选择学生职务级别">
|
||||
<el-option v-for="dict in stu_position_level" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生职务类型" prop="positionType">
|
||||
<el-select v-model="form.positionType" placeholder="请选择学生职务类型">
|
||||
<el-option v-for="dict in stu_position_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="职责描述" prop="responsibilities">
|
||||
<el-input v-model="form.responsibilities" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="排序顺序" prop="sortOrder">
|
||||
<el-input v-model="form.sortOrder" placeholder="请输入排序顺序" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="备注信息" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="XgClassPosDict" lang="ts">
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import {
|
||||
listXgClassPosDict,
|
||||
getXgClassPosDict,
|
||||
delXgClassPosDict,
|
||||
addXgClassPosDict,
|
||||
updateXgClassPosDict
|
||||
} from '@/api/xg/xsjcxx/xgClassPosDict/xgClassPosDict';
|
||||
import { XgClassPosDictVO, XgClassPosDictQuery, XgClassPosDictForm } from '@/api/xg/xsjcxx/xgClassPosDict/xgClassPosDict/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { stu_position_level, stu_position_type, sys_item_enable, stu_position_category } = toRefs<any>(
|
||||
proxy?.useDict('stu_position_level', 'stu_position_type', 'sys_item_enable', 'stu_position_category')
|
||||
);
|
||||
|
||||
const xgClassPosDictList = ref<XgClassPosDictVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const xgClassPosDictFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: XgClassPosDictForm = {
|
||||
id: undefined,
|
||||
deleted: undefined,
|
||||
positionCode: undefined,
|
||||
positionName: undefined,
|
||||
positionCategory: 'class', // 默认选择:班级干部
|
||||
positionLevel: 1, // 默认选择:班长级别
|
||||
positionType: 'regular', // 默认选择:常规职务
|
||||
responsibilities: undefined,
|
||||
status: '0', // 默认选择:启用状态
|
||||
sortOrder: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
const data = reactive<PageData<XgClassPosDictForm, XgClassPosDictQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deleted: undefined,
|
||||
positionCode: undefined,
|
||||
positionName: undefined,
|
||||
positionCategory: undefined,
|
||||
positionLevel: undefined,
|
||||
positionType: undefined,
|
||||
responsibilities: undefined,
|
||||
status: undefined,
|
||||
sortOrder: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
positionCode: [
|
||||
{
|
||||
required: (form: any) => !form.id,
|
||||
message: '职务编码不能为空',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
pattern: /^XT-BJZW-.+/,
|
||||
message: '职务编码必须以XT-BJZW-开头,后面需要补充编码后缀',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
positionName: [{ required: true, message: '职务名称不能为空', trigger: 'blur' }],
|
||||
positionCategory: [{ required: true, message: '学生职务类别:class-班级干部,party-党团干部,other-其他不能为空', trigger: 'change' }],
|
||||
positionLevel: [{ required: true, message: '学生职务级别:1-班长,2-副班长,3-委员,9-普通成员不能为空', trigger: 'change' }],
|
||||
positionType: [{ required: true, message: '学生职务类型:regular-常规职务,special-特殊职务,temporary-临时职务不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态:0-启用,1-停用不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询班级干部职务配置列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listXgClassPosDict(queryParams.value);
|
||||
xgClassPosDictList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
xgClassPosDictFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: XgClassPosDictVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加班级干部职务配置';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: XgClassPosDictVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getXgClassPosDict(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
|
||||
// 确保职务编码格式正确
|
||||
if (form.value.positionCode && !form.value.positionCode.startsWith('XT-BJZW-')) {
|
||||
form.value.positionCode = `XT-BJZW-${form.value.positionCode}`;
|
||||
}
|
||||
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改班级干部职务配置';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
xgClassPosDictFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateXgClassPosDict(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addXgClassPosDict(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XgClassPosDictVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
|
||||
// 根据删除类型生成不同的确认提示
|
||||
let confirmMessage = '';
|
||||
if (row) {
|
||||
// 单个删除
|
||||
confirmMessage = `是否确认删除班级干部职务配置"${row.positionName}"?`;
|
||||
} else {
|
||||
// 批量删除
|
||||
confirmMessage = `是否确认删除选中的 ${ids.value.length} 条班级干部职务配置数据?`;
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(confirmMessage).finally(() => (loading.value = false));
|
||||
await delXgClassPosDict(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'xgClassPosDict/xgClassPosDict/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`xgClassPosDict_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 处理职务编码输入 */
|
||||
const handlePositionCodeInput = (value: string) => {
|
||||
// 如果是修改模式,不允许修改职务编码
|
||||
if (form.value.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果用户输入的是完整编码(包含前缀),直接使用
|
||||
if (value.startsWith('XT-BJZW-')) {
|
||||
form.value.positionCode = value;
|
||||
} else {
|
||||
// 如果用户只输入后缀,自动拼接前缀
|
||||
const suffix = value.replace(/^XT-BJZW-/, ''); // 移除可能的前缀
|
||||
form.value.positionCode = `XT-BJZW-${suffix}`;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
.table-container {
|
||||
.tree-table {
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表单提示信息样式 */
|
||||
.form-tip {
|
||||
margin-top: 4px;
|
||||
|
||||
.el-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="search">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="请假天数" prop="startLeaveDays">
|
||||
<el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="endLeaveDays"> 至 </el-form-item>
|
||||
<el-form-item prop="endLeaveDays">
|
||||
<el-input v-model="queryParams.endLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['workflow:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="leaveList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="主键" align="center" prop="id" />
|
||||
<el-table-column label="请假类型" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ options.find((e) => e.value === scope.row.leaveType)?.label }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开始时间" align="center" prop="startDate">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endDate">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假天数" align="center" prop="leaveDays" />
|
||||
<el-table-column label="请假原因" align="center" prop="remark" />
|
||||
<el-table-column align="center" label="流程状态" min-width="70">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="wf_business_status" :value="scope.row.status"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="162">
|
||||
<template #default="scope">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
|
||||
<el-button v-hasPermi="['workflow:leave:edit']" size="small" type="primary" icon="Edit" @click="handleUpdate(scope.row)"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
|
||||
<el-button v-hasPermi="['workflow:leave:remove']" size="small" type="primary" icon="Delete" @click="handleDelete(scope.row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="scope.row.status === 'waiting'">
|
||||
<el-button size="small" type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">撤销</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Leave" lang="ts">
|
||||
import { delLeave, listLeave } from '@/api/workflow/leave';
|
||||
import { cancelProcessApply } from '@/api/workflow/instance';
|
||||
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
|
||||
const leaveList = ref<LeaveVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '事假'
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '调休'
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '病假'
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '婚假'
|
||||
}
|
||||
];
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const data = reactive<PageData<LeaveForm, LeaveQuery>>({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
startLeaveDays: undefined,
|
||||
endLeaveDays: undefined
|
||||
},
|
||||
rules: {}
|
||||
});
|
||||
|
||||
const { queryParams } = toRefs(data);
|
||||
|
||||
/** 查询请假列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listLeave(queryParams.value);
|
||||
leaveList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: LeaveVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
|
||||
query: {
|
||||
type: 'add'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = (row?: LeaveVO) => {
|
||||
proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = (row?: LeaveVO) => {
|
||||
proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'view'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: LeaveVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除请假编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delLeave(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'workflow/leave/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`leave_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 撤销按钮操作 */
|
||||
const handleCancelProcessApply = async (id: string) => {
|
||||
await proxy?.$modal.confirm('是否确认撤销当前单据?');
|
||||
loading.value = true;
|
||||
const data = {
|
||||
businessId: id,
|
||||
message: '申请人撤销流程!'
|
||||
};
|
||||
await cancelProcessApply(data).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('撤销成功');
|
||||
};
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<el-card shadow="never">
|
||||
<approvalButton
|
||||
@submitForm="submitForm"
|
||||
@approvalVerifyOpen="approvalVerifyOpen"
|
||||
@handleApprovalRecord="handleApprovalRecord"
|
||||
:buttonLoading="buttonLoading"
|
||||
:id="form.id"
|
||||
:status="form.status"
|
||||
:pageType="routeParams.type"
|
||||
/>
|
||||
</el-card>
|
||||
<el-card shadow="never" style="height: 78vh; overflow-y: auto">
|
||||
<el-form ref="leaveFormRef" v-loading="loading" :disabled="routeParams.type === 'view'" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-select v-model="form.leaveType" placeholder="请选择请假类型" style="width: 100%">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假时间" required>
|
||||
<el-date-picker
|
||||
v-model="leaveTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="To"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
@change="changeLeaveTime()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假天数" prop="leaveDays">
|
||||
<el-input v-model="form.leaveDays" disabled type="number" placeholder="请输入请假天数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请假原因" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入请假原因" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- 提交组件 -->
|
||||
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
|
||||
<!-- 审批记录 -->
|
||||
<approvalRecord ref="approvalRecordRef" />
|
||||
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" :before-close="handleClose" width="500">
|
||||
<el-select v-model="flowCode" placeholder="Select" style="width: 240px">
|
||||
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="submitFlow()"> 确认 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Leave" lang="ts">
|
||||
import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
|
||||
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
|
||||
import { startWorkFlow } from '@/api/workflow/task';
|
||||
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
||||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||||
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const leaveTime = ref<Array<string>>([]);
|
||||
//路由参数
|
||||
const routeParams = ref<Record<string, any>>({});
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '事假'
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '调休'
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '病假'
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '婚假'
|
||||
}
|
||||
];
|
||||
const flowCodeOptions = [
|
||||
{
|
||||
value: 'hzdyjs-qj',
|
||||
label: '学生请假流程-杭州第一技师'
|
||||
},
|
||||
{
|
||||
value: 'leave1',
|
||||
label: '请假申请-普通'
|
||||
},
|
||||
{
|
||||
value: 'leave2',
|
||||
label: '请假申请-排他网关'
|
||||
},
|
||||
{
|
||||
value: 'leave3',
|
||||
label: '请假申请-并行网关'
|
||||
},
|
||||
{
|
||||
value: 'leave4',
|
||||
label: '请假申请-会签'
|
||||
},
|
||||
{
|
||||
value: 'leave5',
|
||||
label: '请假申请-并行会签网关'
|
||||
},
|
||||
{
|
||||
value: 'leave6',
|
||||
label: '请假申请-排他并行会签'
|
||||
}
|
||||
];
|
||||
|
||||
const flowCode = ref<string>('');
|
||||
|
||||
const dialogVisible = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: '流程定义'
|
||||
});
|
||||
//提交组件
|
||||
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
|
||||
//审批记录组件
|
||||
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
|
||||
//按钮组件
|
||||
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
|
||||
|
||||
const leaveFormRef = ref<ElFormInstance>();
|
||||
|
||||
const submitFormData = ref<StartProcessBo>({
|
||||
businessId: '',
|
||||
flowCode: '',
|
||||
variables: {}
|
||||
});
|
||||
const taskVariables = ref<Record<string, any>>({});
|
||||
|
||||
const initFormData: LeaveForm = {
|
||||
id: undefined,
|
||||
leaveType: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined,
|
||||
leaveDays: undefined,
|
||||
remark: undefined,
|
||||
status: undefined
|
||||
};
|
||||
const data = reactive<PageData<LeaveForm, LeaveQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
startLeaveDays: undefined,
|
||||
endLeaveDays: undefined
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
|
||||
leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
|
||||
leaveTime: [{ required: true, message: '请假时间不能为空', trigger: 'blur' }],
|
||||
leaveDays: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.visible = false;
|
||||
flowCode.value = '';
|
||||
buttonLoading.value = false;
|
||||
};
|
||||
const { form, rules } = toRefs(data);
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
leaveTime.value = [];
|
||||
leaveFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const changeLeaveTime = () => {
|
||||
const startDate = new Date(leaveTime.value[0]).getTime();
|
||||
const endDate = new Date(leaveTime.value[1]).getTime();
|
||||
const diffInMilliseconds = endDate - startDate;
|
||||
form.value.leaveDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24)) + 1;
|
||||
};
|
||||
/** 获取详情 */
|
||||
const getInfo = () => {
|
||||
loading.value = true;
|
||||
buttonLoading.value = false;
|
||||
nextTick(async () => {
|
||||
const res = await getLeave(routeParams.value.id);
|
||||
Object.assign(form.value, res.data);
|
||||
console.log('res.data:', res.data);
|
||||
console.log('form.value:', form.value);
|
||||
// 为了确保 Element Plus 日期区间组件能够正确渲染,需要直接替换绑定数组引用
|
||||
// 同时将后端返回的日期统一格式化为组件要求的 "YYYY-MM-DD HH:mm:ss"
|
||||
// 后端仅返回 "YYYY-MM-DD",需要为日期补齐时间,符合 value-format="YYYY-MM-DD HH:mm:ss"
|
||||
const ensureDateTime = (dateStr: string | undefined, endFlag = false) => {
|
||||
if (!dateStr) return '';
|
||||
// 如果已包含时间段,直接返回
|
||||
if (/\d{2}:\d{2}:\d{2}$/.test(dateStr)) {
|
||||
return dateStr;
|
||||
}
|
||||
return endFlag ? `${dateStr} 23:59:59` : `${dateStr} 00:00:00`;
|
||||
};
|
||||
const start = ensureDateTime(form.value.startDate, false);
|
||||
const end = ensureDateTime(form.value.endDate, true);
|
||||
leaveTime.value = [start, end];
|
||||
loading.value = false;
|
||||
buttonLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = (status: string) => {
|
||||
if (leaveTime.value.length === 0) {
|
||||
proxy?.$modal.msgError('请假时间不能为空');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
leaveFormRef.value?.validate(async (valid: boolean) => {
|
||||
form.value.startDate = leaveTime.value[0];
|
||||
form.value.endDate = leaveTime.value[1];
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
let res: AxiosResponse<LeaveVO>;
|
||||
if (form.value.id) {
|
||||
res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
form.value = res.data;
|
||||
if (status === 'draft') {
|
||||
buttonLoading.value = false;
|
||||
proxy?.$modal.msgSuccess('暂存成功');
|
||||
proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.go(-1);
|
||||
} else {
|
||||
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
|
||||
flowCode.value = flowCodeOptions[0].value;
|
||||
dialogVisible.visible = true;
|
||||
return;
|
||||
}
|
||||
//说明启动过先随意穿个参数
|
||||
if (flowCode.value === '' || flowCode.value === null) {
|
||||
flowCode.value = 'xx';
|
||||
}
|
||||
await handleStartWorkFlow(res.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const submitFlow = async () => {
|
||||
handleStartWorkFlow(form.value);
|
||||
dialogVisible.visible = false;
|
||||
};
|
||||
//提交申请
|
||||
const handleStartWorkFlow = async (data: LeaveForm) => {
|
||||
try {
|
||||
submitFormData.value.flowCode = flowCode.value;
|
||||
submitFormData.value.businessId = data.id;
|
||||
//流程变量
|
||||
taskVariables.value = {
|
||||
// hzdyjs-qj 使用的流程变量
|
||||
qjts: data.leaveDays,
|
||||
// leave2/6 使用的流程变量
|
||||
leaveDays: data.leaveDays,
|
||||
// leave4/5 使用的流程变量
|
||||
userList: ['1', '3', '4']
|
||||
};
|
||||
submitFormData.value.variables = taskVariables.value;
|
||||
const resp = await startWorkFlow(submitFormData.value);
|
||||
if (submitVerifyRef.value) {
|
||||
buttonLoading.value = false;
|
||||
submitVerifyRef.value.openDialog(resp.data.taskId);
|
||||
}
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
//审批记录
|
||||
const handleApprovalRecord = () => {
|
||||
approvalRecordRef.value.init(form.value.id);
|
||||
};
|
||||
//提交回调
|
||||
const submitCallback = async () => {
|
||||
await proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.go(-1);
|
||||
};
|
||||
//审批
|
||||
const approvalVerifyOpen = async () => {
|
||||
submitVerifyRef.value.openDialog(routeParams.value.taskId);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(async () => {
|
||||
routeParams.value = proxy.$route.query;
|
||||
reset();
|
||||
loading.value = false;
|
||||
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
|
||||
getInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,517 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="企业名称" prop="enterpriseName">
|
||||
<el-input v-model="queryParams.enterpriseName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属行业" prop="industry">
|
||||
<el-select v-model="queryParams.industry" placeholder="请选择所属行业" clearable style="width: 100%">
|
||||
<el-option v-for="dict in zsjy_enterprise_industry" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="企业规模" prop="enterpriseScale">
|
||||
<el-select v-model="queryParams.enterpriseScale" placeholder="请选择企业规模" clearable style="width: 100%">
|
||||
<el-option v-for="dict in zsjy_enterprise_scale" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="企业状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择企业状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 操作按钮和表格 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:enterprise:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enterprise:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enterprise:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['zsjy:enterprise:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" border :data="enterpriseList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="企业名称" align="center" prop="enterpriseName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="所属行业" align="center" prop="industry" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_enterprise_industry" :value="scope.row.industry" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="企业规模" align="center" prop="enterpriseScale" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_enterprise_scale" :value="scope.row.enterpriseScale" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系人" align="center" prop="contactName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="联系电话" align="center" prop="contactPhone" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="企业邮箱" align="center" prop="email" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="企业地址" align="center" prop="address" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px; color: blue"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['zsjy:enterprise:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['zsjy:enterprise:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
size="default"
|
||||
style="font-size: 16px"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['zsjy:enterprise:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改企业信息对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
|
||||
<el-form ref="enterpriseFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业名称" prop="enterpriseName">
|
||||
<el-input v-model="form.enterpriseName" placeholder="请输入企业名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属行业" prop="industry">
|
||||
<el-select v-model="form.industry" placeholder="请选择所属行业" style="width: 100%">
|
||||
<el-option v-for="dict in zsjy_enterprise_industry" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业规模" prop="enterpriseScale">
|
||||
<el-select v-model="form.enterpriseScale" placeholder="请选择企业规模" style="width: 100%">
|
||||
<el-option v-for="dict in zsjy_enterprise_scale" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人姓名" prop="contactName">
|
||||
<el-input v-model="form.contactName" placeholder="请输入联系人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入企业邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="企业地址" prop="address">
|
||||
<el-input v-model="form.address" placeholder="请输入企业地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="主营业务" prop="mainBusiness">
|
||||
<el-input
|
||||
v-model="form.mainBusiness"
|
||||
type="textarea"
|
||||
placeholder="请输入主营业务"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看企业信息对话框 -->
|
||||
<el-dialog title="企业信息详情" v-model="viewDialog.visible" width="1000px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="企业名称">{{ viewForm.enterpriseName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属行业">
|
||||
<dict-tag :options="zsjy_enterprise_industry" :value="viewForm.industry" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="企业规模">
|
||||
<dict-tag :options="zsjy_enterprise_scale" :value="viewForm.enterpriseScale" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人姓名">{{ viewForm.contactName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ viewForm.contactPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业邮箱">{{ viewForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业地址" span="2">{{ viewForm.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主营业务" span="2">{{ viewForm.mainBusiness }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :options="sys_normal_disable" :value="viewForm.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ viewForm.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Enterprise" lang="ts">
|
||||
import { listEnterprise, getEnterprise, delEnterprise, addEnterprise, updateEnterprise } from '@/api/zsjy/enterprise'
|
||||
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from '@/api/zsjy/types'
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
|
||||
const enterpriseList = ref<EnterpriseVO[]>([])
|
||||
const buttonLoading = ref(false)
|
||||
const loading = ref(true)
|
||||
const showSearch = ref(true)
|
||||
const ids = ref<Array<string | number>>([])
|
||||
const single = ref(true)
|
||||
const multiple = ref(true)
|
||||
const total = ref(0)
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>()
|
||||
const enterpriseFormRef = ref<ElFormInstance>()
|
||||
|
||||
// 字典数据
|
||||
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'))
|
||||
const { zsjy_enterprise_industry } = toRefs<any>(proxy?.useDict('zsjy_enterprise_industry'))
|
||||
const { zsjy_enterprise_scale } = toRefs<any>(proxy?.useDict('zsjy_enterprise_scale'))
|
||||
|
||||
const initFormData: EnterpriseForm = {
|
||||
id: undefined,
|
||||
enterpriseName: '',
|
||||
industry: '',
|
||||
mainBusiness: '',
|
||||
enterpriseScale: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
}
|
||||
|
||||
const data = reactive<PageData<EnterpriseForm, EnterpriseQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
enterpriseName: '',
|
||||
industry: '',
|
||||
enterpriseScale: '',
|
||||
status: ''
|
||||
},
|
||||
rules: {
|
||||
enterpriseName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
|
||||
industry: [{ required: true, message: '所属行业不能为空', trigger: 'change' }],
|
||||
mainBusiness: [{ required: true, message: '主营业务不能为空', trigger: 'blur' }],
|
||||
enterpriseScale: [{ required: true, message: '企业规模不能为空', trigger: 'change' }],
|
||||
contactName: [{ required: true, message: '联系人姓名不能为空', trigger: 'blur' }],
|
||||
contactPhone: [
|
||||
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '企业邮箱不能为空', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
address: [{ required: true, message: '企业地址不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data)
|
||||
|
||||
// 对话框相关
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
})
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
})
|
||||
|
||||
const viewForm = ref<EnterpriseVO>({} as EnterpriseVO)
|
||||
|
||||
/** 查询企业信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
const res = await listEnterprise(queryParams.value)
|
||||
enterpriseList.value = res.rows
|
||||
total.value = res.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset()
|
||||
dialog.visible = false
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData }
|
||||
enterpriseFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnterpriseVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
dialog.visible = true
|
||||
dialog.title = '添加企业信息'
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: EnterpriseVO) => {
|
||||
reset()
|
||||
const id = row?.id || ids.value[0]
|
||||
const res = await getEnterprise(id)
|
||||
Object.assign(form.value, res.data)
|
||||
dialog.visible = true
|
||||
dialog.title = '修改企业信息'
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleView = async (row: EnterpriseVO) => {
|
||||
const res = await getEnterprise(row.id)
|
||||
viewForm.value = res.data
|
||||
viewDialog.visible = true
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
enterpriseFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true
|
||||
if (form.value.id) {
|
||||
await updateEnterprise(form.value).finally(() => (buttonLoading.value = false))
|
||||
} else {
|
||||
await addEnterprise(form.value).finally(() => (buttonLoading.value = false))
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功')
|
||||
dialog.visible = false
|
||||
await getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: EnterpriseVO) => {
|
||||
const idList = row?.id || ids.value
|
||||
await proxy?.$modal.confirm('是否确认删除企业信息编号为"' + idList + '"的数据项?')
|
||||
await delEnterprise(idList)
|
||||
proxy?.$modal.msgSuccess('删除成功')
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'zsjy/enterprise/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`enterprise_${new Date().getTime()}.xlsx`
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 继承后勤管理的样式
|
||||
.page-container {
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
text-align: right;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// 表格适应性样式
|
||||
:deep(.el-table) {
|
||||
.el-table__cell {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式表格
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
// 表格列宽自适应
|
||||
.el-table {
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 描述列表样式优化
|
||||
:deep(.el-descriptions) {
|
||||
.el-descriptions__label {
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.el-descriptions__content {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loaded 100 of 662 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user