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,162 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.TeacherCongressDelegatePush;
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
import io.swagger.annotations.Api;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author zhf
* @date 2025/9/22 20:41
* @description
*/
@IocBean
@At("/platform/teacherCongress/delegate/push")
@Ok("json:full")
@Api(value = "/platform/teacherCongress/delegate/push", tags = "教代会代表推选")
public class TeacherCongressDelegatePushController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/teachercongress/delegate/push/index.html")
@SaCheckPermission("tc.delegate.push")
public void index() {
}
/**
* 获取本分工会下的非代表用户
*
* @param sessionId
* @return
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.delegate.push")
@SLog(tag = "民主管理-教代会代表推选", msg = "获取本分工会下的非代表用户")
public Result getUnionUser(String sessionId, String unionId) {
// 查询预选中的代表
Cnd cndx = Cnd.where("sessionId", "=", sessionId);
cndx.andEX("unionId", "=", SecurityUtil.getUnionId());
List<Teacher_congress_delegate> delegateList = dao.query(Teacher_congress_delegate.class, cndx);
// u.professionalTitle,
// u.professionalLevel,
Sql sql = Sqls.create("""
SELECT
u.id AS userId,
u.loginname as loginName,
u.username as userName,
u.sex,
u.birthday,
u.nation,
TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) AS age
FROM
`vw_user` u
LEFT JOIN teacher_congress_delegate tcd ON tcd.userId = u.id
AND tcd.sessionId = @sessionId
LEFT JOIN teacher_congress_delegate_push tcdp ON tcdp.userId = tcdp.id
AND tcdp.sessionId = @sessionId
$condition
""");
sql.setParam("sessionId", sessionId);
Cnd cnd = Cnd.NEW();
cnd.and("u.member", "=", 1);
cnd.and("tcd.userId", "is", null);
cnd.and("tcdp.id", "is", null);
cnd.and("u.unionId", "=", SecurityUtil.getUnionId());
sql.setCondition(cnd);
List userList = baseService.listMap(sql);
return Result.success(Map.of("userData", userList, "userValue", delegateList));
}
/**
* 推选代表
*/
@At
@Aop(TransAop.READ_COMMITTED)
public Object addPreselectionDb(@Param("userValue") String[] userValue, String sessionId, String representativeType) {
List<TeacherCongressDelegatePush> list = new ArrayList<>();
for (String id : userValue) {
View_user user = dao.fetch(View_user.class, Cnd.where("id", "=", id));
TeacherCongressDelegatePush tmd = new TeacherCongressDelegatePush();
tmd.setUserId(user.getId());
tmd.setSessionId(sessionId);
tmd.setDelegationId(null);
tmd.setUserName(user.getUsername());
tmd.setLoginName(user.getLoginname());
tmd.setSex(user.getSex());
tmd.setNation(user.getNation());
tmd.setEducation(user.getEducation());
tmd.setAcademicDegree(user.getAcademicDegree());
tmd.setPolitical(user.getPolitical());
tmd.setProfessionalTitle(user.getProfessionalTitle());
tmd.setProfessionalLevel(user.getProfessionalLevel());
tmd.setBirthday(user.getBirthday());
tmd.setUnitId(user.getUnitId());
tmd.setUnitName(user.getUnitName());
tmd.setUnionId(user.getUnionId());
tmd.setUnionName(user.getUnionName());
tmd.setOrdinaryTeacher(false);
tmd.setSeniorTeacher(false);
tmd.setSchoolLeader(false);
tmd.setMiddleLevelLeader(false);
tmd.setMobile(user.getMobile());
tmd.setIsJdh(true);
tmd.setIsGdh(false);
tmd.setRepresentativeType(representativeType);
if (ObjectUtil.isNotEmpty(user.getBirthday())) {
Date birthdayDate = user.getBirthday();
LocalDate birthday = birthdayDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
LocalDate currentDate = LocalDate.now();
int age = Period.between(birthday, currentDate).getYears();
tmd.setAge(age);
}
tmd.setPushTime(new Date());
list.add(tmd);
}
dao.insert(list);
return Result.success();
}
}
@@ -0,0 +1,186 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegate.models;
import com.budwk.app.base.model.BaseModel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
/**
* @author zhf
* @date 2025/9/22 20:40
* @description 代表推选表
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("teacher_congress_delegate_push")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("代表推选表")
public class TeacherCongressDelegatePush extends BaseModel {
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("代表用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column
@Comment("所属教代会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column
@Comment("所属代表团 当用户为校领导是该字段不为空")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String userName;
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String loginName;
@Column
@Comment("性别")
@ColDefine(type = ColType.VARCHAR, width = 2)
private String sex;
@Column
@Comment("出生日期")
@ColDefine(type = ColType.DATETIME)
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@Column
@Comment("民族")
@ColDefine(type = ColType.VARCHAR, width = 10)
private String nation;
@Column
@Comment("政治面貌")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String political;
@Column
@Comment("学历")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String education;
@Column
@Comment("学位")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String academicDegree;
@Column
@Comment("职称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String professionalTitle;
@Column
@Comment("职级")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String professionalLevel;
@Column
@Comment("是否专任教师")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean ordinaryTeacher;
@Column
@Comment("是否高级职称专任教师")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean seniorTeacher;
@Column
@Comment("是否校领导")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean schoolLeader;
@Column
@Comment("是否中层领导")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean middleLevelLeader;
@Column
@Comment("行政职称===>position")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String administrativeTitle;
@Column
@Comment("年龄")
@ColDefine(type = ColType.INT)
private Integer age;
@Column
@Comment("单位ID")
@ColDefine(type = ColType.VARCHAR)
private String unitId;
@Column
@Comment("单位名称")
@ColDefine(type = ColType.VARCHAR)
private String unitName;
@Column
@Comment("分工会ID")
@ColDefine(type = ColType.VARCHAR)
private String unionId;
@Column
@Comment("分工会名称")
@ColDefine(type = ColType.VARCHAR)
private String unionName;
@Column
@Comment("手机号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String mobile;
@Column
@Comment("推送时间")
@ColDefine(type = ColType.DATETIME)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date pushTime;
@Column
@Comment("教代会代表类型")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String roleId;
@Column
@Comment("教代会")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isJdh;
@Column
@Comment("工代会")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isGdh;
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String bz;
@Column
@Comment("代表类型(正式代表 列席代表)")
@ColDefine(type = ColType.VARCHAR, width = 4)
private String representativeType;
}
@@ -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;
}
`
}