bug整改:亲子活动、活动申报、经费预算管理、费用报销管理、新闻投稿、职工建家

This commit is contained in:
2026-05-18 10:31:03 +08:00
parent e9dd000fc9
commit 2bcf1b3edd
8 changed files with 310 additions and 78 deletions
@@ -27,11 +27,11 @@ import java.util.Map;
@IocBean
public class SmsJshvcServiceImpl implements SmsService {
private static final String APPID = "1430576717768122368";
private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD";
private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
private static final String MSG_URL = "https://gateway.jshvc.edu.cn/mp/restful/v2/message/send";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:";
private static final String APPID = "";
private static final String APP_SECRET = "";
private static final String TOKEN_URL = "";
private static final String MSG_URL = "";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "";
@Inject
private RedisService redisService;
@@ -273,8 +273,8 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
dao().insert(familyUser);
if (StrUtil.isNotBlank(familyUser.getActivityCourseId())) {
TrainSignUpActivityCourse fetch = dao().fetch(TrainSignUpActivityCourse.class, familyUser.getActivityCourseId());
TrainSignUpUserCourse userCourse = new TrainSignUpUserCourse();
FamilyActivityCourse fetch = dao().fetch(FamilyActivityCourse.class, familyUser.getActivityCourseId());
FamilyUserCourse userCourse = new FamilyUserCourse();
userCourse.setActivityId(familyUser.getActivityId());
userCourse.setCourseId(familyUser.getCourseId());
userCourse.setUserId(familyUser.getUserId());
@@ -15,6 +15,10 @@ layout("/layouts/platform.html"){
.el-row--flex {
align-items: center;
}
.header-left {
display: none !important;
}
</style>
<div id="app" v-cloak>
@@ -77,6 +77,7 @@ layout("/layouts/platform.html"){
dicts: ["ARTICLE_ORIGIN"],
computed: {
origins() {
console.log(this.dict,'this.dict')
if (this.dict?.type?.ARTICLE_ORIGIN && this.permission && this.permission.roles) {
return this.dict.type.ARTICLE_ORIGIN.filter((item) => this.permission && this.permission.roles.includes(item.code))
}
@@ -52,6 +52,118 @@ layout("/layouts/platform.html"){
<kpi-form ref="kpiFormRef" @refresh="doSearch"></kpi-form>
</div>
<style nonce="${cspNonce!}">
#app .kpi-plus .kpi-table-wrap {
width: 100%;
max-height: 50vh;
overflow-y: auto;
}
#app .kpi-plus .kpi-content-table {
width: 100% !important;
min-width: 100%;
table-layout: fixed;
border-collapse: collapse;
position: relative;
}
#app .kpi-plus .kpi-content-table thead {
background: #fafafa;
position: sticky;
top: 0;
z-index: 1;
}
#app .kpi-plus .kpi-content-table thead th {
color: rgb(107 114 128);
background: #fafafa;
font-weight: 600;
padding: 14px 20px;
line-height: 20px;
box-sizing: border-box;
text-align: center;
}
#app .kpi-plus .kpi-content-table tbody td {
padding: 14px 20px;
color: rgb(17 24 39);
line-height: 20px;
text-align: center;
vertical-align: middle;
box-sizing: border-box;
}
#app .kpi-plus .kpi-content-table tbody td.item-content {
border-bottom: 1px solid rgb(229 231 235);
text-align: left;
word-break: break-all;
}
#app .kpi-plus .kpi-content-table tbody .category-split td,
#app .kpi-plus .kpi-content-table tbody td[rowspan] {
border-bottom: 1px solid rgb(33 40 53) !important;
}
#app .kpi-plus .kpi-content-table tbody tr:last-child {
position: sticky;
bottom: 0;
background-color: white;
z-index: 1;
}
#app .kpi-plus .kpi-content-table .category-name-cell,
#app .kpi-plus .kpi-content-table .score-total-cell {
font-weight: 500;
}
#app .kpi-plus .kpi-add-category-btn.el-button--primary {
background-color: #1f6db8;
border-color: #1f6db8;
}
#app .kpi-plus .kpi-add-category-btn.el-button--primary:hover,
#app .kpi-plus .kpi-add-category-btn.el-button--primary:focus {
background-color: #1b61a4;
border-color: #1b61a4;
}
#app .kpi-plus .td-extra {
margin-top: 4px;
transition: opacity 0.3s ease;
}
#app .kpi-plus .td-extra i {
margin-right: 6px;
}
#app .kpi-plus .td-extra .edit {
color: #0a84ff;
}
#app .kpi-plus .td-extra .edit:hover {
color: #1375d8;
transform: scale(1.2);
}
#app .kpi-plus .td-extra .delete {
color: #ff3b30;
}
#app .kpi-plus .td-extra .delete:hover {
color: #ec2216;
transform: scale(1.2);
}
#app .kpi-plus .td-extra .add {
color: rgb(17 24 39);
}
#app .kpi-plus .td-extra .add:hover {
color: #0a84ff;
transform: scale(1.2);
}
</style>
<script nonce="${cspNonce!}">
<!--#include('kpiForm.js'){}#-->
@@ -28,73 +28,81 @@ const kpiForm = {
<div class="process-title">考核内容</div>
<div class="kpi-plus">
<el-row type="flex" justify="space-between" class="mb20">
<el-button type="primary" icon="el-icon-plus" size="small" @click="showAddCategoryDialog">新增考核项目
<el-button class="kpi-add-category-btn" type="primary" icon="el-icon-plus" size="small" style="background-color: #1f6db8; border-color: #1f6db8;" @click="showAddCategoryDialog">新增考核项目
</el-button>
</el-row>
<div>
<table>
<div class="kpi-table-wrap" style="width: 100%;">
<table class="kpi-content-table" style="width: 100%; min-width: 100%; table-layout: fixed; border-collapse: collapse;">
<colgroup>
<col style="width: 10%">
<col style="width: 15%">
<col style="width: 50%">
<col style="width: 10%">
<col style="width: 10%">
<col style="width: 5%">
</colgroup>
<thead class="bg-gray-50">
<tr>
<th style="width: 10%">序号</th>
<th style="width: 10%">考核项目名称</th>
<th style="width: 50%">考核内容详情</th>
<th style="width: 10%">单项分</th>
<th style="width: 10%">分值</th>
<th style="width: 10%">操作</th>
<th>序号</th>
<th>考核项目名称</th>
<th>考核内容详情</th>
<th>单项分</th>
<th>分值</th>
<th>操作</th>
</tr>
</thead>
<tbody ref="categoryList" style="height: 50vh;overflow-y: auto;display: table;width: 100%">
<tbody ref="categoryList">
<template v-for="(category,categoryIndex) in formData.categoryList">
<tr v-for="(item,itemIndex) in category.items"
:class="{'category-split':itemIndex+1===category.items.length}">
<td v-if="itemIndex===0" :rowspan="category.items.length" class="text-center" style="width: 10%">
<td v-if="itemIndex===0" :rowspan="category.items.length" class="text-center">
<div>{{categoryIndex+1}}</div>
</td>
<td v-if="itemIndex===0" :rowspan="category.items.length" style="width: 10%">
<span v-if="category?.$edit_status?.name === false">
<td v-if="itemIndex===0" :rowspan="category.items.length" class="category-name-cell">
<span v-if="!isCategoryNameEditing(category)">
{{category.name}}
</span>
<el-input v-model="category.name" v-else
@blur="$set(category['$edit_status'],'name',false)"></el-input>
@blur="setCategoryNameEditing(category,false)"></el-input>
<div class="td-extra" v-if="category?.$edit_status?.name === false">
<div class="td-extra" v-if="!isCategoryNameEditing(category)">
<i class="fa fa-pencil edit" aria-hidden="true"
@click="$set(category['$edit_status'],'name',true)"></i>
@click="setCategoryNameEditing(category,true)"></i>
<i class="fa fa-times delete" aria-hidden="true"
@click="onDeleteCategory(categoryIndex)"></i>
<i class="fa fa-plus" aria-hidden="true"
<i class="fa fa-plus add" aria-hidden="true"
@click="addNewItem(categoryIndex)"></i>
</div>
</td>
<td class="item-content" style="width: 50%">
<span v-if="item?.$edit_status?.content === false">
<td class="item-content">
<span v-if="!isItemEditing(item,'content')">
{{item.content}}
</span>
<el-input v-else v-model="item.content" type="textarea" :rows="2"
@blur="$set(item['$edit_status'],'content',false)"></el-input>
<div class="td-extra" v-if="item?.$edit_status?.content === false">
@blur="setItemEditing(item,'content',false)"></el-input>
<div class="td-extra" v-if="!isItemEditing(item,'content')">
<i class="fa fa-pencil edit" aria-hidden="true"
@click="$set(item['$edit_status'],'content',true)"></i>
@click="setItemEditing(item,'content',true)"></i>
<i class="fa fa-times delete" aria-hidden="true"
@click="onDeleteItem(categoryIndex,itemIndex)"></i>
</div>
</td>
<td style="width: 10%">
<span v-if="item?.$edit_status?.score === false">
<td>
<span v-if="!isItemEditing(item,'score')">
{{item.score}}
</span>
<el-input-number v-else v-model="item.score" :min="1"
@blur="$set(item['$edit_status'],'score',false)"></el-input-number>
<div class="td-extra" v-if="item?.$edit_status?.score === false">
@blur="setItemEditing(item,'score',false)"></el-input-number>
<div class="td-extra" v-if="!isItemEditing(item,'score')">
<i class="fa fa-pencil edit" aria-hidden="true"
@click="$set(item['$edit_status'],'score',true)"></i>
@click="setItemEditing(item,'score',true)"></i>
</div>
</td>
<td v-if="itemIndex===0" :rowspan="category.items.length" style="width: 10%">
{{category.items.reduce((sum, item) => sum + (item.score || 0), 0)}}
<td v-if="itemIndex===0" :rowspan="category.items.length" class="score-total-cell">
{{getCategoryScore(category)}}
</td>
<td style="width: 10%">
<td>
<div class="td-extra" style="opacity: 1!important;">
<i class="fa fa-times delete" aria-hidden="true"
@click="onDeleteItem(categoryIndex,itemIndex)"></i>
@@ -111,11 +119,12 @@ const kpiForm = {
<td></td>
<td></td>
<td></td>
<td :colspan="2">
<td>
<span style="font-weight: bold">
{{formData.categoryList.flatMap(v=>v.items).map(v=>v.score || 0).reduce((sum, v) => sum + v, 0)}}
{{getTotalScore()}}
</span>
</td>
<td></td>
</tr>
</tbody>
</table>
@@ -248,8 +257,14 @@ const kpiForm = {
return
}
}
//每个考核内容至少要有分数
const hasNoneScore = this.formData.categoryList.flatMap((v) => v.items).some((v) => !v.score)
// 考核内容详情不能为空,防止删除最后一行后保存出空明细。
const hasNoneContent = this.getAllItems().some((v) => !v.content || !String(v.content).trim())
if (hasNoneContent) {
this.$message.error("考核内容详情不能为空")
return
}
// 每条考核内容详情都必须有单项分,避免汇总分值和保存明细不一致。
const hasNoneScore = this.getAllItems().some((v) => !v.score)
if (hasNoneScore) {
this.$message.error("考核内容需要设置分数")
return
@@ -298,18 +313,32 @@ const kpiForm = {
},
initStatus() {
if (!this.formData.categoryList || !Array.isArray(this.formData.categoryList)) {
this.$set(this.formData, "categoryList", [])
}
if (!this.formData.scoreLevels || !Array.isArray(this.formData.scoreLevels)) {
this.$set(this.formData, "scoreLevels", [])
}
this.formData.categoryList.forEach((category) => {
// 编辑状态只服务于弹窗内联编辑,不提交到后端业务字段。
this.$set(category, "$edit_status", {
name: false // 初始化 name 的编辑状态
name: false
})
if (category.items && Array.isArray(category.items)) {
category.items.forEach((item) => {
this.$set(item, "$edit_status", {
content: false, // 控制 item.content 的编辑状态
score: false // 控制 item.score 的编辑状态
})
})
if (!category.items || !Array.isArray(category.items) || category.items.length === 0) {
this.$set(category, "items", [this.createItem(category, 0, true)])
}
category.items.forEach((item, itemIndex) => {
if (!item.categoryId) {
this.$set(item, "categoryId", category.id)
}
if (!item.sortOrder) {
this.$set(item, "sortOrder", itemIndex + 1)
}
this.$set(item, "$edit_status", {
content: false,
score: false
})
})
})
},
@@ -319,7 +348,7 @@ const kpiForm = {
name: "",
code: "",
sortOrder: this.formData.categoryList.length + 1,
items: [{}]
items: []
}
},
@@ -331,25 +360,16 @@ const kpiForm = {
const cateGoryId = Date.now()
// 新增考核项目时默认放入一条可编辑明细,方便继续录入多个考核内容详情。
const category = {
id: cateGoryId,
name: this.newCategory.name,
code: this.newCategory.code,
sortOrder: this.newCategory.sortOrder,
items: [
{
categoryId: cateGoryId,
content: null,
score: 1,
sortOrder: 1,
$edit_status: {
content: true,
score: false
}
}
],
items: [],
$edit_status: { name: false }
}
category.items.push(this.createItem(category, 0, true))
this.formData.categoryList.push(category)
this.dialogVisible = false
@@ -358,18 +378,67 @@ const kpiForm = {
addNewItem(categoryIndex) {
const category = this.formData.categoryList[categoryIndex]
const newItem = {
category.items.push(this.createItem(category, category.items.length, true))
},
createItem(category, itemIndex, editContent) {
return {
id: Date.now(),
categoryId: category.id,
content: "",
score: 1,
sortOrder: category.items.length + 1,
sortOrder: itemIndex + 1,
$edit_status: {
content: true,
content: editContent,
score: false
}
}
category.items.push(newItem)
},
getAllItems() {
const list = []
const categoryList = this.formData.categoryList || []
categoryList.forEach((category) => {
const items = category.items || []
items.forEach((item) => {
list.push(item)
})
})
return list
},
getCategoryScore(category) {
return (category.items || []).reduce((sum, item) => {
return sum + Number(item.score || 0)
}, 0)
},
getTotalScore() {
return (this.formData.categoryList || []).reduce((sum, category) => {
return sum + this.getCategoryScore(category)
}, 0)
},
isCategoryNameEditing(category) {
return !!(category.$edit_status && category.$edit_status.name)
},
setCategoryNameEditing(category, status) {
if (!category.$edit_status) {
this.$set(category, "$edit_status", {})
}
this.$set(category.$edit_status, "name", status)
},
isItemEditing(item, field) {
return !!(item.$edit_status && item.$edit_status[field])
},
setItemEditing(item, field, status) {
if (!item.$edit_status) {
this.$set(item, "$edit_status", {})
}
this.$set(item.$edit_status, field, status)
},
// 删除考核项目
@@ -386,7 +455,7 @@ const kpiForm = {
.catch(() => {})
},
// 删除考核内容
// 删除当前行的考核内容详情和单项分;最后一行被删除时保留空白行,避免项目名称行消失。
onDeleteItem(cateGoryIndex, itemIndex) {
this.$confirm("确认删除该考核内容吗?", "提示", {
confirmButtonText: "确定",
@@ -394,7 +463,14 @@ const kpiForm = {
type: "warning"
})
.then(() => {
this.formData.categoryList[cateGoryIndex].items.splice(itemIndex, 1)
const category = this.formData.categoryList[cateGoryIndex]
category.items.splice(itemIndex, 1)
if (category.items.length === 0) {
category.items.push(this.createItem(category, 0, true))
}
category.items.forEach((item, index) => {
item.sortOrder = index + 1
})
this.$message.success("删除成功")
})
.catch(() => {})
@@ -405,28 +481,45 @@ const kpiForm = {
}
.kpi-table-wrap {
width: 100%;
max-height: 50vh;
overflow-y: auto;
}
.kpi-plus .kpi-content-table {
min-width: 100%;
}
.kpi-add-category-btn {
background-color: #1f6db8;
border-color: #1f6db8;
}
.kpi-add-category-btn:hover,
.kpi-add-category-btn:focus {
background-color: #1b61a4;
border-color: #1b61a4;
}
.kpi-plus table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
position: relative;
}
.kpi-plus table thead {
background: #fafafa;
display: table;
table-layout: fixed;
width: calc(100% - 1em);
}
.kpi-plus table tbody {
display: block;
position: sticky;
top: 0;
z-index: 1;
}
.kpi-plus thead th,
.kpi-plus tbody td {
width: auto;
min-width: 100px;
box-sizing: border-box;
vertical-align: middle;
}
.kpi-plus table thead th {
@@ -441,6 +534,7 @@ const kpiForm = {
position: sticky;
bottom: 0;
background-color: white;
z-index: 1;
}
.kpi-plus table tbody tr td {
@@ -463,6 +557,7 @@ const kpiForm = {
.kpi-plus table tbody td.item-content {
border-bottom: 1px solid rgb(229 231 235);
text-align: left;
word-break: break-all;
}
.kpi-plus table tbody .category-split td {
@@ -473,6 +568,11 @@ const kpiForm = {
border-bottom: 1px solid rgb(33 40 53);
}
.kpi-plus .category-name-cell,
.kpi-plus .score-total-cell {
font-weight: 500;
}
.kpi-plus .score-levels-table .el-input-number{
width: unset!important;
}
@@ -481,9 +581,14 @@ const kpiForm = {
.td-extra {
/*display: none;*/
/*opacity: 0;*/
margin-top: 4px;
transition: opacity 0.3s ease;
}
.td-extra i {
margin-right: 6px;
}
.td-extra .edit {
color: #0a84ff;
}
@@ -502,6 +607,15 @@ const kpiForm = {
transform: scale(1.2);
}
.td-extra .add {
color: rgb(17 24 39);
}
.td-extra .add:hover {
color: #0a84ff;
transform: scale(1.2);
}
`
}
@@ -13,7 +13,7 @@ layout("/layouts/platform.html"){
<search-item label="预算年度">
<el-select placeholder="请选择预算年度" v-model="pageForm.outlayManageId"
style="width: 100%;"
filterable @change="">
filterable @change="getApplyMoney">
<el-option v-for="item in outlayManageList"
:label="item.year"
:key="item.id"
@@ -177,6 +177,7 @@ const apply = {
this.dialogVisible = true
this.formData = clone(applyRow)
this.$set(this.formData, 'applyTime', this.formData.startTime + '-' + this.formData.endTime)
console.log(this.$store.state.user.sex, 'this.$store.state.user.sex');
this.$set(this.formData, 'sex', this.$store.state.user.sex)
await this.queryTimesByDay()
}