This commit is contained in:
那些花儿
2025-09-09 14:59:56 +08:00
parent bee7b352a6
commit d7358932dc
31 changed files with 1964 additions and 2912 deletions
@@ -1,5 +1,6 @@
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -23,13 +24,14 @@ public class H5QsvController {
private QsvActivityService qsvActivityService;
@At("")
@Ok("beetl:/mobile/qsv/index.html")
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/index.html")
@SaCheckLogin
public void index() {
}
// 分页查询
@At
@SaCheckLogin
public Result pageData(@Valid PageForm pageForm, @Valid String category) {
Cnd cnd = Cnd.NEW();
cnd.andEX("category", "=", category);
@@ -1,7 +1,9 @@
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
@@ -44,26 +46,30 @@ public class H5QsvQuizController {
private QsvQuizService qsvQuizService;
@At("")
@Ok("beetl:/mobile/qsv/quiz/index.html")
@SaCheckLogin
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/quiz/index.html")
public void index() {
}
@At
@Ok("beetl:/mobile/qsv/quiz/result.html")
@SaCheckLogin
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/quiz/result.html")
public void result() {
}
// 题目列表
@At
@SaCheckLogin
public Result subjects(@Valid String activityId) {
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("您无需参加此次答题,感谢您的关注!");
if(activity.getGroupId() != null){
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("您无需参加此次答题,感谢您的关注!");
}
}
String mode = activity.getMode();
@@ -46,6 +46,13 @@ public class ProposalFeedbackEvaluationController {
public void index() {
}
@At(value = "/platform/h5/proposal/feedbackEvaluation",top = true)
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/feedbackEvaluation/index.html")
@SaCheckPermission("proposal.feedbackEvaluation")
public void h5Index() {
}
@At
@SaCheckPermission("proposal.feedbackEvaluation")
@ApiOperation("分页列表")
@@ -49,6 +49,14 @@ public class ProposalSchoolLeaderApprovalController {
public void index() {
}
@At(value = "/platform/h5/proposal/schoolLeaderApproval",top = true)
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/schoolLeaderApproval/index.html")
@SaCheckPermission("proposal.schoolLeaderApproval")
public void h5Index() {
}
@At
@SaCheckPermission("proposal.schoolLeaderApproval")
@ApiOperation("分页列表")
@@ -44,17 +44,17 @@ public class SuggestionBoxMineController {
}
@At(value = "/platform/h5/suggestionBox/mine",top = true)
@At(value = "/platform/h5/suggestionBox/mine", top = true)
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/mine/index.html")
@SaCheckLogin
public void h5Index(){
public void h5Index() {
}
@At
@SaCheckPermission("suggestionBox.mine")
public Result pageData(Integer pageNumber, Integer pageSize, String title, Integer year) {
public Result pageData(Integer pageNumber, Integer pageSize, String searchKeyword, Integer year) {
Sql sql = Sqls.create("""
SELECT
info.id,
@@ -89,8 +89,8 @@ public class SuggestionBoxMineController {
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
if (StrUtil.isNotBlank(title)) {
cnd.where().andLike("info.title", title);
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.where().andLike("info.title", searchKeyword);
}
cnd.andEX("YEAR(info.submitTime)", "=", year);
cnd.desc("info.submitTime");
@@ -1,10 +1,11 @@
package com.budwk.app.zhgh.democratic.suggestionBox.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.suggestionBox.service.SuggestionBoxService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -36,15 +37,22 @@ public class SuggestionBoxQueryController {
@At("")
@Ok("beetl:/platform/zhgh/democratic/suggestionBox/query/index.html")
@SaCheckLogin
@SaCheckPermission("suggestionBox.query")
public void index() {
}
@At(value = "/platform/h5/suggestionBox/query", top = true)
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/query/index.html")
@SaCheckPermission("suggestionBox.query")
public void h5Index() {
}
@At
@SaCheckLogin
public Result pageData(Integer pageNumber, Integer pageSize, String title, Integer year) {
@SaCheckPermission("suggestionBox.query")
public Result pageData(Integer pageNumber, Integer pageSize, String searchKeyword, Integer year) {
Sql sql = Sqls.create("""
SELECT
info.id,
@@ -79,7 +87,9 @@ public class SuggestionBoxQueryController {
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("YEAR(info.submitTime)", "=", year);
cnd.and(Cnd.likeEX("info.title", title));
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.where().andLike("info.title", searchKeyword);
}
cnd.desc("info.submitTime");
sql.setCondition(cnd);
Pagination<NutMap> pagination = suggestionBoxService.listPageMap(pageNumber, pageSize, sql);
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.democratic.suggestionBox.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
@@ -35,14 +36,22 @@ public class SuggestionXghController {
@At("")
@Ok("beetl:/platform/zhgh/democratic/suggestionBox/xgh/index.html")
@SaCheckLogin
@SaCheckPermission("suggestionBox.xgh")
public void index() {
}
@At(value = "/platform/h5/suggestionBox/xgh",top = true)
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/xgh/index.html")
@SaCheckPermission("suggestionBox.xgh")
public void h5Index() {
}
@At
@SaCheckLogin
public Result pageData(PageForm pageForm, String taskId, String title, Integer year, boolean approval) {
@SaCheckPermission("suggestionBox.xgh")
public Result pageData(PageForm pageForm, String taskId, String searchKeyword, Integer year, boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.id,
@@ -88,7 +97,10 @@ public class SuggestionXghController {
}
cnd.andEX("YEAR(info.submitTime)", "=", year);
cnd.and(Cnd.likeEX("info.title", title));
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.where().andLike("info.title", searchKeyword);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.submitTime");
@@ -103,7 +115,7 @@ public class SuggestionXghController {
}
@At
@SaCheckLogin
@SaCheckPermission("suggestionBox.xgh")
@ApiOperation("查询任务")
public Result queryTask(@Valid @Param("taskId") String taskId) {
Sql sql = Sqls.create("""
@@ -219,10 +219,22 @@ body {
}
.form-container .van-cell-group__title {
padding: 12px 16px 8px;
font-size: 15px;
color: #323233;
font-weight: bold;
color: var(--color-black);
font-weight: 600;
padding: 10px;
position: relative;
}
.form-container .van-cell-group__title::before{
content: "";
width: 5px;
background: var(--color-primary);
display: inline-block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
}
.form-container .van-field__label {
@@ -256,3 +268,32 @@ body {
margin-top: 8px;
}
/***************************查看详情CSS********************************/
.detail-container {
background: #f9f9f9
}
.detail-container .van-cell-group__title{
font-size: 15px;
color: var(--color-black);
font-weight: 600;
padding: 10px;
position: relative;
}
.detail-container .van-cell-group__title::before{
content: "";
width: 5px;
background: var(--color-primary);
display: inline-block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
}
.detail-container .direction-column-cell{
display: flex;
flex-direction: column;
}
@@ -8,8 +8,8 @@
<title>智慧工会</title>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css"/>
<link rel="stylesheet" href="https://cdn.staticfile.net/animate.css/4.1.1/animate.css"/>
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css"/>
@@ -396,13 +396,13 @@ const subjectForm = {
/* background: rgb(248, 249, 250);*/
/*}*/
.questionnaire-editor {
::v-deep .questionnaire-editor {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.editor-header {
::v-deep .editor-header {
background: white;
padding: 20px;
border-radius: 8px;
@@ -410,7 +410,7 @@ const subjectForm = {
margin-bottom: 20px;
}
.subject-item {
::v-deep .subject-item {
background: white;
border-radius: 8px;
padding: 25px;
@@ -420,18 +420,18 @@ const subjectForm = {
border: 1px dashed #d9d9d9;
}
.subject-item:hover {
::v-deep .subject-item:hover {
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
/*transform: translateY(-1px);*/
}
.subject-header {
::v-deep .subject-header {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 15px;
}
.subject-toolbar {
::v-deep .subject-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
@@ -440,7 +440,7 @@ const subjectForm = {
border-top: 1px solid #eee;
}
.toolbar {
::v-deep .toolbar {
background: white;
padding: 15px;
border-radius: 8px;
@@ -448,12 +448,12 @@ const subjectForm = {
z-index: 1000;
}
.toolbar-btn {
::v-deep .toolbar-btn {
margin: 5px 0;
width: 100%;
}
.option-item {
::v-deep .option-item {
background: #f8f9fa;
border-radius: 4px;
padding: 10px;
@@ -462,33 +462,33 @@ const subjectForm = {
align-items: center;
}
.option-drag-handle {
::v-deep .option-drag-handle {
cursor: move;
margin-right: 10px;
color: #909399;
}
.option-content {
::v-deep .option-content {
flex-grow: 1;
margin-right: 10px;
}
.option-tools {
::v-deep .option-tools {
display: flex;
align-items: center;
gap: 10px;
}
.preview-dialog {
::v-deep .preview-dialog {
max-width: 800px;
margin: 0 auto;
}
.date-visible {
::v-deep .date-visible {
margin-left: 20px;
}
.subject-meta {
::v-deep .subject-meta {
display: flex;
flex-wrap: wrap;
gap: 15px;
@@ -498,7 +498,7 @@ const subjectForm = {
border-radius: 4px;
}
.image-preview {
::v-deep .image-preview {
max-width: 200px;
max-height: 200px;
margin-top: 10px;
@@ -506,24 +506,24 @@ const subjectForm = {
border: 1px solid #dcdfe6;
}
.drag-handler {
::v-deep .drag-handler {
cursor: move;
color: #909399;
margin-right: 10px;
}
.meta-info {
::v-deep .meta-info {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
.subject-type-icon {
::v-deep .subject-type-icon {
margin-right: 5px;
}
.option-img-box {
::v-deep .option-img-box {
position: relative;
width: 40px;
height: 40px;
@@ -531,16 +531,16 @@ const subjectForm = {
flex-shrink: 0
}
.option-img-box:hover .el-icon-remove {
::v-deep .option-img-box:hover .el-icon-remove {
display: block;
}
.option-img-box img {
::v-deep .option-img-box img {
width: 100%;
height: 100%;
}
.option-img-box .el-icon-remove {
::v-deep .option-img-box .el-icon-remove {
position: absolute;
top: -7px;
right: -7px;
@@ -548,7 +548,7 @@ const subjectForm = {
display: none;
}
.option-img-box .el-icon-remove:hover {
::v-deep .option-img-box .el-icon-remove:hover {
transform: scale(1.1);
}
@@ -314,11 +314,11 @@ E.先进性"
}
},
style: /*language=CSS*/ `
.txt-import-dialog .el-dialog__body {
::v-deep .txt-import-dialog .el-dialog__body {
padding: 10px 20px;
}
.txt-import-container {
::v-deep .txt-import-container {
display: flex;
height: 80vh;
border-radius: 4px;
@@ -327,7 +327,7 @@ E.先进性"
background-color: #fff;
}
.txt-import-panel-header {
::v-deep .txt-import-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
@@ -337,7 +337,7 @@ E.先进性"
height: 32px; /* 统一高度 */
}
.txt-import-left-panel {
::v-deep .txt-import-left-panel {
width: 50%;
padding: 20px;
border-right: 1px solid #ebeef5;
@@ -345,7 +345,7 @@ E.先进性"
flex-direction: column;
}
.txt-import-right-panel {
::v-deep .txt-import-right-panel {
width: 50%;
padding: 20px;
background-color: #fff; /* 改为白色背景 */
@@ -354,22 +354,22 @@ E.先进性"
flex-direction: column;
}
.txt-import-title {
::v-deep .txt-import-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.txt-import-format-tip {
::v-deep .txt-import-format-tip {
display: flex;
align-items: center;
}
.txt-import-format-tooltip {
::v-deep .txt-import-format-tooltip {
margin-left: 10px;
}
.txt-import-input-area {
::v-deep .txt-import-input-area {
flex: 1;
position: relative;
margin-bottom: 15px;
@@ -379,7 +379,7 @@ E.先进性"
padding: 2px;
}
.txt-import-textarea {
::v-deep .txt-import-textarea {
width: 100%;
height: 100%;
padding: 15px;
@@ -394,25 +394,25 @@ E.先进性"
background-color: transparent;
}
.txt-import-textarea:focus {
::v-deep .txt-import-textarea:focus {
box-shadow: 0 0 0 2px rgba(24,103,176,0.2);
}
.txt-import-action-buttons {
::v-deep .txt-import-action-buttons {
display: flex;
justify-content: flex-end;
margin-top: 10px;
gap: 10px;
}
.txt-import-stats {
::v-deep .txt-import-stats {
color: #1867b0;
font-size: 13px;
display: flex;
align-items: center;
}
.txt-import-result-display {
::v-deep .txt-import-result-display {
flex: 1;
overflow-y: auto;
background-color: #f9fafc; /* 只在结果显示区域使用浅灰色背景 */
@@ -421,7 +421,7 @@ E.先进性"
border: 1px solid #ebeef5;
}
.txt-import-no-questions {
::v-deep .txt-import-no-questions {
height: 100%;
display: flex;
align-items: center;
@@ -431,7 +431,7 @@ E.先进性"
padding: 20px;
}
.txt-import-question-item {
::v-deep .txt-import-question-item {
background-color: #fff;
margin-bottom: 15px;
padding: 15px;
@@ -441,12 +441,12 @@ E.先进性"
border-left: 3px solid #67c23a;
}
.txt-import-question-item:hover {
::v-deep .txt-import-question-item:hover {
box-shadow: 0 4px 12px 0 rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.txt-import-question-title {
::v-deep .txt-import-question-title {
font-size: 15px;
font-weight: bold;
margin-bottom: 12px;
@@ -454,7 +454,7 @@ E.先进性"
line-height: 1.5;
}
.txt-import-question-number {
::v-deep .txt-import-question-number {
display: inline-block;
width: 25px;
height: 25px;
@@ -467,11 +467,11 @@ E.先进性"
font-size: 13px;
}
.txt-import-options {
::v-deep .txt-import-options {
margin-bottom: 12px;
}
.txt-import-option-item {
::v-deep .txt-import-option-item {
margin-bottom: 8px;
padding: 8px 12px;
background-color: #f5f7fa;
@@ -483,27 +483,27 @@ E.先进性"
transition: all 0.2s;
}
.txt-import-option-item:hover {
::v-deep .txt-import-option-item:hover {
background-color: #ebeef5;
}
.txt-import-option-item.txt-import-correct {
::v-deep .txt-import-option-item.txt-import-correct {
background-color: #f0f9eb;
color: #67c23a;
border-left: 3px solid #67c23a;
}
.txt-import-option-text {
::v-deep .txt-import-option-text {
flex: 1;
}
.txt-import-correct-mark {
::v-deep .txt-import-correct-mark {
color: #67c23a;
font-weight: bold;
font-size: 16px;
}
.txt-import-question-meta {
::v-deep .txt-import-question-meta {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed #ebeef5;
@@ -511,12 +511,12 @@ E.先进性"
color: #909399;
}
.txt-import-dialog-footer {
::v-deep .txt-import-dialog-footer {
display: flex;
justify-content: flex-end;
}
.txt-import-no-options {
::v-deep .txt-import-no-options {
padding: 10px;
color: #f56c6c;
font-style: italic;
@@ -66,7 +66,7 @@ layout("/layouts/platform.html"){
<el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="openAudit(row)" size="mini" type="primary">答复
<el-button v-if="row.taskState === 10" @click="openAudit(row)" size="mini" type="primary">评价
</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回
</el-button>
@@ -10,7 +10,7 @@ layout("/layouts/platform.html"){
style="width: 100%"></el-date-picker>
</search-item>
<search-item label="标题">
<el-input v-model="pageForm.title" placeholder="标题" clearable></el-input>
<el-input v-model="pageForm.searchKeyword" placeholder="标题" clearable></el-input>
</search-item>
</search>
</el-card>
@@ -0,0 +1,93 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-tabs v-model="pageForm.category" @change="doSearch">
<van-tab v-for="item in dict.type.ACTIVITY_QSV_CATEGORY" :name="item.code" :title="item.label"
:key="item.code"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/h5/qsv/pageData" :page_form.sync="pageForm" ref="tableListRef" title="title"
@ready="doSearch">
<!-- <template #header="{index,row}"></template>-->
<template v-slot="{index,row}">
<table-column label="开始时间">{{row.startTime}}</table-column>
<table-column label="结束时间">{{row.endTime}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onEnter(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
</template>
</table-list>
</div>
<script>
const vue = new Vue({
el: "#app",
dicts: ['ACTIVITY_QSV_CATEGORY'],
store,
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
category: "QUIZ"
},
categoryPaths: {
QUIZ: "/platform/h5/qsv/quiz",
SURVEY: "/platform/h5/qsv/survey",
VOTE: "/platform/h5/qsv/vote"
}
}
},
methods: {
historyBack,
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
async onEnter(item) {
debugger
if (item.groupId) {
const {
code,
data
} = await $.get('/platform/activity/basic/scope/getScopeUser', {activityGroupId: item.groupId})
if (code === 0 && data === 0) {
this.$toast.fail("您没有权限参与")
return
}
}
const {startTime, endTime} = item
if (this.$moment(startTime).unix() > this.$moment().unix()) {
this.$toast("未开始")
return
}
const path = this.categoryPaths[item.category]
if (path) {
pjaxReplace(path + "?id=" + item.id)
}
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,406 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
<div class="container" :style="{'padding-bottom': answerRecord.isFinish ? 0 : '84px'}">
<van-sticky offset-top="46px">
<!--开启计时 未完成 活动未结束-->
<div class="timer" v-if="activity.timeLimit > 0 && !answerRecord.isFinish && !isEnd">⏰{{ remainingTime }}s
</div>
<h2 class="title">{{ activity.title }}</h2>
</van-sticky>
<div v-if="!answerRecord.isFinish">
<div v-for="(subject, index) in subjects" :key="index" class="subject">
<p>{{index+1}}、{{ subject.title }}</p>
<div class="tag">
<van-tag v-if="subject.type === 'checkbox'" type="primary" size="large">多选</van-tag>
<van-tag v-if="subject.type === 'radio'" type="primary" size="large">单选</van-tag>
<van-tag v-if="subject.score" type="primary" size="large">{{subject.score}}分</van-tag>
</div>
<van-checkbox-group v-model="subject.userSelectOptionIds" :ref="'subject'+subject.id">
<van-cell-group>
<van-cell
v-for="(option,index) in subject.options"
clickable
:key="option.id"
:title="option.text"
@click="cellToggle(subject,option.id)"
>
<template #icon>
<van-checkbox
:name="option.id"
:ref="'option'+option.id"
:disabled="answerRecord.isFinish"
:shape="subject.type==='checkbox' ? 'square' : 'round'"
style="margin-right: 10px"
></van-checkbox>
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
</div>
<div class="button-control" v-if="!answerRecord.isFinish">
<van-button type="info" @click="onSubmit" block :disabled="isEnd">{{isEnd ? '已结束' : '提交'}}
</van-button>
</div>
</div>
</div>
</div>
<script>
const vue = new Vue({
el: "#app",
store,
data() {
return {
list: [],
id: GetQueryString("id"),
H5JumpTo: GetQueryString("H5JumpTo"),
subjects: [],
activity: {},
isFinished: false,
answerRecord: {},
answerRecordId: null,
//历史记录
historyScores: [],
//答题用时
answerTime: 0
}
},
computed: {
isEnd() {
if (this.activity) {
return this.$moment().unix() > this.$moment(this.activity.endTime).unix()
}
return true
},
remainingTime() {
if (this.activity && this.activity.timeLimit > 0) {
return this.activity.timeLimit * 60 - this.answerTime
}
return 0
}
},
methods: {
//获取活动、题目
listSubjects() {
this.$axios.post("/platform/h5/qsv/quiz/subjects", {activityId: this.id}).then((res) => {
if (res.code === 0) {
this.activity = res.data.activity
this.subjects = res.data.subjects
this.answerRecordId = res.data.answerRecordId
// this.checkGroupPermission()
if (!this.H5JumpTo && this.activity.repeatable && res.data.repeatTips) {
// 如果是可重复答题,第二次答题进来弹出提示
vant.Dialog.confirm({
title: '温馨提示',
message: '您已完成本次答题,是否要重新答题?',
confirmButtonText: '重新答题',
cancelButtonText: '查看答题记录',
}).then(() => {
this.getAnswerRecord()
}).catch(() => {
pjaxReplace("/platform/h5/qsv/quiz/result?answerRecordId=" + this.answerRecordId)
// pjaxReplace("/mobile/index")
});
} else {
this.getAnswerRecord()
}
} else {
vant.Dialog.alert({
title: '温馨提示',
message: res.msg,
}).then(() => {
pjaxReplace("/mobile/index")
})
}
})
},
//获取回答记录
getAnswerRecord() {
this.$axios.post("/platform/h5/qsv/quiz/answerRecord", {answerRecordId: this.answerRecordId}).then((res) => {
if (res.code === 0) {
this.answerRecord = res.data
if (this.answerRecord.isFinish || this.$moment().unix() > this.$moment(this.activity.endTime)) {
pjaxReplace("/platform/h5/qsv/quiz/result?answerRecordId=" + this.answerRecordId)
}
this.initAnswer()
this.checkTimer()
}
})
},
//答案回显
initAnswer() {
this.subjects.forEach((subject, subjectIndex) => {
if (subject.type === "radio") {
this.$refs["subject" + subject.id][0].toggleAll(false)
}
if (this.answerRecord.optionIds) {
this.answerRecord.optionIds[subjectIndex].forEach((optionId) => {
this.$refs["option" + optionId][0].toggle()
})
}
})
},
historyBack() {
if (this.timerInterval) {
clearInterval(this.timerInterval)
}
pjaxReplace('platform/h5/qsv')
},
//答题计时器
checkTimer() {
if (this.timerInterval) {
clearInterval(this.timerInterval)
}
//开启计时
const startInterval = () => {
this.timerInterval = setInterval(() => {
this.answerTime++
if (this.activity.timeLimit > 0 && this.answerTime >= this.activity.timeLimit * 60) {
clearInterval(this.timerInterval)
this.$dialog.alert({
title: "提示",
message: "答题时间到,请刷新页面重新答题",
}).then(() => {
window.location.reload()
})
// const loading = this.$toast.loading({
// message: "答题时间到,自动提交中",
// forbidClick: true
// })
// setTimeout(() => {
// this.autoSubmit(loading)
// }, 1500)
}
}, 1000)
}
if (!this.isEnd && !this.answerRecord.isFinish) {
if (this.activity.timeLimit > 0) {
this.$dialog
.alert({
title: "提示",
message: "本次答题时间" + this.activity.timeLimit + "分钟,点击确认开始答题"
})
.then(() => {
startInterval()
})
} else {
startInterval()
}
}
// if (this.activity.timeLimit > 0 && !this.isEnd) {
// if (!this.answerRecord.isFinish) {
// this.$dialog
// .alert({
// title: "提示",
// message: "本次答题时间" + this.activity.timeLimit + "分钟,点击确认开始答题"
// })
// .then(() => {
// this.remainingTime = this.activity.timeLimit * 60
// this.timerInterval = setInterval(() => {
// if (this.remainingTime > 0) {
// this.remainingTime--
// } else {
// clearInterval(this.timerInterval)
// const loading = this.$toast.loading({
// message: "答题时间到,自动提交中",
// forbidClick: true
// })
// this.autoSubmit(loading)
// }
// }, 1000)
// })
// }
// }
},
historyScore() {
this.$axios.post("/platform/h5/qsv/quiz/historyScore", {activityId: this.activityId}).then((res) => {
if (res.code === 0) {
this.historyScores = res.data
}
})
},
//选项点击
cellToggle(subject, optionId) {
if (this.answerRecord.isFinish) {
return
}
if (subject.type === "radio") {
this.$refs["subject" + subject.id][0].toggleAll(false)
}
this.$refs["option" + optionId][0].toggle()
},
//手动提交
onSubmit() {
if (this.isEnd) {
this.$toast("调查已结束")
return
}
//提示那些题没有作答
for (let i = 0; i < this.subjects.length; i++) {
let subject = this.subjects[i]
//["radio", "checkbox"].includes(subject.type) &&
if (!subject.userSelectOptionIds || subject.userSelectOptionIds.length === 0) {
this.$toast.fail("第" + (i + 1) + "题未做答")
return
}
}
if (this.timerInterval) {
clearInterval(this.timerInterval)
}
this.autoSubmit()
},
//自动提交
autoSubmit(loading = null) {
this.$axios.post("/platform/h5/qsv/quiz/submitAnswer", {
answer: JSON.stringify({
activityId: this.id,
answerRecordId: this.answerRecordId,
subjects: this.subjects.map((subject) => {
return {
id: subject.id,
userSelectOptionIds: ["checkbox", "radio"].includes(subject.type) ? subject.userSelectOptionIds : []
}
}),
answerTime: this.answerTime
})
})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
pjaxReplace("/platform/h5/qsv/quiz/result?answerRecordId=" + this.answerRecordId)
}
})
.always(() => {
if (loading) {
loading.clear()
}
})
},
// async checkGroupPermission() {
// const groupId = this.activity.groupId
// if (groupId) {
// const { code, data } = await this.$axios.post("/open/common/checkGroupPermission", { groupId })
// if (code === 0) {
// if (!data) {
// this.$dialog
// .alert({
// title: "提示",
// message: "您没有权限参与"
// })
// .then(() => {
// location.back()
// })
// }
// }
// }
// }
},
created() {
if (this.timerInterval) {
clearInterval(this.timerInterval)
}
if (this.id) {
this.listSubjects()
}
},
style: /*language=CSS*/ `
.container {
padding: 0;
}
::v-deep .container .title {
text-align: center;
padding: 20px;
background: #ffffff;
margin: 0 0 10px 0 !important;
color: var(--color-primary);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.container .card {
padding: 10px;
background: #ffffff;
}
.subject {
background: #fff;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
}
.subject p {
font-size: 16px;
margin: 20px 0 10px 0;
font-weight: bold;
}
.subject .tag {
position: absolute;
top: 0;
}
::v-deep .timer {
text-align: center;
font-size: 18px;
background: #fff;
padding: 10px 0;
}
.submit-button {
width: 100%;
margin-top: 20px;
}
.result {
text-align: center;
font-size: 20px;
margin-top: 20px;
}
.button-control {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
display: flex;
column-gap: 20px;
background: #fff;
}
`
})
</script>
<!--#
}
#-->
@@ -0,0 +1,359 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
<div class="container" :style="{'padding-bottom': answerRecord.isFinish ? 0 : '84px'}">
<van-sticky offset-top="46px">
<h2 class="title">{{ activity.title }}</h2>
<div class="score-form">
<div class="score-form-total">
<div class="score-font-style">{{totalScore}}</div>
<i class="score-underline"></i>
</div>
</div>
</van-sticky>
<div>
<div v-for="(subject, index) in subjects" :key="index" class="subject">
<p>{{index+1}}、{{ subject.title }}</p>
<div class="tag">
<van-tag v-if="subject.type === 'checkbox'" type="primary" size="large">多选</van-tag>
<van-tag v-if="subject.type === 'radio'" type="primary" size="large">单选</van-tag>
<van-tag v-if="subject.score" type="primary" size="large">{{subject.score}}分</van-tag>
</div>
<van-checkbox-group v-model="subject.userSelectOptionIds" :ref="'subject'+subject.id">
<van-cell-group>
<van-cell
v-for="(option,index) in subject.options"
clickable
:key="option.id"
:title="option.text"
>
<template #title>
<span :style="{color : option.isCorrect ? 'green' : ''}">{{option.text}}</span>
</template>
<template #icon>
<van-checkbox
:name="option.id"
:ref="'option'+option.id"
:disabled="answerRecord.isFinish"
style="margin-right: 10px"
></van-checkbox>
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
<div class="answer-container">
<div v-if="answerRecord.extJson?.[subject.id]?.isCorrect" class="correct-answer">
<van-icon name="passed"></van-icon>
回答正确
</div>
<div v-else-if="answerRecord.extJson?.[subject.id]" class="incorrect-answer">
<van-icon name="close"></van-icon>
回答错误
</div>
</div>
</div>
<div class="button-control">
<van-button v-if="canAgainQuestion" type="primary" @click="againQuestion" block>重新答题</van-button>
<van-button type="primary" @click="openHistory" block>查看全部答题记录</van-button>
</div>
</div>
<van-action-sheet v-model="historyShow" title="历史记录" >
<div class="history-list">
<div v-for="item in historyList" class="history-item">
<div class="history-header">
<span>{{ item.date }}</span>
<van-tag v-if="item.isHighestScore" type="danger" class="tag">最高分</van-tag>
<van-tag v-if="item.isLatestScore" type="primary" class="tag">最新得分</van-tag>
</div>
<div class="history-info">答题分数:{{ item.totalScore }}</div>
<div class="history-info">答题用时:{{ item.answerTime | formatSeconds }}</div>
</div>
</div>
</van-action-sheet>
</div>
</div>
<script>
const vue = new Vue({
el: "#app",
data() {
return {
answerRecordId: GetQueryString("answerRecordId"),
answerRecord: {},
subjects: [],
activity: {},
list: [],
historyShow: false,
historyList: [],
canAgainQuestion: false
}
},
computed: {
totalScore() {
if (!this.answerRecord || !this.answerRecord.extJson) {
return 0
}
let totalScore = 0
for (const key in this.answerRecord.extJson) {
if (this.answerRecord.extJson.hasOwnProperty(key)) {
totalScore += this.answerRecord.extJson[key].score || 0
}
}
return totalScore
}
},
filters: {
formatSeconds(seconds) {
if (seconds) {
const minutes = Math.floor(seconds / 60) // 获取总分钟数
const remainingSeconds = seconds % 60 // 获取剩余秒数
// 格式化输出,确保秒数始终为两位数
return minutes + "分钟" + String(remainingSeconds).padStart(2, "0") + "秒"
}
return null
}
},
methods: {
/**
* 重新答题
*/
againQuestion() {
vant.Dialog.confirm({
title: '温馨提示',
message: '确定要重新答题吗?',
}).then(() => {
pjaxReplace('/platform/h5/qsv/quiz?id=' + this.activity.id + "&H5JumpTo=true")
}).catch(() => {
// on cancel
});
},
getAnswerResult() {
this.$axios.post("/platform/h5/qsv/quiz/answerResult", { answerRecordId: this.answerRecordId }).then((res) => {
if (res.code === 0) {
this.subjects = res.data.subjects
this.activity = res.data.activity
this.getAnswerRecord()
this.getHistoryQuestion()
}
})
},
//获取回答记录
getAnswerRecord() {
this.$axios.post("/platform/h5/qsv/quiz/answerRecord", { answerRecordId: this.answerRecordId }).then((res) => {
if (res.code === 0) {
this.answerRecord = res.data
this.initAnswer()
}
})
},
//答案回显
initAnswer() {
this.subjects.forEach((subject, subjectIndex) => {
const answer = this.answerRecord.extJson[subject.id]
if (["radio", "checkbox"].includes(subject.type)) {
this.$nextTick(() => {
this.$refs["subject" + subject.id][0].toggleAll(false)
})
answer?.optionIds.forEach((optionId) => {
this.$nextTick(() => {
this.$refs["option" + optionId][0].toggle()
})
})
} else if (subject.type === "text") {
subject.userFillContent = answer?.text
}
})
},
historyBack() {
pjaxReplace('/platform/h5/qsv')
},
openHistory() {
this.historyShow = true
},
getHistoryQuestion() {
this.$axios.post("/platform/h5/qsv/quiz/historyScore", { activityId: this.activity.id }).then((res) => {
if (res.code === 0) {
this.historyList = res.data
if (this.activity.maxAttempts > res.data.length) {
this.canAgainQuestion = true
}
}
})
}
},
created() {
this.getAnswerResult()
},
style: /*language=CSS*/ `
.container {
padding: 0;
}
::v-deep .container .title {
text-align: center;
padding: 20px;
margin: 0 !important;
background: #ffffff;
color: var(--color-primary);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.container .score {
}
.container .card {
padding: 10px;
background: #ffffff;
}
.subject {
background: #fff;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
}
.subject p {
font-size: 16px;
margin: 20px 0 10px 0;
font-weight: bold;
}
.subject .tag {
position: absolute;
top: 0;
}
::v-deep .score-form {
background: #ffffff;
display: flex;
margin-bottom: 10px;
border-top: 1px solid #f1f1f1;
align-items: center;
}
::v-deep .score-form-total {
flex: 1;
background: #fff;
display: flex;
padding: 10px 20px 10px 0;
flex-direction: column;
align-items: flex-end;
}
::v-deep .score-text-news {
flex: 1;
background: #fff;
}
::v-deep .score-font-style {
font-size: 32px;
color: #ff6a00;
word-break: keep-all;
line-height: 38px;
min-width: 47px;
text-align: center;
}
::v-deep .score-underline {
background: url(//image.wjx.cn/images/newimg/score-form/score-underline@2x.png) no-repeat center;
background-size: 47px 16px;
display: inline-block;
height: 16px;
width: 47px;
margin-top: 7px;
}
.van-checkbox__icon--checked .van-icon {
color: #fff !important;
background-color: var(--color-primary) !important;
border-color: var(--color-primary) !important;
}
.van-checkbox__icon--disabled .van-icon {
background-color: #fff;
}
.answer-container {
padding: 16px;
}
.correct-answer {
color: green;
}
.incorrect-answer {
color: red;
}
.button-control {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
display: flex;
column-gap: 20px;
background: #fff;
}
::v-deep .history-list {
background: #f1f1f1;
padding: 10px;
}
::v-deep .history-item {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin: 20px 10px;
box-shadow:
8px 8px 16px #d9d9d9,
-8px -8px 16px #ffffff;
transition: transform 0.2s ease-in-out;
}
::v-deep .history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
::v-deep .history-header span {
font-size: 14px;
font-weight: 600;
color: #333;
}
::v-deep .history-header .tag {
margin-left: 8px;
color: #fff;
padding: 2px 5px;
}
::v-deep .history-info {
font-size: 14px;
color: #555;
margin-bottom: 4px;
}
`
})
</script>
<!--#
}
#-->
@@ -2,82 +2,129 @@ const PROPOSAL_INFO = {
name: "ProposalInfo",
template: /*language=HTML*/ `
<van-action-sheet v-model="visible" title="查看详情">
<div class="process-title">基本信息</div>
<van-cell-group>
<van-cell title="提案名称">
<div style="font-weight:bold;">
{{ viewData.name }}
</div>
</van-cell>
<van-cell title="提案编号">{{ viewData.code }}</van-cell>
<van-cell title="提案人">{{ viewData.createUserName }}</van-cell>
<van-cell title="提案时间">{{ viewData.createTime }}</van-cell>
<van-cell title="教代会届次">{{ viewData.fullName }}</van-cell>
<van-cell title="提案人单位">{{ viewData.unitName }}</van-cell>
<van-cell :title="viewData.mannerCode!=='W03'?'代表团名称':'委员会名称'">
{{ viewData.mannerCode !== 'W03' ? viewData.delegationName : viewData.committeeName }}
</van-cell>
<van-cell title="提案类别">
{{viewData.typeName}}
</van-cell>
<van-cell title="立案结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="viewData.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="提案摘要">
<div slot="label" class="font-size1 van-color-text-primary break-all" style="white-space: normal;">
<div class="detail-container">
<van-cell-group title="基本信息">
<van-cell title="提案名称">
<div style="font-weight:bold;">
{{ viewData.name }}
</div>
</van-cell>
<van-cell title="提案编号">{{ viewData.code }}</van-cell>
<van-cell title="提案人">{{ viewData.createUserName }}</van-cell>
<van-cell title="提案时间">{{ viewData.createTime }}</van-cell>
<van-cell title="教代会届次">{{ viewData.fullName }}</van-cell>
<van-cell title="提案人单位">{{ viewData.unitName }}</van-cell>
<van-cell :title="viewData.mannerCode!=='W03'?'代表团名称':'委员会名称'">
{{ viewData.mannerCode !== 'W03' ? viewData.delegationName : viewData.committeeName }}
</van-cell>
<van-cell title="提案类别">
{{viewData.typeName}}
</van-cell>
<van-cell title="立案结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="viewData.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="提案摘要" class="direction-column-cell">
<div v-html="viewData.excerpt"></div>
</div>
</van-cell>
<van-cell title="提案内容">
<div slot="label" class="font-size1 van-color-text-primary break-all" style="white-space: normal;">
</van-cell>
<van-cell title="提案内容" class="direction-column-cell">
<div v-html="viewData.brief"></div>
</div>
</van-cell>
<van-cell title="可行性分析">
<div slot="label" class="font-size1 van-color-text-primary break-all">
</van-cell>
<van-cell title="可行性分析" class="direction-column-cell">
<div v-html="viewData.measures"></div>
</div>
</van-cell>
<!-- <van-cell title="附件">
<div slot="label">
{{viewData.files}}
</div>
</van-cell>-->
<!-- <van-cell title="签字">-->
<!-- <div slot="label">-->
<!-- <van-image :src="viewData.signature"-->
<!-- style="width: 100%;height: 150px;border: 1px dashed"></van-image>-->
<!-- </div>-->
<!-- </van-cell>-->
</van-cell-group>
</van-cell>
<!-- <van-cell title="附件">
<div slot="label">
{{viewData.files}}
</div>
</van-cell>-->
<!-- <van-cell title="签字">-->
<!-- <div slot="label">-->
<!-- <van-image :src="viewData.signature"-->
<!-- style="width: 100%;height: 150px;border: 1px dashed"></van-image>-->
<!-- </div>-->
<!-- </van-cell>-->
</van-cell-group>
<div class="process-title">
附议人信息
<template v-for="task in doneTasks">
<van-cell-group :title="task.displayName" v-if="task.ext.isFirstTaskNode">
<van-cell title="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})
</van-cell>
<van-cell title="申请时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</van-cell>
</van-cell-group>
<!--邀请附议人-->
<van-cell-group :title="task.displayName"
v-else-if="task.taskName === '85b7b9bd-d706-48cb-99a1-ef1370fb1819'">
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})
</van-cell>
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
<van-cell title="附议人" v-if="task.taskName === '85b7b9bd-d706-48cb-99a1-ef1370fb1819'" class="direction-column-cell">
<el-table :data="task?.taskFormData?.seconder">
<el-table-column label="姓名" prop="userName"></el-table-column>
<el-table-column label="工号" prop="loginName"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="单位" prop="unitName" show-overflow-tooltip></el-table-column>
<el-table-column label="分工会" prop="unionName" show-overflow-tooltip></el-table-column>
<el-table-column label="代表团" prop="delegationName" show-overflow-tooltip></el-table-column>
</el-table>
<!-- <table>-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th>姓名</th>-->
<!-- <th>工号</th>-->
<!-- <th>性别</th>-->
<!-- <th>单位</th>-->
<!-- <th>分工会</th>-->
<!-- <th>代表团</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr v-for="item in task?.taskFormData?.seconder">-->
<!-- <td>{{item.userName}}</td>-->
<!-- <td>{{item.loginName}}</td>-->
<!-- <td>{{item.sex}}</td>-->
<!-- <td>{{item.unitName}}</td>-->
<!-- <td>{{item.unionName}}</td>-->
<!-- <td>{{item.delegationName}}</td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
</van-cell>
</van-cell-group>
<!--提案委员会立案-->
<van-cell-group :title="task.displayName"
v-else-if="task.taskName === '9846ab38-40c5-4093-bafc-a9b3b443338b'">
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})
</van-cell>
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
<van-cell title="立案结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="task.ext.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="主办单位">{{ task.ext.tf_hostUnitName }}</van-cell>
<van-cell title="协办单位">{{ task?.ext?.tf_helpUnitNames.join('、') }}</van-cell>
</van-cell-group>
<van-cell-group :title="task.displayName" v-else>
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})
</van-cell>
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="task.ext.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="办理意见" v-if="!task.ext.isFirstTaskNode" class="direction-column-cell">
<div v-html="task.taskFormData.tf_opinion"></div>
</van-cell>
</van-cell-group>
</template>
</div>
<van-cell-group>
<van-cell title="提案名称" v-for="item in viewData.seconders">
<template slot="title">
<span class="font-size1">{{item.createVariable.userName}}</span>
<span class="margin-left10 font-size1 van-color-text-regular">/ {{item.createVariable.loginName}}</span>
</template>
<template slot="label">
<span class="font-size0 van-color-text-assist">{{item.createVariable.unitName}}</span>
</template>
<template>
<span class="font-size2 van-color-text-placeholder bold">
<span v-if="item.extVariable && item.extVariable.bpmTaskApprovalType">
<van-tag size="mini" type="success"
v-if="item.extVariable.bpmTaskApprovalType==='PASS'">同意</van-tag>
<van-tag size="mini" type="danger" v-if="item.extVariable.bpmTaskApprovalType==='REJECT'">拒绝</van-tag>
</span>
<span v-else>
<van-tag size="mini">未附议</van-tag>
</span>
</span>
</template>
</van-cell>
</van-cell-group>
<slot></slot>
</van-action-sheet>
@@ -86,7 +133,6 @@ const PROPOSAL_INFO = {
data() {
return {
visible:false,
viewData: {},
doneTasks: [],
row: null,
@@ -131,4 +177,4 @@ const PROPOSAL_INFO = {
})
},
}
}
};
@@ -161,6 +161,20 @@ layout("/layouts/platform_h5.html"){
}
},
onRevoke(row){
this.$dialog.confirm({
title: "提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
@@ -0,0 +1,224 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="反馈评价" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.name"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入提案名称搜索"
@search="doSearch"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
<van-tab title="已评价" name="1"></van-tab>
<van-tab title="未评价" name="0"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/proposal/feedbackEvaluation/pageData" :page_form.sync="pageForm" @ready="onReady"
ref="tableListRef"
title="name">
<template v-slot="{index,row}">
<table-column label="提案编号">{{row.code}}</table-column>
<table-column label="提案类别">{{row.typeName}}</table-column>
<table-column label="提案人">{{row.createUserName}}</table-column>
<table-column label="代表团">{{row.delegationName}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-edit"></i>
<span>评价</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-reply"></i>
<span>撤回</span>
</div>
</template>
</table-list>
<proposal-info ref="proposalInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_feedback_name"
name="tf_feedback_name"
label="满意度"
placeholder="请选择满意度"
:rules="[{ required: true, message: '请选择满意度' }]"
required
is-link
@click="showFeedBackPicker = true"
></van-field>
<van-popup v-model="showFeedBackPicker" position="bottom">
<van-picker
show-toolbar
:columns="dict.type.PROPOSAL_FEEDBACK.map(item => ({text: item.label, value: item.code}))"
@confirm="onFeedBackConfirm"
@cancel="showFeedBackPicker = false"
></van-picker>
</van-popup>
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="$refs.proposalInfoRef.onClose()">取消</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">提交</van-button>
</div>
</div>
</proposal-info>
</div>
<script>
<!--#include("../../common/info.js"){}#-->
const vue = new Vue({
el: "#app",
dicts: ["PROPOSAL_FEEDBACK"],
store,
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
name: null,
sessionId: null,
approvalText: "0",
approval: false
},
sessionOptions: [],
formData: {},
showApprovalForm: false,
showFeedBackPicker: false
}
},
components: {
"proposal-info": PROPOSAL_INFO
},
methods: {
onReady() {
this.listSession()
},
listSession() {
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
if (res.code === 0) {
this.sessionOptions = [
{
text: "全部届次",
value: null
}
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
if (this.sessionOptions.length > 0) {
this.pageForm.sessionId = this.sessionOptions[0].value
this.doSearch()
}
}
})
},
onView(row) {
this.showApprovalForm = false
this.$refs.proposalInfoRef.onOpen(row)
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.proposalInfoRef.onOpen(row)
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName
}
},
onFeedBackConfirm({value, text}) {
this.formData.tf_feedback_name = text
this.formData.tf_feedback = value
this.showFeedBackPicker = false
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate();
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(async () => {
const formData = {
...this.formData,
submitType: val
}
// 不满意
if (this.formData.tf_feedback === 'DISSATISFIED') {
const caseInfo = await this.getCaseInfo(this.formData.instanceId)
formData.tf_hostUnitId = caseInfo.tf_hostUnitId
}
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify(formData)
}).then((res) => {
if (res.code === 0) {
this.$refs.proposalInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
})
} catch (error) {
}
},
onRevoke(row) {
this.$dialog.confirm({
title: "提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,189 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="分管校领导审批" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.name"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入提案名称搜索"
@search="doSearch"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/proposal/schoolLeaderApproval/pageData" :page_form.sync="pageForm" @ready="onReady"
ref="tableListRef"
title="name">
<template v-slot="{index,row}">
<table-column label="提案编号">{{row.code}}</table-column>
<table-column label="提案类别">{{row.typeName}}</table-column>
<table-column label="提案人">{{row.createUserName}}</table-column>
<table-column label="代表团">{{row.delegationName}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-edit"></i>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-reply"></i>
<span>撤回</span>
</div>
</template>
</table-list>
<proposal-info ref="proposalInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="handleTaskAction(4)">退回答复</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意答复</van-button>
</div>
</div>
</proposal-info>
</div>
<script>
<!--#include("../../common/info.js"){}#-->
const vue = new Vue({
el: "#app",
store,
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
name: null,
sessionId: null,
approvalText: "0",
approval: false
},
sessionOptions: [],
formData: {},
showApprovalForm: false
}
},
components: {
"proposal-info": PROPOSAL_INFO
},
methods: {
onReady() {
this.listSession()
},
listSession() {
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
if (res.code === 0) {
this.sessionOptions = [
{
text: "全部届次",
value: null
}
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
if (this.sessionOptions.length > 0) {
this.pageForm.sessionId = this.sessionOptions[0].value
this.doSearch()
}
}
})
},
onView(row) {
this.showApprovalForm = false
this.$refs.proposalInfoRef.onOpen(row)
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.proposalInfoRef.onOpen(row)
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName
}
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate();
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.proposalInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
})
} catch (error) {
}
},
onRevoke(row){
this.$dialog.confirm({
title: "提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
}
})
</script>
<!--#
}
#-->
@@ -242,6 +242,20 @@ layout("/layouts/platform_h5.html"){
}
},
onRevoke(row){
this.$dialog.confirm({
title: "提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
@@ -1,878 +0,0 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37A6BD;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
#app {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
}
/* 筛选区域样式 */
.filter-section {
background-color: var(--card-bg);
padding: 12px 16px;
margin-bottom: 10px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.filter-section.collapsed {
max-height: 84px;
}
.filter-section.expanded {
max-height: 500px;
}
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.filter-title {
font-size: 15px;
font-weight: 500;
color: var(--text-color);
}
.filter-toggle {
color: var(--text-secondary);
display: flex;
align-items: center;
}
.filter-toggle .van-icon {
transition: transform 0.3s;
margin-left: 4px;
}
.filter-toggle .van-icon.rotate {
transform: rotate(180deg);
}
.filter-content {
transition: opacity 0.3s;
}
.filter-content.hidden {
opacity: 0;
height: 0;
overflow: hidden;
}
.filter-row {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.filter-row:last-child {
margin-bottom: 0;
}
.filter-label {
font-size: 13px;
color: var(--text-secondary);
margin-right: 10px;
min-width: 60px;
padding-top: 4px;
}
.filter-options {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.filter-tag {
padding: 4px 10px;
border-radius: 16px;
font-size: 12px;
margin-right: 8px;
margin-bottom: 6px;
background-color: var(--bg-color);
color: var(--text-secondary);
}
.filter-tag.active {
background-color: var(--primary-light);
color: var(--primary-color);
font-weight: 500;
}
.filter-search {
padding: 8px 0;
}
.filter-search .van-search {
padding: 0;
}
.filter-search .van-search__content {
background-color: var(--bg-color);
}
.suggestion-list {
padding: 16px;
background-color: var(--bg-color);
flex: 1;
display: flex;
flex-direction: column;
}
.van-pull-refresh, .van-list {
flex: 1;
display: flex;
flex-direction: column;
}
.suggestion-card {
background-color: var(--card-bg);
border-radius: 12px;
margin-bottom: 16px;
padding: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.02);
}
.suggestion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.suggestion-title {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.suggestion-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
margin-left: 10px;
font-weight: 500;
}
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-processing {
background-color: #fff7e6;
color: #fa8c16;
}
.status-completed {
background-color: #f6ffed;
color: #52c41a;
}
.suggestion-content {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
overflow: hidden;
background-color: var(--bg-color);
padding: 10px;
border-radius: 8px;
word-break: break-all;
white-space: pre-line;
max-height: 3.2em;
text-overflow: ellipsis;
display: block;
}
.suggestion-submitter {
display: flex;
align-items: center;
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.suggestion-submitter .van-icon {
margin-right: 5px;
font-size: 14px;
}
.suggestion-unit {
margin-left: 15px;
}
.suggestion-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: var(--text-light);
border-top: 1px solid #f5f5f5;
padding-top: 12px;
}
.suggestion-time {
display: flex;
align-items: center;
}
.suggestion-time .van-icon {
font-size: 14px;
margin-right: 4px;
}
.suggestion-action {
color: var(--primary-color);
display: flex;
align-items: center;
font-weight: 500;
}
.suggestion-action .van-icon {
font-size: 14px;
margin-left: 2px;
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 16px;
flex: 1;
}
.empty-icon {
font-size: 64px;
color: #ddd;
margin-bottom: 16px;
text-align: center;
}
.empty-text {
font-size: 15px;
color: var(--text-light);
text-align: center;
margin-bottom: 20px;
}
/* 详情弹窗样式 */
.detail-popup {
padding: 24px;
max-height: 80vh;
overflow-y: auto;
}
.detail-header {
margin-bottom: 20px;
}
.detail-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-color);
}
.detail-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
font-size: 14px;
}
.detail-section {
margin-bottom: 24px;
}
.detail-label {
color: var(--text-secondary);
margin-bottom: 8px;
font-weight: 500;
font-size: 15px;
}
.detail-content {
color: var(--text-color);
line-height: 1.8;
font-size: 15px;
white-space: pre-wrap;
word-break: break-word;
}
.submitter-info {
background-color: var(--bg-color);
border-radius: 8px;
padding: 12px 15px;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8px;
line-height: 1.6;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-label {
color: var(--text-secondary);
width: 80px;
font-size: 14px;
}
.info-value {
color: var(--text-color);
flex: 1;
font-size: 14px;
}
.detail-reply {
background-color: #f9f9f9;
padding: 16px;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
.detail-attachments {
display: flex;
flex-wrap: wrap;
}
.attachment-item {
width: 90px;
height: 90px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.attachment-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.reply-form {
margin-top: 16px;
}
.reply-textarea {
box-sizing: border-box;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
width: 100%;
height: 100px;
font-size: 14px;
background-color: var(--card-bg);
margin-bottom: 16px;
}
.reply-attachments {
margin-bottom: 16px;
}
.reply-actions {
display: flex;
justify-content: space-between;
}
.no-reply {
padding: 20px 0;
text-align: center;
background-color: var(--bg-color);
border-radius: 8px;
}
.no-reply-icon {
font-size: 36px;
color: #ccc;
margin-bottom: 8px;
}
.no-reply-text {
font-size: 14px;
color: var(--text-light);
}
.van-button--primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* 下拉刷新和上拉加载样式 */
.van-pull-refresh__track {
flex: 1;
}
.van-list {
min-height: 100%;
}
/* 统计面板 */
.stats-panel {
background-color: var(--card-bg);
padding: 16px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
}
.stats-item {
flex: 1;
text-align: center;
}
.stats-value {
font-size: 20px;
font-weight: bold;
color: var(--primary-color);
}
.stats-label {
font-size: 12px;
color: var(--text-secondary);
margin-top: 4px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="意见箱管理" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
<!-- 统计面板 -->
<div class="stats-panel">
<div class="stats-item">
<div class="stats-value">{{ stats.total }}</div>
<div class="stats-label">总意见数</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.pending }}</div>
<div class="stats-label">待回复</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.replied }}</div>
<div class="stats-label">已回复</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.today }}</div>
<div class="stats-label">今日新增</div>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section" :class="filters.isCollapsed ? 'collapsed' : 'expanded'">
<div class="filter-header">
<div class="filter-title">筛选条件</div>
<div class="filter-toggle" @click="toggleFilterCollapse">
<span>{{ filters.isCollapsed ? '展开' : '收起' }}</span>
<van-icon :name="filters.isCollapsed ? 'arrow-down' : 'arrow-up'"
:class="{ rotate: !filters.isCollapsed }"></van-icon>
</div>
</div>
<div class="filter-search">
<van-search v-model="filters.keyword" placeholder="搜索意见标题、内容或提交人"
@search="onSearch"></van-search>
</div>
<div class="filter-content" :class="{ hidden: filters.isCollapsed }">
<div class="filter-row">
<div class="filter-label">状态</div>
<div class="filter-options">
<div class="filter-tag" :class="{ active: filters.status === '' }" @click="setFilter('status', '')">
全部
</div>
<div class="filter-tag" :class="{ active: filters.status === '0' }"
@click="setFilter('status', '0')">
待回复
</div>
<div class="filter-tag" :class="{ active: filters.status === '1' }"
@click="setFilter('status', '1')">
已回复
</div>
</div>
</div>
<div class="filter-row">
<div class="filter-label">时间</div>
<div class="filter-options">
<div class="filter-tag" :class="{ active: filters.time === '' }" @click="setFilter('time', '')">全部
</div>
<div class="filter-tag" :class="{ active: filters.time === 'today' }"
@click="setFilter('time', 'today')">今日
</div>
<div class="filter-tag" :class="{ active: filters.time === 'week' }"
@click="setFilter('time', 'week')">
本周
</div>
<div class="filter-tag" :class="{ active: filters.time === 'month' }"
@click="setFilter('time', 'month')">本月
</div>
</div>
</div>
</div>
</div>
<!-- 内容区域 -->
<div class="suggestion-list">
<!-- 下拉刷新和上拉加载更多 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadMore"
>
<!-- 空状态 -->
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
<van-icon name="comment-circle-o" class="empty-icon"></van-icon>
<div class="empty-text">暂无符合条件的意见</div>
</div>
<!-- 意见列表 -->
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id"
@click="showDetail(item)">
<div class="suggestion-header">
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
<div class="suggestion-status" :class="getStatusClass(item.isReply)">
{{ getStatusText(item.isReply) }}
</div>
</div>
<div class="suggestion-submitter">
<van-icon name="contact"/>
<span>{{ item.submitterName }}</span>
<span class="suggestion-unit">{{ item.submitterUnitName }}</span>
</div>
<div class="suggestion-content">{{ item.content }}</div>
<div class="suggestion-footer">
<div class="suggestion-time">
<van-icon name="clock-o"/>
<span>{{ formatDate(item.submitTime) }}</span>
</div>
<div class="suggestion-action">
{{ item.reply ? '查看详情' : '去回复' }}
<van-icon name="arrow"/>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 详情弹出层 -->
<van-popup v-model="showDetailPopup" round closeable position="bottom">
<div class="detail-popup" v-if="currentSuggestion">
<div class="detail-header">
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
<div class="detail-meta">
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
</div>
</div>
<div class="detail-section">
<div class="detail-label">提交人信息</div>
<div class="submitter-info">
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">工号:</span>
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">单位:</span>
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系方式:</span>
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-label">意见内容</div>
<div class="detail-content">{{ currentSuggestion.content }}</div>
</div>
<div class="detail-section"
v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
<div class="detail-label">附件</div>
<div class="detail-attachments">
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.attachments" :key="idx"
@click.stop="previewImage(file.url, idx)">
<img :src="file.url" class="attachment-image">
</div>
</div>
</div>
<div class="detail-section" v-if="currentSuggestion.reply">
<div class="detail-label">已回复内容</div>
<div class="detail-reply detail-content">{{ currentSuggestion.reply }}</div>
<div class="detail-meta" style="margin-top: 10px;">
回复时间:{{ formatDate(currentSuggestion.replyTime) }}
</div>
<div v-if="currentSuggestion.replyAttachments && currentSuggestion.replyAttachments.length > 0"
style="margin-top: 12px;">
<div class="detail-label">回复附件</div>
<div class="detail-attachments">
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.replyAttachments"
:key="idx" @click.stop="previewImage(file.url, idx, 'reply')">
<img :src="file.url" class="attachment-image">
</div>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-label">{{ currentSuggestion.isReply ? '修改回复' : '回复意见' }}</div>
<div class="reply-form">
<textarea class="reply-textarea" v-model="currentSuggestion.replyContent"
placeholder="请输入回复内容..."></textarea>
<!-- <div class="reply-attachments">-->
<!-- <vant-file-upload :files.sync="formData.replyAttachments" :max="15"></vant-file-upload>-->
<!-- </div>-->
<div class="reply-actions">
<van-button style="border-radius: 10px" block type="info" :color="themeColor"
@click="submitReply">提交回复
</van-button>
</div>
</div>
</div>
</div>
</van-popup>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
refreshing: false,
loading: false,
finished: false,
showDetailPopup: false,
currentSuggestion: null,
pageNumber: 1,
pageSize: 10,
suggestions: [],
replyContent: '',
replyAttachments: [],
filters: {
status: '', // 状态筛选
time: '', // 时间筛选
keyword: '', // 关键词搜索
isCollapsed: true // 筛选区域是否折叠
},
stats: {
total: 0,
pending: 0,
replied: 0,
today: 0
},
formData: {}
}
},
created() {
this.loadStats();
this.loadData();
},
methods: {
// 加载统计数据
loadStats() {
$.post('/platform/suggestionBox/admin/getStats').done((res) => {
if (res.code === 0 && res.data) {
this.stats = {
total: res.data.total || 0,
pending: res.data.pending || 0,
replied: res.data.replied || 0,
today: res.data.today || 0
};
}
}).fail(() => {
this.$toast.fail('统计数据加载失败');
});
},
// 加载意见数据
loadData() {
this.loading = true;
$.post('/platform/suggestionBox/admin/pageData', {
pageNumber: this.pageNumber,
pageSize: this.pageSize,
status: this.filters.status,
timeRange: this.filters.time,
keyword: this.filters.keyword
}).done((res) => {
if (res.code === 0 && res.data) {
if (this.pageNumber === 1) {
this.suggestions = res.data.list || [];
} else {
this.suggestions = this.suggestions.concat(res.data.list || []);
}
this.finished = !res.data.list || res.data.list.length < this.pageSize;
} else {
this.finished = true;
}
this.loading = false;
this.refreshing = false;
}).fail(() => {
this.loading = false;
this.refreshing = false;
this.finished = true;
});
},
// 下拉刷新
onRefresh() {
this.pageNumber = 1;
this.finished = false;
this.loadStats();
this.loadData();
},
// 上拉加载更多
loadMore() {
this.pageNumber++;
this.loadData();
},
// 设置筛选条件
setFilter(type, value) {
this.filters[type] = value;
this.pageNumber = 1;
this.finished = false;
this.loadData();
},
// 搜索
onSearch() {
this.pageNumber = 1;
this.finished = false;
this.loadData();
},
// 查看详情
showDetail(suggestion) {
if (suggestion.attachments && typeof suggestion.attachments === 'string') {
try{
suggestion.attachments = JSON.parse(suggestion.attachments);
}catch (e){
suggestion.attachments = [];
}
}
console.log(suggestion)
this.currentSuggestion = suggestion;
this.showDetailPopup = true;
},
// 提交回复
submitReply() {
if (!this.currentSuggestion.replyContent || !this.currentSuggestion.replyContent.trim()) {
this.$toast('请输入回复内容');
return;
}
this.$dialog.confirm({
title: '确认提交',
message: '确定提交此回复内容吗?'
}).then(() => {
// 提交回复
$.post('/platform/suggestionBox/admin/reply', {
reply: JSON.stringify(this.currentSuggestion)
}).done((res) => {
if (res.code === 0) {
this.$toast.success('回复成功');
this.showDetailPopup = false;
this.loadStats();
this.pageData();
} else {
this.$toast.fail(res.msg || '回复失败');
}
}).fail(() => {
this.$toast.fail('网络错误,请重试');
});
});
},
// 获取状态class
getStatusClass(isReply) {
if (!isReply || isReply === 0) return 'status-pending';
if (isReply === 1) return 'status-processing';
return 'status-completed';
},
// 获取状态文本
getStatusText(isReply) {
if (!isReply || isReply === 0) return '待回复';
if (isReply === 1) return '已回复';
return '已处理';
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
},
// 预览图片
previewImage(url, index, type = 'submission') {
if (!url) return;
// 创建图片查看器
const urls = type === 'reply'
? this.currentSuggestion.replyAttachments.map(file => file.url)
: this.currentSuggestion.attachments.map(file => file.url);
this.$imagePreview({
images: urls,
startPosition: index
});
},
// 切换筛选区域折叠状态
toggleFilterCollapse() {
this.filters.isCollapsed = !this.filters.isCollapsed;
}
}
});
</script>
<!--#}#-->
@@ -1,538 +0,0 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="意见箱" fixed placeholder></van-nav-bar>
<!-- 顶部Banner区域 -->
<div class="banner-section">
<div class="banner-content">
<div class="banner-text">
<h2>我们重视您的意见</h2>
<p>每一条建议都将认真对待</p>
</div>
<div class="banner-image">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTI1LjYgMTAyLjdsLTE5LTE2LjhtLTQuNy0yMy43bDExLjUtNi44TTU3LjQgMTZsMTEuNSAxMS43TTMxLjIgNTMuOGwyMy42IDcuOSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtvcGFjaXR5Oi40Ii8+PHBhdGggZD0iTTY0LjEgMzEuN0w0NyA0OS45Yy0zIDMuMi0zLjEgOC4xLS4xIDExLjJsMjQuNCAyNWMzIDMuMSA3LjkgMy4yIDExIC4xTDk5IDY5YzMtMy4yIDMuMS04LjEuMS0xMS4yTDc0LjcgMzIuOGMtMyAzLjEtNy45IDMuMS0xMC42LTEuMXoiIHN0eWxlPSJmaWxsOiNmZmY7c3Ryb2tlOiNmZmY7c3Ryb2tlLW1pdGVybGltaXQ6MTAiLz48cGF0aCBkPSJNMTA4LjkgOTUuN2wtMTUuOC0xMi0xMi4yIDEzIDE0LjQgMTMuN2MuMyAyLjUgMi4zIDQuNSA0LjggNC41aDEzLjdjMi43IDAgNC45LTIuMiA0LjktNC45VjkzLjVjMC0yLjgtMi4xLTUtNC45LTV2OC40cy4xLTEuMi00LjkgMi44LjEtMyAuMS0zeiIgc3R5bGU9ImZpbGw6I2ZmZjtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMCIvPjwvc3ZnPg==" alt="Feedback">
</div>
</div>
</div>
<!-- 功能卡片区域 -->
<div class="cards-container">
<!-- 提交意见 -->
<div class="feature-card" @click="goToSubmitPage">
<div class="card-icon submit-icon">
<van-icon name="edit" />
</div>
<div class="card-info">
<h3>提交意见</h3>
<p>分享您的想法和建议</p>
</div>
<div class="card-arrow">
<van-icon name="arrow" />
</div>
</div>
<!-- 我的意见 -->
<div class="feature-card" @click="goToMyOpinionsPage">
<div class="card-icon my-icon">
<van-icon name="records" />
</div>
<div class="card-info">
<h3>我的意见</h3>
<p>查看您提交的所有意见</p>
</div>
<div class="card-arrow">
<van-icon name="arrow" />
</div>
</div>
<!-- 意见管理(管理员) -->
<div class="feature-card" v-if="isAdmin" @click="goToAllOpinionsPage">
<div class="card-icon admin-icon">
<van-icon name="manager" />
</div>
<div class="card-info">
<h3>意见管理</h3>
<p>管理所有用户提交的意见</p>
</div>
<div class="card-arrow">
<van-icon name="arrow" />
</div>
</div>
</div>
<!-- 使用指南 -->
<div class="guide-container" v-if="!isAdmin">
<div class="guide-header">
<h3>使用指南</h3>
</div>
<div class="guide-steps">
<div class="guide-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>提交意见</h4>
<p>点击"提交意见"按钮,填写您的意见和建议</p>
</div>
<div class="step-icon">
<van-icon name="edit" />
</div>
</div>
<div class="step-divider"></div>
<div class="guide-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>等待处理</h4>
<p>我们将在3个工作日内处理您的意见</p>
</div>
<div class="step-icon">
<van-icon name="underway-o" />
</div>
</div>
<div class="step-divider"></div>
<div class="guide-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>查看回复</h4>
<p>在"我的意见"中查看官方回复</p>
</div>
<div class="step-icon">
<van-icon name="comment-o" />
</div>
</div>
</div>
</div>
<!-- 常见问题 -->
<div class="faq-container" v-if="!isAdmin">
<div class="faq-header">
<h3>常见问题</h3>
<span class="faq-subtitle">解答您的疑惑</span>
</div>
<div class="faq-list">
<div class="faq-item" :class="{'faq-active': activeFaq === 1}" @click="toggleFaq(1)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o" /></span>
<span>如何提交带附件的意见?</span>
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
</div>
<div class="faq-answer" v-show="activeFaq === 1">
<p>在提交意见表单中,您可以上传最多3个文件作为附件,支持图片格式。点击附件上传区域,选择您要上传的文件即可。</p>
</div>
</div>
<div class="faq-item" :class="{'faq-active': activeFaq === 2}" @click="toggleFaq(2)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o" /></span>
<span>意见提交后多久能收到回复?</span>
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
</div>
<div class="faq-answer" v-show="activeFaq === 2">
<p>我们会在3个工作日内处理您的意见,紧急问题会优先处理。您可以随时在"我的意见"中查看处理进度。</p>
</div>
</div>
<div class="faq-item" :class="{'faq-active': activeFaq === 3}" @click="toggleFaq(3)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o" /></span>
<span>我可以修改已提交的意见吗?</span>
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
</div>
<div class="faq-answer" v-show="activeFaq === 3">
<p>提交后的意见暂不支持修改,如有补充,请重新提交并说明这是对之前意见的补充。我们会将相关意见关联处理。</p>
</div>
</div>
</div>
</div>
</div>
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37A6BD;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f6f6f6;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.5;
}
.page-container {
padding-bottom: 50px;
}
/* 顶部Banner */
.banner-section {
padding: 0;
height: 180px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
position: relative;
overflow: visible;
}
.banner-curve {
display: none;
}
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 20px 0;
height: 100%;
}
.banner-text {
color: white;
z-index: 2;
}
.banner-text h2 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.banner-text p {
font-size: 14px;
opacity: 0.9;
}
.banner-image {
width: 100px;
height: 100px;
z-index: 2;
}
.banner-image img {
width: 100%;
height: 100%;
object-fit: contain;
}
/* 功能卡片 */
.cards-container {
padding: 20px 16px 16px;
margin-top: -20px;
position: relative;
z-index: 10;
background-color: var(--bg-color);
border-radius: 20px 20px 0 0;
}
.feature-card {
display: flex;
align-items: center;
background: var(--card-bg);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.feature-card:active {
transform: scale(0.98);
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 16px;
}
.card-icon .van-icon {
font-size: 24px;
color: white;
}
.submit-icon {
background: linear-gradient(135deg, #1989fa 0%, #39b9f9 100%);
}
.my-icon {
background: linear-gradient(135deg, #07c160 0%, #10d878 100%);
}
.admin-icon {
background: linear-gradient(135deg, #ff6b6b 0%, #ffaa7f 100%);
}
.card-info {
flex: 1;
}
.card-info h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
color: var(--text-color);
}
.card-info p {
font-size: 13px;
color: var(--text-light);
margin: 0;
}
.card-arrow {
color: #ccc;
}
/* 使用指南 */
.guide-container {
padding: 0 16px 16px;
}
.guide-header {
margin-bottom: 16px;
}
.guide-header h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
}
.guide-steps {
background: var(--card-bg);
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.guide-step {
display: flex;
align-items: center;
position: relative;
}
.step-number {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-content h4 {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
color: var(--text-color);
}
.step-content p {
font-size: 13px;
color: var(--text-secondary);
margin: 0;
}
.step-icon {
margin-left: 12px;
color: var(--primary-color);
}
.step-divider {
height: 24px;
width: 1px;
background: #e8e8e8;
margin: 8px 0 8px 15px;
}
/* 常见问题 */
.faq-container {
padding: 0 16px 16px;
}
.faq-header {
margin-bottom: 16px;
display: flex;
align-items: baseline;
}
.faq-header h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
margin-right: 8px;
}
.faq-subtitle {
font-size: 12px;
color: var(--text-light);
}
.faq-list {
background: var(--card-bg);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.faq-item {
border-bottom: 1px solid var(--border-color);
}
.faq-item:last-child {
border-bottom: none;
}
.faq-question {
display: flex;
align-items: center;
padding: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.faq-active .faq-question {
background-color: var(--primary-light);
}
.faq-icon {
color: var(--primary-color);
margin-right: 12px;
}
.faq-arrow {
margin-left: auto;
color: var(--text-light);
transition: transform 0.3s ease;
}
.faq-active .faq-arrow .van-icon {
transform: rotate(180deg);
}
.faq-answer {
padding: 0 16px 16px 44px;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.6;
border-top: 1px dashed var(--border-color);
background-color: rgba(0, 0, 0, 0.02);
}
/* 弹窗样式 */
.popup-title {
text-align: center;
font-size: 16px;
font-weight: 500;
padding: 16px 0;
border-bottom: 1px solid #ebedf0;
}
.popup-content {
padding: 16px;
max-height: calc(100% - 60px);
overflow-y: auto;
}
.suggestion-content {
font-size: 14px;
line-height: 1.5;
}
.suggestion-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 6px;
color: var(--text-color);
}
.suggestion-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
}
.attachment-list {
display: flex;
flex-wrap: wrap;
}
</style>
<script>
new Vue({
el: '#app',
data: function() {
return {
isAdmin: true, // 控制是否为管理员
allSuggestions: [],
user: 'user1',
activeFaq: null,
totalSuggestions: 0,
respondedPercent: 0,
recentSuggestions: []
}
},
computed: {
userSuggestions: function() {
return this.allSuggestions.filter(function(item) {
return item.user === this.user;
}.bind(this));
}
},
methods: {
goToSubmitPage: function() {
window.location.href = '/platform/suggestionBox/h5/write';
},
goToMyOpinionsPage: function() {
window.location.href = '/platform/suggestionBox/h5/mine';
},
goToAllOpinionsPage: function() {
window.location.href = '/platform/suggestionBox/admin/h5';
},
toggleFaq: function(id) {
this.activeFaq = this.activeFaq === id ? null : id;
},
loadStats: function() {
// 模拟加载统计数据
this.totalSuggestions = 25;
this.respondedPercent = 80;
this.recentSuggestions = [1, 2, 3];
}
},
created: function() {
// 初始化数据
if (this.isAdmin) {
this.loadStats();
}
}
});
</script>
<!--#
}
#-->
@@ -0,0 +1,91 @@
const SUGGESTION_BOX_INFO = {
template: /*language=HTML*/ `
<van-action-sheet v-model="visible" title="查看详情">
<div class="detail-container">
<van-cell-group title="基本信息">
<van-cell title="姓名">{{viewData.userName}}</van-cell>
<van-cell title="工号">{{viewData.loginName}}</van-cell>
<van-cell title="单位">{{viewData.unitName}}</van-cell>
<van-cell title="工会">{{viewData.unionName}}</van-cell>
<van-cell title="标题">{{viewData.title}}</van-cell>
<van-cell title="意见建议内容" class="direction-column-cell">{{viewData.content}}</van-cell>
</van-cell-group>
<template v-for="task in doneTasks">
<van-cell-group :title="task.displayName" v-if="task.ext.isFirstTaskNode">
<van-cell title="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})
</van-cell>
<van-cell title="申请时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</van-cell>
</van-cell-group>
<van-cell-group :title="task.displayName" v-else>
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})
</van-cell>
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="task.ext.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="办理意见" v-if="!task.ext.isFirstTaskNode" class="direction-column-cell">
<div v-html="task.taskFormData.tf_opinion"></div>
</van-cell>
</van-cell-group>
</template>
</div>
<slot></slot>
</van-action-sheet>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data(){
return{
visible: false,
viewData: {},
doneTasks: [],
row: null,
}
},
methods: {
// 打开
onOpen(row) {
this.row = row
this.visible = true
this.getInfo()
this.getDoneTasks()
},
// 关闭
onClose(){
this.visible = false
},
// 获取申请信息
getInfo() {
this.$axios.post('/platform/suggestionBox/common/info', {id: this.row.id}).then((res) => {
if (res.code === 0) {
this.viewData = res.data || {}
}
})
},
// 查看提案
openView(id) {
this.$nextTick(() => {
this.$refs.infoDialogRef.onOpen(id)
})
},
// 获取已办任务审批记录
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
}
})
},
}
}
@@ -1,542 +0,0 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37a6bd;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
#app {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
}
/* 页面样式 */
.suggestion-list {
padding: 16px;
background-color: var(--bg-color);
flex: 1;
display: flex;
flex-direction: column;
}
.van-pull-refresh,
.van-list {
flex: 1;
display: flex;
flex-direction: column;
}
.suggestion-card {
background-color: var(--card-bg);
border-radius: 12px;
margin-bottom: 16px;
padding: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
transition: all 0.2s ease;
border: 1px solid rgba(0, 0, 0, 0.02);
}
.suggestion-card:active {
transform: scale(0.98);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.suggestion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.suggestion-title {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.suggestion-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
margin-left: 10px;
font-weight: 500;
}
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-processing {
background-color: #fff7e6;
color: #fa8c16;
}
.status-completed {
background-color: #f6ffed;
color: #52c41a;
}
.suggestion-content {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
overflow: hidden;
background-color: var(--bg-color);
padding: 10px;
border-radius: 8px;
word-break: break-all;
white-space: pre-line;
max-height: 3.2em;
text-overflow: ellipsis;
display: block;
}
.suggestion-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: var(--text-light);
border-top: 1px solid #f5f5f5;
padding-top: 12px;
}
.suggestion-time {
display: flex;
align-items: center;
}
.suggestion-time .van-icon {
font-size: 14px;
margin-right: 4px;
}
.suggestion-action {
color: var(--primary-color);
display: flex;
align-items: center;
font-weight: 500;
}
.suggestion-action .van-icon {
font-size: 14px;
margin-left: 2px;
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 16px;
flex: 1;
}
.empty-icon {
font-size: 64px;
color: #ddd;
margin-bottom: 16px;
}
.empty-text {
font-size: 15px;
color: var(--text-light);
text-align: center;
margin-bottom: 20px;
}
/* 详情弹窗样式 */
.detail-popup {
padding: 24px;
max-height: 80vh;
overflow-y: auto;
}
.detail-header {
margin-bottom: 20px;
}
.detail-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-color);
}
.detail-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
font-size: 14px;
}
.detail-section {
margin-bottom: 24px;
}
.detail-label {
color: var(--text-secondary);
margin-bottom: 8px;
font-weight: 500;
font-size: 15px;
}
.detail-content {
color: var(--text-color);
line-height: 1.8;
font-size: 15px;
}
.detail-reply {
background-color: #f9f9f9;
padding: 16px;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
.detail-attachments {
display: flex;
flex-wrap: wrap;
}
.attachment-item {
width: 90px;
height: 90px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.attachment-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.loader {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.van-button--primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* 下拉刷新和上拉加载样式 */
.van-pull-refresh__track {
flex: 1;
}
.van-list {
min-height: 100%;
}
.no-reply {
padding: 20px 0;
text-align: center;
background-color: var(--bg-color);
border-radius: 8px;
}
.no-reply-icon {
font-size: 36px;
color: #ccc;
margin-bottom: 8px;
}
.no-reply-text {
font-size: 14px;
color: var(--text-light);
}
.submitter-info {
background-color: var(--bg-color);
border-radius: 8px;
padding: 12px 15px;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8px;
line-height: 1.6;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-label {
color: var(--text-secondary);
width: 80px;
font-size: 14px;
}
.info-value {
color: var(--text-color);
flex: 1;
font-size: 14px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="我的意见建议" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
<!-- 内容区域 -->
<div class="suggestion-list">
<!-- 下拉刷新和上拉加载更多 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="loadMore">
<!-- 空状态 -->
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
<van-icon name="comment-circle-o" class="empty-icon" />
<div class="empty-text">您还没有提交过意见</div>
<van-button type="primary" size="normal" round @click="goToSubmitPage">去提交意见</van-button>
</div>
<!-- 意见列表 -->
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id" @click="showDetail(item)">
<div class="suggestion-header">
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
<div class="suggestion-status" :class="getStatusClass(item.isReply)">{{ item.node_name }}</div>
</div>
<div class="suggestion-content">{{ item.content }}</div>
<div class="suggestion-footer">
<div class="suggestion-time">
<van-icon name="clock-o" />
<span>{{ formatDate(item.submitTime) }}</span>
</div>
<div style="display: flex; column-gap: 10px">
<div class="suggestion-action">
查看详情
<van-icon name="arrow" />
</div>
<div class="suggestion-action" v-if="item.flow_status==='1'" @click.stop="onRevoke(item)">
撤销
<van-icon name="arrow" />
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 详情弹出层 -->
<van-popup v-model="showDetailPopup" round closeable position="bottom" :style="{ height: '80%' }">
<div class="detail-popup" v-if="currentSuggestion">
<div class="detail-header">
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
<div class="detail-meta">
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
</div>
</div>
<div class="detail-section">
<div class="detail-label">提交人信息</div>
<div class="submitter-info">
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">工号:</span>
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">单位:</span>
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系方式:</span>
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-label">意见内容</div>
<div class="detail-content">{{ currentSuggestion.content }}</div>
</div>
<div class="detail-section" v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
<div class="detail-label">附件</div>
<div class="detail-attachments">
<div
class="attachment-item"
v-for="(file, idx) in currentSuggestion.attachments"
:key="idx"
@click.stop="previewImage(file.url, idx)"
>
<img :src="file.url" class="attachment-image" />
</div>
</div>
</div>
<div class="detail-section" v-if="currentSuggestion.isReply">
<div class="detail-label">回复</div>
<div class="detail-reply detail-content">{{ currentSuggestion.replyContent }}</div>
</div>
<div v-else class="detail-section">
<div class="detail-label">回复</div>
<div class="no-reply">
<van-icon name="chat-o" class="no-reply-icon" />
<div class="no-reply-text">暂无回复,请耐心等待</div>
</div>
</div>
</div>
</van-popup>
</div>
<script>
new Vue({
el: "#app",
data() {
return {
refreshing: false,
loading: false,
finished: false,
showDetailPopup: false,
currentSuggestion: null,
pageNumber: 1,
pageSize: 10,
suggestions: []
}
},
created() {
this.loadData()
},
methods: {
loadData() {
this.loading = true
$.post("/platform/suggestionBox/pageData", {
pageNumber: this.pageNumber,
pageSize: this.pageSize
})
.done((res) => {
if (res.code === 0 && res.data) {
if (this.pageNumber === 1) {
this.suggestions = res.data.list || []
} else {
this.suggestions = this.suggestions.concat(res.data.list || [])
}
this.finished = !res.data.list || res.data.list.length < this.pageSize
} else {
this.finished = true
}
this.loading = false
this.refreshing = false
})
.fail(() => {
this.loading = false
this.refreshing = false
this.finished = true
})
},
// 下拉刷新
onRefresh() {
this.pageNumber = 1
this.finished = false
this.loadData()
},
// 上拉加载更多
loadMore() {
this.pageNumber++
this.loadData()
},
// 查看详情
showDetail(suggestion) {
if (suggestion.attachments && typeof suggestion.attachments === "string") {
try {
suggestion.attachments = JSON.parse(suggestion.attachments)
} catch (e) {
suggestion.attachments = []
}
}
this.currentSuggestion = suggestion
this.showDetailPopup = true
},
// 获取状态class
getStatusClass(isReply) {
if (!isReply || isReply === 0) return "status-pending"
if (isReply === 1) return "status-processing"
return "status-completed"
},
// 获取状态文本
getStatusText(isReply) {
if (!isReply || isReply === 0) return "待处理"
if (isReply === 1) return "处理中"
return "已处理"
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return ""
const date = new Date(dateStr)
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0")
},
// 前往提交意见页面
goToSubmitPage() {
window.location.href = "/platform/suggestionBox/h5/write"
},
// 预览图片
previewImage(url, index) {
if (!url) return
// 创建图片查看器
const urls = this.currentSuggestion.attachments.map((file) => file.url)
this.$imagePreview({
images: urls,
startPosition: index
})
},
// 撤销
onRevoke(item) {
this.$dialog
.confirm({
title: "提示",
message: "您确认要撤销吗?"
})
.then(() => {})
}
}
})
</script>
<!--#}#-->
@@ -2,120 +2,9 @@
layout("/layouts/platform_h5.html"){
#-->
<style>
/*.list-container {*/
/* padding: 0;*/
/*}*/
/*.list-item {*/
/* background-color: #fff;*/
/* border-bottom: 1px solid #eaecef;*/
/* padding: 16px;*/
/* position: relative;*/
/* margin-bottom: 10px;*/
/*}*/
/*.list-item:last-child {*/
/* border-bottom: none;*/
/*}*/
/*.item-header {*/
/* display: flex;*/
/* justify-content: space-between;*/
/* align-items: flex-start;*/
/* margin-bottom: 10px;*/
/*}*/
/*.item-title {*/
/* font-size: 16px;*/
/* font-weight: 500;*/
/* color: #1f2f3d;*/
/* overflow: hidden;*/
/* text-overflow: ellipsis;*/
/* white-space: nowrap;*/
/* flex: 1;*/
/* padding-right: 10px;*/
/*}*/
/*.item-meta {*/
/* display: flex;*/
/* color: #606266;*/
/* font-size: 14px;*/
/* margin-bottom: 5px;*/
/* padding: 4px 0;*/
/*}*/
/*.meta-item {*/
/* display: flex;*/
/* align-items: center;*/
/* gap: 8px;*/
/* width: 100%;*/
/*}*/
/*.meta-item i {*/
/* font-size: 14px;*/
/* color: #909399;*/
/* width: 16px;*/
/* text-align: center;*/
/*}*/
/*.item-content {*/
/* color: #606266;*/
/* font-size: 14px;*/
/* overflow: hidden;*/
/* text-overflow: ellipsis;*/
/* display: -webkit-box;*/
/* -webkit-line-clamp: 2;*/
/* -webkit-box-orient: vertical;*/
/* line-height: 1.5;*/
/*}*/
/*.item-footer {*/
/* display: flex;*/
/* justify-content: space-between;*/
/* margin-top: 12px;*/
/* padding-top: 8px;*/
/* border-top: 1px dashed #eaecef;*/
/* color: #909399;*/
/* font-size: 13px;*/
/*}*/
/*.item-actions {*/
/* display: flex;*/
/* justify-content: space-around;*/
/* margin-top: 15px;*/
/* padding-top: 12px;*/
/* border-top: 1px solid #eaecef;*/
/*}*/
/*.action-btn {*/
/* display: flex;*/
/* align-items: center;*/
/* justify-content: end;*/
/* font-size: 14px;*/
/* color: var(--color-primary);*/
/* padding: 4px 0;*/
/* flex: 1;*/
/*}*/
/*.action-btn i {*/
/* margin-right: 6px;*/
/* font-size: 16px;*/
/*}*/
/*.action-btn.delete {*/
/* color: #f56c6c;*/
/*}*/
/*.action-btn.review {*/
/* color: #67c23a;*/
/*}*/
/*.empty-state {*/
/* text-align: center;*/
/* padding: 60px 20px;*/
/* color: #909399;*/
/*}*/
/*.empty-state i {*/
/* font-size: 60px;*/
/* margin-bottom: 16px;*/
/* color: #dcdee0;*/
/*}*/
/*.empty-state p {*/
/* margin-top: 10px;*/
/* font-size: 14px;*/
/*}*/
</style>
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="我的建言献策" placeholder
fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
@@ -127,10 +16,9 @@ layout("/layouts/platform_h5.html"){
></van-search>
</van-sticky>
<table-list api="/platform/proposal/mine/pageData" :page_form.sync="pageForm">
<table-list api="/platform/suggestionBox/mine/pageData" :page_form.sync="pageForm" @ready="onReady" ref="tableListRef" title="title">
<template v-slot="{index,row}">
<table-column label="申请人">{{row.userName}}</table-column>
<table-column label="所属单位">{{row.unitName}}</table-column>
<table-column label="申请时间">{{row.submitTime}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
@@ -155,12 +43,20 @@ layout("/layouts/platform_h5.html"){
</div>
</template>
</table-list>
<suggestion-box-info ref="suggestionBoxInfoRef"></suggestion-box-info>
</div>
<script>
<!--#include('../info.js'){}#-->
new Vue({
el: '#app',
store,
components: {
'suggestion-box-info': SUGGESTION_BOX_INFO
},
data() {
return {
pageForm: {
@@ -174,35 +70,28 @@ layout("/layouts/platform_h5.html"){
},
methods: {
historyBack,
doSearch() {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.tableData = []
this.pageData()
onReady(){
this.doSearch()
},
pageData() {
$.post('/platform/suggestionBox/mine/pageData', this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = this.tableData.concat(res.data.list)
this.pageForm.totalCount = res.data.totalCount
if (this.tableData.length >= this.pageForm.totalCount) {
this.tableFinished = true
} else {
this.pageForm.pageNumber++
}
this.tableLoading = false
}
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
// 查看详情
onView(item) {
onView(row) {
this.showApprovalForm = false
this.$refs.suggestionBoxInfoRef.onOpen(row)
},
// 编辑
onEdit(row) {
this.$pjaxReplace("/platform/h5/suggestionBox/write?bizId=" + row.id + "&taskId=" + row.startTaskId)
},
// 撤回
@@ -221,7 +110,6 @@ layout("/layouts/platform_h5.html"){
});
},
//
onDelete(id) {
this.$dialog.confirm({
title: '提示',
@@ -236,9 +124,6 @@ layout("/layouts/platform_h5.html"){
}).catch(() => {
});
}
},
created() {
this.pageData()
}
})
</script>
@@ -0,0 +1,83 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="建言献策查询" placeholder
fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入关键字搜索"
@search="doSearch"
></van-search>
</van-sticky>
<table-list api="/platform/suggestionBox/query/pageData" :page_form.sync="pageForm" @ready="onReady"
ref="tableListRef" title="title">
<template v-slot="{index,row}">
<table-column label="申请人">{{row.userName}}</table-column>
<table-column label="申请时间">{{row.submitTime}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
</template>
</table-list>
<suggestion-box-info ref="suggestionBoxInfoRef"></suggestion-box-info>
</div>
<script>
<!--#include('../info.js'){}#-->
new Vue({
el: '#app',
store,
components: {
'suggestion-box-info': SUGGESTION_BOX_INFO
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: "",
},
}
},
methods: {
historyBack,
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
// 查看详情
onView(row) {
this.showApprovalForm = false
this.$refs.suggestionBoxInfoRef.onOpen(row)
}
}
})
</script>
<!--#
}
#-->
@@ -1,363 +0,0 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.task-form-container {
padding: 16px;
background-color: #f5f7fa;
min-height: 100vh;
}
.card {
background-color: #fff;
border-radius: 12px;
margin-bottom: 16px;
padding: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
display: flex;
align-items: center;
}
.card-title::before {
content: "";
display: inline-block;
width: 4px;
height: 16px;
background-color: rgb(0, 78, 100);
margin-right: 8px;
border-radius: 2px;
}
.info-item {
display: flex;
margin-bottom: 12px;
line-height: 1.6;
}
.info-label {
color: #666;
width: 80px;
font-size: 14px;
}
.info-value {
color: #333;
flex: 1;
font-size: 14px;
}
.content-box {
background-color: #f9f9f9;
padding: 12px;
border-radius: 8px;
margin-top: 8px;
white-space: pre-line;
word-break: break-all;
}
.status-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
margin-left: 8px;
}
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-processing {
background-color: #fff7e6;
color: #fa8c16;
}
.status-completed {
background-color: #f6ffed;
color: #52c41a;
}
.timeline {
padding: 8px 0;
}
.reply-form {
margin-top: 16px;
}
.reply-textarea {
width: 100%;
height: 120px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
font-size: 14px;
}
.btn-container {
display: flex;
justify-content: center;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="意见箱处理" left-arrow @click-left="goBack" fixed placeholder></van-nav-bar>
<div class="task-form-container">
<!-- 加载状态 -->
<van-empty v-if="loading" description="加载中...">
<template #image>
<van-loading type="spinner" size="36" />
</template>
</van-empty>
<template v-else>
<!-- 业务表单数据 -->
<div class="card">
<div class="card-title">基本信息</div>
<div class="info-item">
<span class="info-label">提交人:</span>
<span class="info-value">{{ suggestionData.submitterName }}</span>
</div>
<div class="info-item">
<span class="info-label">工号:</span>
<span class="info-value">{{ suggestionData.submitterLoginName }}</span>
</div>
<div class="info-item">
<span class="info-label">单位:</span>
<span class="info-value">{{ suggestionData.submitterUnitName }}</span>
</div>
<div class="info-item">
<span class="info-label">联系方式:</span>
<span class="info-value">{{ suggestionData.concat }}</span>
</div>
<div class="info-item">
<span class="info-label">提交时间:</span>
<span class="info-value">{{ formatDate(suggestionData.submitTime) }}</span>
</div>
<div class="info-item">
<span class="info-label">标题:</span>
<span class="info-value">{{ suggestionData.title || '无标题' }}</span>
</div>
<div class="info-item">
<span class="info-label">内容:</span>
</div>
<div class="content-box">{{ suggestionData.content }}</div>
</div>
<!-- 流程信息 -->
<div class="card">
<div class="card-title">
流程信息
<span class="status-tag" :class="getStatusClass()">{{ getStatusText() }}</span>
</div>
<div class="timeline">
<van-steps direction="vertical" :active="flowHistory.length - 1">
<van-step v-for="(item, index) in flowHistory" :key="index">
<h3>{{ item.taskName }}</h3>
<p>处理人: {{ item.assigneeName || '系统' }}</p>
<p>开始时间: {{ formatDate(item.startTime) }}</p>
<p v-if="item.endTime">完成时间: {{ formatDate(item.endTime) }}</p>
<p v-if="item.comment">处理意见: {{ item.comment }}</p>
</van-step>
</van-steps>
</div>
</div>
<!-- 回复表单 -->
<div class="card" v-if="!taskCompleted && currentTask">
<div class="card-title">处理意见</div>
<div class="reply-form">
<textarea v-model="replyForm.replyContent" class="reply-textarea"
placeholder="请输入回复内容"></textarea>
<div class="btn-container">
<van-button type="primary" block round @click="submitReply">提交回复</van-button>
</div>
</div>
</div>
<!-- 已回复信息 -->
<div class="card" v-if="suggestionData.isReply">
<div class="card-title">回复信息</div>
<div class="info-item">
<span class="info-label">回复人:</span>
<span class="info-value">{{ suggestionData.replyUserName }}</span>
</div>
<div class="info-item">
<span class="info-label">回复时间:</span>
<span class="info-value">{{ formatDate(suggestionData.replyTime) }}</span>
</div>
<div class="info-item">
<span class="info-label">回复内容:</span>
</div>
<div class="content-box">{{ suggestionData.replyContent }}</div>
</div>
</template>
</div>
</div>
<script>
new Vue({
el: "#app",
data() {
return {
loading: true,
taskId: "",
businessKey: "",
processInstanceId: "",
suggestionData: {},
flowHistory: [],
currentTask: null,
taskCompleted: false,
replyForm: {
id: "",
replyContent: ""
}
}
},
created() {
// 获取URL参数
this.taskId = GetQueryString("taskId")
this.businessKey = GetQueryString("businessKey")
this.processInstanceId = GetQueryString("processInstanceId")
// 加载数据
this.loadData()
},
methods: {
goBack() {
history.back()
},
// 加载数据
async loadData() {
this.loading = true
try {
// 1. 获取业务数据
await this.loadSuggestionData()
// 2. 获取流程历史
await this.loadFlowHistory()
// 3. 获取当前任务信息
if (this.taskId) {
await this.loadTaskInfo()
}
} catch (error) {
console.error("加载数据失败:", error)
this.$toast.fail("加载数据失败")
} finally {
this.loading = false
}
},
// 加载意见箱数据
async loadSuggestionData() {
if (!this.businessKey) return
const {
code,
data
} = await $.post("/platform/suggestionBox/admin/getSuggestionById", { id: this.businessKey })
if (code === 0 && data) {
this.suggestionData = data
this.replyForm.id = data.id
}
},
// 加载流程历史
async loadFlowHistory() {
if (!this.processInstanceId) return
const {
code,
data
} = await $.post("/platform/workflow/task/getProcessHistory", { processInstanceId: this.processInstanceId })
if (code === 0 && data) {
this.flowHistory = data
}
},
// 加载任务信息
async loadTaskInfo() {
const { code, data } = await $.post("/platform/workflow/task/getTaskInfo", { taskId: this.taskId })
if (code === 0 && data) {
this.currentTask = data
this.taskCompleted = data.endTime != null
}
},
// 提交回复
async submitReply() {
if (!this.replyForm.replyContent.trim()) {
this.$toast("请输入回复内容")
return
}
try {
// 保存业务数据并完成任务
const result = await $.post("/platform/suggestionBox/admin/reply", {
reply: JSON.stringify(this.replyForm),
taskId: this.taskId
})
if (result.code === 0) {
this.$toast.success("处理成功")
// 重新加载数据
setTimeout(() => {
this.loadData()
}, 1000)
} else {
this.$toast.fail(result.msg || "处理失败")
}
} catch (error) {
console.error("提交回复失败:", error)
this.$toast.fail("提交回复失败")
}
},
// 获取状态样式
getStatusClass() {
if (this.flowHistory.length === 0) return "status-pending"
const lastTask = this.flowHistory[this.flowHistory.length - 1]
if (!lastTask.endTime) return "status-processing"
return "status-completed"
},
// 获取状态文本
getStatusText() {
if (this.flowHistory.length === 0) return "未开始"
const lastTask = this.flowHistory[this.flowHistory.length - 1]
if (!lastTask.endTime) return "处理中"
return "已完成"
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return ""
const date = new Date(dateStr)
return date.getFullYear() + "-" +
String(date.getMonth() + 1).padStart(2, "0") + "-" +
String(date.getDate()).padStart(2, "0") + " " +
String(date.getHours()).padStart(2, "0") + ":" +
String(date.getMinutes()).padStart(2, "0")
}
}
})
</script>
<!--#
}
#-->
@@ -1,294 +0,0 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.form-container {
background-color: #f6f6f6;
padding: 0;
}
.form-section {
margin-bottom: 12px;
}
.section-title {
display: flex;
align-items: center;
padding: 12px 16px;
background: #fff;
border-bottom: 1px solid #f6f6f6;
}
.dot {
width: 8px;
height: 8px;
background-color: rgb(0, 78, 100);
border-radius: 50%;
margin-right: 8px;
}
.section-title span {
color: #333;
font-weight: 500;
}
.input-row {
display: flex;
align-items: center;
border-bottom: 1px solid #f5f5f5;
padding: 12px 16px;
background: #fff;
}
.input-label {
width: 80px;
color: #333;
padding: 8px 8px 8px 0;
}
.input-control {
flex: 1;
text-align: right;
}
.input-control input {
width: 100%;
border: none;
outline: none;
text-align: right;
color: #666;
font-size: 14px;
}
.textarea-container {
padding: 10px 16px;
background: #fff;
border-bottom: 1px solid #f5f5f5;
}
.textarea-container textarea {
width: 100%;
height: 120px;
border: none;
outline: none;
resize: none;
font-size: 14px;
color: #333;
}
.word-count {
text-align: right;
font-size: 12px;
color: #999;
margin-top: 4px;
}
.upload-area {
padding: 16px;
background: #fff;
}
.upload-grid {
display: flex;
flex-wrap: wrap;
}
.upload-item,
.upload-btn {
width: 80px;
height: 80px;
margin-right: 8px;
margin-bottom: 8px;
border-radius: 4px;
overflow: hidden;
position: relative;
}
.upload-btn {
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.uploaded-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 0 0 4px;
}
.submit-area {
padding: 20px 16px;
}
</style>
<div id="app" v-cloak>
<van-sticky>
<van-nav-bar title="我要提意见" left-arrow left-text="返回" @click-left="history.go(-1)"></van-nav-bar>
</van-sticky>
<div class="form-container">
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写人信息</span>
</div>
<div class="input-row">
<div class="input-label">姓名</div>
<div class="input-control">
<input type="text" v-model="formData.submitterName" placeholder="请输入姓名" readonly />
</div>
</div>
<div class="input-row">
<div class="input-label">工号</div>
<div class="input-control">
<input type="text" v-model="formData.submitterLoginName" placeholder="请输入工号" readonly />
</div>
</div>
<div class="input-row">
<div class="input-label">手机号码</div>
<div class="input-control">
<input type="tel" v-model="formData.concat" placeholder="请输入手机号码" />
</div>
</div>
<div class="input-row">
<div class="input-label">所在单位</div>
<div class="input-control">
<input type="text" v-model="formData.submitterUnitName" placeholder="请输入所在单位" readonly />
</div>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写标题</span>
</div>
<div class="input-row">
<div class="input-label">标题</div>
<div class="input-control">
<input type="text" v-model="formData.title" placeholder="请输入意见标题" />
</div>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写意见建议内容</span>
</div>
<div class="textarea-container">
<textarea v-model="formData.content" placeholder="请描述您要填写的意见建议内容..."></textarea>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>照片上传</span>
</div>
<div class="upload-area">
<!-- <vant-file-upload :files.sync="formData.attachments" :max="15"></vant-file-upload>-->
<!-- <div class="upload-grid">-->
<!-- <div class="upload-item" v-for="(item, index) in formData.fileList" :key="index">-->
<!-- <img :src="item.content || item.url" class="uploaded-image">-->
<!-- <div class="delete-btn" @click="deleteImage(index)">-->
<!-- <van-icon name="cross" />-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="upload-btn" v-if="formData.fileList.length < 3" @click="triggerUpload">-->
<!-- <input type="file" ref="fileInput" style="display:none" accept="image/*" @change="onFileChange" multiple>-->
<!-- <van-icon name="plus" size="24" />-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
<div class="submit-area">
<van-button @click="submitForm" style="border-radius: 10px" block type="info">提 交</van-button>
</div>
</div>
</div>
<script>
new Vue({
el: "#app",
data() {
return {
formData: {
name: "",
mobile: "",
idCard: "",
department: "",
content: "",
address: "",
fileList: []
}
}
},
methods: {
goBack() {
history.back()
},
async submitForm() {
if (!this.formData.concat.trim()) {
this.$toast("请输入手机号码")
return
}
//正则验证
if (!/^1[3-9]\d{9}$/.test(this.formData.concat)) {
this.$toast("请输入正确的手机号码")
return
}
if (!this.formData.content.trim()) {
this.$toast("请输入投诉内容")
return
}
this.$dialog
.confirm({
title: "提示",
message: "您确定要提交吗?"
})
.then(async () => {
const { code, data, msg } = await $.post("/platform/suggestionBox/submit", { suggestion: JSON.stringify(this.formData) })
if (code === 0) {
this.$toast.success("提交成功")
setTimeout(() => {
// history.back()
}, 200)
} else {
this.$toast(msg)
}
})
}
},
created() {
const id = GetQueryString("id")
if (!id) {
this.$set(this.formData, "submitterId", "${@auth.getPrincipalProperty('id')}")
this.$set(this.formData, "submitterName", "${@auth.getPrincipalProperty('username')}")
this.$set(this.formData, "submitterLoginName", "${@auth.getPrincipalProperty('loginname')}")
this.$set(this.formData, "submitterUnitId", "${@auth.getPrincipalProperty('unitid')}")
this.$set(this.formData, "submitterUnitName", "${@auth.getPrincipalProperty('unit').getName()}")
this.$set(this.formData, "submitterUnionId", "${@auth.getPrincipalProperty('union').getId()}")
this.$set(this.formData, "submitterUnionName", "${@auth.getPrincipalProperty('union').getName()}")
this.$set(this.formData, "concat", "${@auth.getPrincipalProperty('mobile')}")
console.log(this.formData)
}
}
})
</script>
<!--#}#-->
@@ -3,8 +3,7 @@ layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="填写建言献策" left-text="返回" left-arrow @click-left="historyBack" placeholder
fixed></van-nav-bar>
<van-nav-bar title="填写建言献策" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="form-container">
<van-form ref="formRef">
<van-cell-group title="基本信息">
@@ -39,7 +38,7 @@ layout("/layouts/platform_h5.html"){
<div class="form-actions">
<van-button plain type="info" @click="onSave">保存</van-button>
<van-button type="primary" @click="onSubmit" v-if="!taskId">提交</van-button>
<van-button type="primary" @click="onSubmitAgain" v-else>提交</van-button>
<van-button type="primary" @click="onFinishTask" v-else>提交</van-button>
</div>
</van-form>
</div>
@@ -74,6 +73,7 @@ layout("/layouts/platform_h5.html"){
this.$axios.post('/platform/suggestionBox/write/save', {data: JSON.stringify(this.formData)}).then(res => {
if (res.code === 0) {
this.$toast.success("保存成功");
pjaxReplace("/platform/h5/suggestionBox/mine")
}
})
}).catch(() => {
@@ -95,7 +95,8 @@ layout("/layouts/platform_h5.html"){
}).then(() => {
this.$axios.post('/platform/suggestionBox/write/submit', {data: JSON.stringify(this.formData)}).then(res => {
if (res.code === 0) {
this.$toast.success("保存成功");
this.$toast.success("提交成功");
pjaxReplace("/platform/h5/suggestionBox/mine")
}
})
}).catch(() => {
@@ -121,7 +122,7 @@ layout("/layouts/platform_h5.html"){
}).then(res => {
if (res.code === 0) {
this.$toast.success("提交成功");
window.location.href = '/platform/suggestionBox/mine';
pjaxReplace("/platform/h5/suggestionBox/mine")
}
})
}).catch(() => {
@@ -0,0 +1,176 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="建言献策审核" placeholder
fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入关键字搜索"
@search="doSearch"
></van-search>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/suggestionBox/xgh/pageData" :page_form.sync="pageForm" @ready="onReady"
ref="tableListRef" title="title">
<template v-slot="{index,row}">
<table-column label="申请人">{{row.userName}}</table-column>
<table-column label="申请时间">{{row.submitTime}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-edit"></i>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(item)">
<i class="fa fa-undo"></i>
<span>撤回</span>
</div>
</template>
</table-list>
<suggestion-box-info ref="suggestionBoxInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</suggestion-box-info>
</div>
<script>
<!--#include('../info.js'){}#-->
new Vue({
el: '#app',
store,
components: {
'suggestion-box-info': SUGGESTION_BOX_INFO
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: "",
sessionId: null,
approvalText: "0",
approval: false
},
formData: {},
showApprovalForm: false
}
},
methods: {
historyBack,
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
// 查看详情
onView(row) {
this.showApprovalForm = false
this.$refs.suggestionBoxInfoRef.onOpen(row)
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.suggestionBoxInfoRef.onOpen(row)
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName
}
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate();
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.suggestionBoxInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
})
} catch (error) {
}
},
// 撤回
onRevoke(row) {
this.$dialog.confirm({
title: '提示',
message: '您确定要撤回吗?',
}).then(() => {
this.$axios.post('/flow/common/revokeTask', {taskId: row.startTaskId}).then(res => {
if (res.code === 0) {
this.$toast.success('撤回成功');
this.doSearch();
}
})
}).catch(() => {
});
}
}
})
</script>
<!--#
}
#-->