This commit is contained in:
2025-09-23 10:33:01 +08:00
parent a09f7fac9f
commit 2ecb9c82bd
5 changed files with 1239 additions and 0 deletions
@@ -0,0 +1,181 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="姓名/工号">
<el-input placeholder="请输入内容" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="教代会">
<el-select v-model="pageForm.sessionId" @change="sessionChange">
<el-option v-for="item in sessionOptions" :label="item.fullName" :value="item.id"
:key="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分工会">
<el-select v-model="pageForm.unionId" filterable @change="listUnit" clearable style="width: 100%">
<el-option v-for="i in unionOptions" :label="i.name" :key="i.id" :value="i.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<push-progress ref="pushProgressRef"></push-progress>
</el-card>
<el-card shadow="never">
<table-tool>
<el-button
icon="el-icon-plus"
size="small"
type="primary"
@click="$refs.pushFormalRef.onOpen(pageForm.sessionId,pageForm.unionId)"
>正式代表预选
</el-button>
<el-button
icon="el-icon-plus"
size="small"
type="primary"
@click="$refs.pushAttendanceRef.onOpen(pageForm.sessionId,pageForm.unionId)"
>列席代表预选
</el-button>
<el-button icon="el-icon-download" size="small" type="primary" @click="doExport">导出</el-button>
<el-button icon="el-icon-check" size="small" type="primary" @click="onBatchSubmit">正式代表提交上报
</el-button>
<el-radio-group v-model="pageForm.isFormalOrAttendance" size="small" class="ml10"
@change="doSearch">
<el-radio-button label="全部"></el-radio-button>
<el-radio-button label="正式代表"></el-radio-button>
<el-radio-button label="列席代表"></el-radio-button>
</el-radio-group>
</table-tool>
<el-table
ref="tableRef"
:data="tableData"
row-key="id"
@sort-change="pageOrder"
>
<el-table-column type="selection" width="55" reserve-selection fixed="left"></el-table-column>
<el-table-column type="index" width="55" label="序号" :index="indexMethod" fixed="left"></el-table-column>
<el-table-column label="姓名" prop="userName" width="100" fixed="left"></el-table-column>
<el-table-column label="年龄" prop="age" width="100"></el-table-column>
<el-table-column label="性别" prop="sex" width="100" fixed="left"></el-table-column>
<el-table-column label="民族" prop="nation" min-width="100"></el-table-column>
<el-table-column label="职称" prop="jobTitle" min-width="150"></el-table-column>
<el-table-column label="职级" prop="jobTitleLevel" min-width="100"></el-table-column>
<el-table-column label="校级领导" prop="schoolLeader" min-width="100">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.schoolLeader"
:style="{'pointer-events':row.stateCode===0?'auto':'none'}"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="中层领导" prop="middleLevelLeader" min-width="100">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.middleLevelLeader"
:style="{'pointer-events':row.stateCode===0?'auto':'none'}"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="专任教师" prop="ordinaryTeacher" min-width="100">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.ordinaryTeacher"
:style="{'pointer-events':row.stateCode===0?'auto':'none'}"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="高级职称专任教师" prop="seniorTeacher" min-width="150">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.seniorTeacher"
style="pointer-events:none"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="教代会代表" prop="isJdh" min-width="100">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.isJdh"
:style="{'pointer-events':row.stateCode===0?'auto':'none'}"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="工代会代表" prop="isGdh" min-width="100">
<template scope="{row}">
<el-checkbox
v-if="row"
v-model="row.isGdh"
:style="{'pointer-events':row.stateCode===0?'auto':'none'}"
></el-checkbox>
</template>
</el-table-column>
<el-table-column label="代表类型" prop="representativeType" show-overflow-tooltip width="100"
fixed="right"></el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<push-formal-dialog ref="pushFormalRef" @refresh="doSearch"></push-formal-dialog>
</div>
<script>
<!--#include('push-progress.js'){}#-->
<!--#include('push-formal-dialog.js'){}#-->
new Vue({
el: '#app',
mixins: [initTableMixins],
components: {
'push-progress': pushProgress,
'push-formal-dialog': pushFormalDialog,
},
data() {
return {
sessionOptions:[],
unionOptions:[]
}
},
methods: {
listUnion() {
this.$businessTool.listUnion().then((res) => {
this.unionOptions = res
})
},
listSession() {
this.$axios.post("/platform/teacherCongress/common/listSession").then((res) => {
if (res.code === 0) {
this.sessionOptions = res.data
if (this.sessionOptions && this.sessionOptions.length > 0) {
this.pageForm.sessionId = this.sessionOptions[0].id
// this.pageData()
}
}
})
}
},
created() {
this.listUnion()
this.listSession()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,135 @@
const pushFormalDialog = {
template: /*language=HTML*/ `
<el-dialog :close-on-click-modal="false"
:visible.sync="dialogVisible"
title="正式代表预选"
class="push-formal-dialog"
width="70%">
<el-transfer
ref="transfer"
v-model="userValue"
:data="userData"
:filter-method="filterMethod"
:props="{key: 'id',label: 'name'}"
:titles="['可报人员名单', '当前选择']"
filterable
>
<div slot-scope="{ option }">
<div class="transfer-item">
<div class="transfer-item-name">{{ option.userName }} - {{ option.loginName }}</div>
<div class="transfer-item-details">
<span class="detail-item">{{ option.sex}}</span>
<span class="detail-item" v-if="option.age">{{ option.age }}岁</span>
<span class="detail-item" v-if="option.jobTitle">{{ option.jobTitle }}</span>
<span class="detail-item" v-if="option.jobTitleLevel">{{ option.jobTitleLevel }}</span>
<span class="detail-item" v-if="option.nation">{{ option.nation }}</span>
</div>
</div>
</div>
</el-transfer>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="onConfirm">确 定</el-button>
</span>
</el-dialog>
`,
data() {
return {
dialogVisible: false,
userValue: [],
userData: [],
rightChecked: [],
attendanceRightChecked: [],
sessionId: null
}
},
methods: {
async onOpen(sessionId, unionId) {
this.dialogVisible = true
this.sessionId = sessionId
this.userValue = []
this.userData = []
const {code, data} = await $.get('/platform/teacherCongress/delegate/push/getUnionUser', {
sessionId: sessionId,
unionId: unionId
})
if (code === 0) {
data.userData.forEach(v => {
this.userData.push({id: v.userId, ...v})
})
}
},
filterMethod(query, item) {
return item.userName.indexOf(query) > -1
},
async onConfirm() {
const {code, msg} = await $.post('/platform/teacherCongress/delegate/push/addPreselectionDb', {
userValue: JSON.stringify(this.userValue),
sessionId: this.sessionId,
representativeType: '正式代表'
})
if (code === 0) {
this.dialogVisible = false
this.$message.success(msg)
this.$emit('refresh')
}
}
},
style: /*language=CSS*/ `
.push-formal-dialog ::v-deep .el-transfer{
text-align: center!important;
}
.push-formal-dialog ::v-deep .el-transfer-panel__item.el-checkbox {
height: auto;
display: block;
margin-right: 0;
padding: 0 15px;
}
.push-formal-dialog ::v-deep .el-checkbox__input {
vertical-align: top;
margin-top: 5px;
}
.push-formal-dialog ::v-deep .transfer-item {
padding: 5px;
font-size: 12px;
border-bottom: 1px solid #f0f0f0;
}
.push-formal-dialog ::v-deep .transfer-item-name {
font-weight: bold;
margin-bottom: 3px;
color: #303133;
}
.push-formal-dialog ::v-deep .transfer-item-details {
display: flex;
flex-wrap: wrap;
gap: 6px;
color: #606266;
}
.push-formal-dialog ::v-deep .detail-item {
display: inline-flex;
align-items: center;
}
.push-formal-dialog ::v-deep .el-transfer-panel{
height: 60vh;
text-align: left !important;
width: 35% !important;
}
.push-formal-dialog ::v-deep .el-transfer-panel__body {
height: calc(100% - 40px) !important;
display: flex;
flex-direction: column;
}
.push-formal-dialog ::v-deep .el-transfer-panel__list.is-filterable {
flex: 1;
}
`
}
@@ -0,0 +1,575 @@
const pushProgress = {
template: /*language=HTML*/ `
<div class="representative-allocation">
<div class="container-x">
<!-- 主卡片 -->
<div class="card">
<div class="header-x">
<div class="title">
<h1>正式代表名额分配概况</h1>
<p>{{teacherMeetName}}教职工代表大会代表名额分配情况</p>
</div>
<div class="buttons">
<!-- <el-button type="primary" icon="el-icon-download">导出数据</el-button>-->
</div>
</div>
<div class="grid-cols-3" style="grid-template-columns: repeat(8,1fr)">
<!-- 总代表数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">总代表数</div>
<div class="metric-value">
{{ metrics.total.current }}
<span class="metric-target">
/ {{ metrics.total.target }}
</span>
</div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-users text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-success"
:style="{width: metrics.total.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.total.progress>1" class="text-danger">
已超出
</span>
<span v-else-if="metrics.total.progress<1" class="text-primary">
未达标
</span>
<span v-else class="text-success">
已达标
</span>
<!-- <span class="text-gray-500" v-if="metrics.total.progress<1">剩余 {{ metrics.total.remaining }} 人</span>-->
</div>
</div>
<!-- 高级职称专任教师代表数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">高级职称专任教师</div>
<div class="metric-value">{{ metrics.seniorTeacher.current }}<span
class="metric-target"
>/ {{ metrics.seniorTeacher.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-user-tie text-primary"/>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-primary"
:style="{width: metrics.seniorTeacher.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.seniorTeacher.progress>=1 || metrics.seniorTeacher.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.seniorTeacher.progress<1" class="text-primary">
未达标
</span>
<span
v-if="metrics.seniorTeacher.remaining>0"
class="text-gray-500"
>剩余 {{ metrics.seniorTeacher.remaining }} 人</span>
</div>
</div>
<!-- 专任教师代表数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">专任教师代表</div>
<div class="metric-value">{{ metrics.ordinaryTeacher.current }}<span
class="metric-target"
>/ {{ metrics.ordinaryTeacher.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-chalkboard-teacher text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div
class="progress-bar-inner bg-primary"
:style="{width: metrics.ordinaryTeacher.progress * 100 + '%'}"
></div>
</div>
<div class="progress-info">
<span v-if="metrics.ordinaryTeacher.progress>=1 || metrics.ordinaryTeacher.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.ordinaryTeacher.progress<1" class="text-primary">
未达标
</span>
<span
v-if="metrics.ordinaryTeacher.remaining>0"
class="text-gray-500"
>剩余 {{ metrics.ordinaryTeacher.remaining }} 人</span>
</div>
</div>
<!-- 女代表数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">女代表数</div>
<div class="metric-value">{{ metrics.female.current }}<span
class="metric-target"
>/ {{ metrics.female.target }}</span></div>
</div>
<div class="metric-icon bg-warning-light">
<i class="fas fa-female text-warning"></i>
</div>
</div>
<div class="progress-bar">
<div
class="progress-bar-inner bg-primary"
:style="{width: metrics.female.progress * 100 + '%'}"
></div>
</div>
<div class="progress-info">
<span v-if="metrics.female.progress>=1 || metrics.female.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.female.progress<1" class="text-primary">
未达标
</span>
<span v-if="metrics.female.remaining>0" class="text-gray-500">剩余 {{
metrics.female.remaining
}} 人</span>
</div>
</div>
<!-- 45岁以下代表数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">45岁以下代表</div>
<div class="metric-value">{{ metrics.under45.current }}<span
class="metric-target"
>/ {{ metrics.under45.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-user-graduate text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-primary"
:style="{width: metrics.under45.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.under45.progress>=1 || metrics.under45.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.under45.progress<1" class="text-primary">
未达标
</span>
<span v-if="metrics.under45.remaining>0" class="text-gray-500">剩余 {{
metrics.under45.remaining
}} 人</span>
</div>
</div>
<!-- 少数民族数 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">少数民族代表</div>
<div class="metric-value">{{ metrics.ethnicMinority.current }}<span
class="metric-target"
>/ {{ metrics.ethnicMinority.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-user-graduate text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-primary"
:style="{width: metrics.ethnicMinority.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.ethnicMinority.progress>=1 || metrics.ethnicMinority.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.ethnicMinority.progress<1" class="text-primary">
未达标
</span>
<span v-if="metrics.ethnicMinority.remaining>0" class="text-gray-500">剩余 {{
metrics.ethnicMinority.remaining
}} 人</span>
</div>
</div>
<!-- 校级领导 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">校级领导代表</div>
<div class="metric-value">{{ metrics.schoolLeader.current }}<span
class="metric-target"
>/ {{ metrics.schoolLeader.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-user-graduate text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-primary"
:style="{width: metrics.schoolLeader.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.schoolLeader.progress>=1 || metrics.schoolLeader.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.schoolLeader.progress<1" class="text-primary">
未达标
</span>
<span v-if="metrics.schoolLeader.remaining>0" class="text-gray-500">剩余 {{
metrics.schoolLeader.remaining
}} 人</span>
</div>
</div>
<!-- 中层领导 -->
<div class="metric-card">
<div class="metric-header">
<div>
<div class="metric-title">中层领导</div>
<div class="metric-value">{{ metrics.middleLevelLeader.current }}<span
class="metric-target"
>/ {{ metrics.middleLevelLeader.target }}</span></div>
</div>
<div class="metric-icon bg-primary-light">
<i class="fas fa-user-graduate text-primary"></i>
</div>
</div>
<div class="progress-bar">
<div class="progress-bar-inner bg-primary"
:style="{width: metrics.middleLevelLeader.progress * 100 + '%'}"></div>
</div>
<div class="progress-info">
<span v-if="metrics.middleLevelLeader.progress>=1 || metrics.middleLevelLeader.target===0"
class="text-success">
已达标
</span>
<span v-else-if="metrics.middleLevelLeader.progress<1" class="text-primary">
未达标
</span>
<span v-if="metrics.middleLevelLeader.remaining>0" class="text-gray-500">剩余 {{
metrics.middleLevelLeader.remaining
}} 人</span>
</div>
</div>
</div>
</div>
<div class="card">
<h2 class="legend-title">指标说明</h2>
<div class="legend-container">
<div class="legend-item">
<div class="legend-color bg-success"></div>
<span class="legend-text">已达标:当前数量已达到或超过目标值</span>
</div>
<div class="legend-item">
<div class="legend-color bg-primary"></div>
<span class="legend-text">未达标:当前数量未达到目标值,但已有代表选派</span>
</div>
</div>
<div class="update-time"></div>
</div>
<div class="card">
<h2 class="legend-title">上报说明</h2>
<div class="legend-container">
<div class="legend-item">
<div class="legend-color bg-warning "></div>
<span class="legend-text">正式代表完成信息填写后才能进行上报,列席代表可直接上报且不计入名额</span>
</div>
</div>
</div>
</div>
</div>
`,
props: {
sessionId: {
type: String,
required: false,
default: null
},
unionId: {
type: String,
required: false,
default: null
}
},
data() {
return {
metrics: {
total: {
current: 15,
target: 15,
progress: 100,
remaining: 0
},
female: {
current: 3,
target: 5,
progress: 60,
remaining: 2
},
seniorTeacher: {
current: 4,
target: 6,
progress: 66.7,
remaining: 2
},
ordinaryTeacher: {
current: 8,
target: 10,
progress: 80,
remaining: 2
},
under45: {
current: 5,
target: 7,
progress: 71.4,
remaining: 2
},
ethnicMinority:{
current: 1,
target: 2,
progress: 50,
remaining: 1
},
schoolLeader:{
current: 2,
target: 3,
progress: 66.7,
remaining: 1
},
middleLevelLeader:{
current: 1,
target: 2,
progress: 33.3,
remaining: 1
}
},
teacherMeetName: null
}
},
watch: {
sessionId(val) {
this.refreshData()
}
},
methods: {
async refreshData(sessionId, teacherMeetName, unionId) {
this.teacherMeetName = teacherMeetName
const {code, data} = await $.get('/platform/teacherCongress/delegate/push/selfUnionPushMetric', {
teacherMeetId: sessionId,
unionId: unionId
})
if (code === 0) {
if (data) {
this.metrics = data
} else {
this.$message.error('暂未分配名额')
}
}
}
},
style: /*language=CSS*/ `
.representative-allocation {
margin: 0;
padding: 0;
}
.representative-allocation .card {
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
margin-bottom: 24px;
}
.representative-allocation .header-x {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.representative-allocation .header-x .title h1 {
font-size: 24px;
font-weight: 700;
margin: 0;
color: #111827;
}
.representative-allocation .header-x .title p {
font-size: 14px;
color: #6b7280;
margin-top: 8px;
margin-bottom: 0;
}
.representative-allocation .buttons {
display: flex;
gap: 16px;
}
.representative-allocation .grid-cols-3 {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 24px;
}
.representative-allocation .metric-card {
background-color: #f3f3f3;
border-radius: 8px;
padding: 24px;
}
.representative-allocation .metric-card .metric-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
}
.representative-allocation .metric-card .metric-header .metric-title {
font-size: 14px;
color: #6b7280;
margin-bottom: 4px;
font-weight: 600;
}
.representative-allocation .metric-card .metric-header .metric-value {
font-size: 30px;
font-weight: 700;
color: #111827;
}
.representative-allocation .metric-card .metric-header .metric-value .metric-target {
font-size: 14px;
color: #6b7280;
margin-left: 4px;
}
.representative-allocation .metric-card .metric-icon {
width: 40px;
height: 40px;
border-radius: 50%;
align-items: center;
justify-content: center;
display: none;
}
.representative-allocation .progress-bar {
height: 4px;
border-radius: 2px;
background-color: #f0f0f0;
overflow: hidden;
}
.representative-allocation .progress-bar .progress-bar-inner {
height: 100%;
transition: width 0.3s ease;
}
.representative-allocation .progress-info {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
}
.representative-allocation .bg-primary {
background-color: #1677ff;
}
.representative-allocation .bg-primary-light {
background-color: rgba(22, 119, 255, 0.1);
}
.representative-allocation .text-primary {
color: #1677ff;
}
.representative-allocation .bg-secondary {
background-color: #4096ff;
}
.representative-allocation .bg-success {
background-color: #52c41a;
}
.representative-allocation .text-success {
color: #52c41a;
}
.representative-allocation .bg-warning {
background-color: #faad14;
}
.representative-allocation .text-warning {
color: #faad14;
}
.representative-allocation .bg-warning-light {
background-color: rgba(250, 173, 20, 0.1);
}
.representative-allocation .text-gray-500 {
color: #6b7280;
}
.representative-allocation .legend-title {
font-size: 18px;
font-weight: 700;
color: #111827;
margin-bottom: 16px;
}
.representative-allocation .legend-container {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.representative-allocation .legend-item {
display: flex;
align-items: center;
}
.representative-allocation .legend-item .legend-color {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.representative-allocation .legend-item .legend-text {
color: #6b7280;
}
.representative-allocation .update-time {
font-size: 14px;
color: #6b7280;
margin-top: 16px;
}
`
}