Merge branch 'release_20260831' into feature_周何峰

This commit is contained in:
2026-08-31 15:20:19 +08:00
45 changed files with 7516 additions and 2399 deletions
@@ -70,7 +70,7 @@ public class FlowCommonService {
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "userName", SecurityUtil.getUserUsername());
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "loginName", SecurityUtil.getUserLoginname());
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitId", SecurityUtil.getUnitId());
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitName", Optional.of(sysUnit).map(Sys_unit::getName).orElse(""));
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitName", Optional.ofNullable(sysUnit).map(Sys_unit::getName).orElse(""));
args.put(FlowConst.TASK_FORM_DATA_PREFIX + "unionId", SecurityUtil.getUnionId());
if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.ROLLBACK.getCode())) {
@@ -11,7 +11,7 @@ import org.nutz.mvc.annotation.Ok;
public class ActivityWorksCollectionNewController {
@At("")
@Ok("beetl:/platform/zhgh/activity/workscollection/manage/index.html")
@Ok("beetl:/platform/zhgh/activity/workscollection/new/index.html")
@SaCheckPermission(value = {"activity.workscollection.manage", "activity.workscollection.new"}, mode = SaMode.OR)
public void index() {
}
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.activity.workscollection.controller;
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;
import com.budwk.app.base.result.Result;
@@ -19,6 +20,7 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -56,16 +58,21 @@ public class ActivityWorksCollectionUploadController {
/**
* @param pageForm 分页
* @param activityId 活动id
* @param subjectId 作品类型
* @return
* @param subjectId 主题类型主键
* @param worksId 作品类型主键
* @param year 活动开始时间所在年度
* @param activityType 活动状态:1 全部、2 征集中、3 已结束
* @return Resultdata 为当前登录人的作品分页数据
*/
@At
@SaCheckPermission("activity.workscollection.upload")
public Result pageData(@Valid PageForm pageForm, String activityId, String subjectId, String worksId, Long year) {
public Result pageData(@Valid PageForm pageForm, String activityId, String subjectId, String worksId,
Long year, Integer activityType) {
Sql sql = Sqls.create("""
select
up.*,
co.NAME AS activityName,
co.cover AS cover,
co.startDateTime AS activityStartDateTime,
co.endDateTime AS activityEndDateTime,
su.typeName AS subjectName,
@@ -83,6 +90,19 @@ public class ActivityWorksCollectionUploadController {
cnd.andEX("up.activityId", "=", activityId);
cnd.andEX("up.subjectId", "=", subjectId);
cnd.andEX("up.worksId", "=", worksId);
// searchKeyword 支持按作品名称或所属活动名称搜索,空值不参与筛选。
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
String keyword = "%" + pageForm.getSearchKeyword().trim() + "%";
SqlExpressionGroup keywordGroup = Cnd.exps("up.name", "like", keyword)
.or("co.name", "like", keyword);
cnd.and(keywordGroup);
}
// activityType1 或空值查询全部,2 查询征集中,3 查询已结束。
if (activityType != null && activityType == 2) {
cnd.and(new Static("now() >= co.startDateTime and now() < co.endDateTime"));
} else if (activityType != null && activityType == 3) {
cnd.and(new Static("now() >= co.endDateTime"));
}
cnd.desc("up.createdAt");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.workscollection.controller.h5;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -64,7 +65,11 @@ public class H5ActivityWorksUploadCollectionController {
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("YEAR(awc.startDateTime)", "=", year);
//查询报名中
// searchKeyword 为活动名称关键字,空值不参与筛选;接口仍返回分页对象。
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and("awc.name", "like", "%" + pageForm.getSearchKeyword().trim() + "%");
}
// activityType1 全部、2 征集中、3 已结束、4 即将开始。
if (activityType == 2) {
cnd.and(new Static("now() > awc.startDateTime and now() < awc.endDateTime"));
}//查询已结束的
@@ -99,7 +99,6 @@ public class DifficultHelpMineController {
difficult_help_info info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
$condition
""");
Cnd cnd = Cnd.NEW();
Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

@@ -0,0 +1,8 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="24" cy="24" r="22" fill="#EAF3FF"/>
<rect x="15" y="11" width="18" height="17" rx="4" fill="#2F7CF6"/>
<path d="M12 24C10.8954 24 10 24.8954 10 26V32C10 33.1046 10.8954 34 12 34H36C37.1046 34 38 33.1046 38 32V26C38 24.8954 37.1046 24 36 24C34.8954 24 34 24.8954 34 26V29H14V26C14 24.8954 13.1046 24 12 24Z" fill="#1769E8"/>
<rect x="14" y="28" width="20" height="7" rx="2.5" fill="#2F7CF6"/>
<path d="M16 34H20L19 38H15L16 34Z" fill="#1769E8"/>
<path d="M28 34H32L33 38H29L28 34Z" fill="#1769E8"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

@@ -3,16 +3,16 @@
<div class="file-preview-container" v-if="fileList && fileList.length > 0">
<div class="file-item" v-for="item in fileList" :key="item.id">
<img
v-if="item.suffix === 'jpg' || item.suffix === 'png' || item.suffix === 'jpeg' || item.suffix === 'gif'"
v-if="isImageFile(item)"
:src="item.thumbnail"
@click.stop="$commonUtil.previewFile(item)"
@click.stop="onPreviewFile(item)"
class="file-image"
/>
<img
v-else
:src="$commonUtil.getFileItemShowIcon(item.suffix)"
@click.stop="$commonUtil.previewFile(item)"
@click.stop="onPreviewFile(item)"
class="file-image"
/>
@@ -57,7 +57,12 @@ module.exports = {
},
data() {
return {
fileList: []
fileList: [],
imagePreviewInstance: null,
imagePreviewContainer: null,
imagePreviewHistoryUnsubscribe: null,
closingImagePreviewFromHistory: false,
imagePreviewLayerName: "h5-file-image-preview"
}
},
methods: {
@@ -103,9 +108,94 @@ module.exports = {
requestFullFile(ids) {
this.$axios.post("/platform/sys/file/previewFileData", {ids: JSON.stringify(ids)}).then((res) => {
if (res.code === 0) {
this.fileList = res.data
this.$set(this, "fileList", res.data || [])
}
})
},
// 判断附件是否为图片;item 传完整文件对象,返回 boolean 类型。
isImageFile(item) {
const suffix = item && item.suffix ? String(item.suffix).toLowerCase() : ""
return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(suffix)
},
/**
* 预览附件。
* item 传 previewFileData 返回的文件对象,需包含 id、suffix、downloadPath;方法无返回值。
* 图片会与当前附件列表中的其他图片组成轮播,非图片继续使用公共文件预览逻辑。
*/
onPreviewFile(item) {
if (!this.isImageFile(item)) {
this.$commonUtil.previewFile(item)
return
}
if (this.imagePreviewInstance) return
const imageFiles = this.fileList.filter((file) => this.isImageFile(file) && file.downloadPath)
const startPosition = imageFiles.findIndex((file) => file.id === item.id)
if (startPosition < 0) return
const previewContainer = document.createElement("div")
previewContainer.style.display = "none"
imageFiles.forEach((file) => {
const image = document.createElement("img")
image.src = file.downloadPath
image.alt = file.name || ""
previewContainer.appendChild(image)
})
document.body.appendChild(previewContainer)
this.$set(this, "closingImagePreviewFromHistory", false)
this.$set(this, "imagePreviewContainer", previewContainer)
this.$set(this, "imagePreviewInstance", new Viewer(previewContainer, {
zIndex: 99999,
title: false,
initialViewIndex: startPosition,
hidden: () => { this.handleImagePreviewHidden() }
}))
this.imagePreviewInstance.show()
window.h5HistoryLayerManager.open(this.imagePreviewLayerName)
},
// Viewer 完全关闭后销毁实例和临时图片容器,并按关闭来源同步浏览器历史。
handleImagePreviewHidden() {
const closedFromHistory = this.closingImagePreviewFromHistory
this.cleanupImagePreview()
if (closedFromHistory) {
this.$set(this, "closingImagePreviewFromHistory", false)
return
}
if (window.h5HistoryLayerManager.stack.includes(this.imagePreviewLayerName)) {
window.h5HistoryLayerManager.close(this.imagePreviewLayerName)
}
},
// 清理 Viewer 实例及其临时 DOM;方法无参数、无返回值。
cleanupImagePreview() {
const previewInstance = this.imagePreviewInstance
const previewContainer = this.imagePreviewContainer
this.$set(this, "imagePreviewInstance", null)
this.$set(this, "imagePreviewContainer", null)
if (previewInstance) previewInstance.destroy()
if (previewContainer && previewContainer.parentNode) {
previewContainer.parentNode.removeChild(previewContainer)
}
},
// 浏览器返回导致图片预览层出栈时,仅关闭预览实例,不再重复回退历史记录。
syncImagePreviewHistory(stack) {
if (!this.imagePreviewInstance || stack.includes(this.imagePreviewLayerName)) return
this.$set(this, "closingImagePreviewFromHistory", true)
this.imagePreviewInstance.hide()
}
},
created() {
this.$set(this, "imagePreviewHistoryUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncImagePreviewHistory(stack)
}))
},
beforeDestroy() {
if (this.imagePreviewHistoryUnsubscribe) this.imagePreviewHistoryUnsubscribe()
if (this.imagePreviewInstance || this.imagePreviewContainer) {
this.$set(this, "closingImagePreviewFromHistory", true)
this.cleanupImagePreview()
}
}
}
@@ -118,283 +118,7 @@ layout("/layouts/platform.html"){
</div>
</template>
<template #edit>
<div v-if="formVisible">
<el-form :model="formData" ref="formRef" label-width="130px" :rules="formRules">
<el-form-item label="活动主题" prop="name">
<el-input placeholder="请输入活动主题" v-model="formData.name" clearable></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动时间" prop="activityTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.activityTime"
value-format="yyyy-MM-dd HH:mm:ss"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点赞时间" prop="nbTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.nbTime"
value-format="yyyy-MM-dd HH:mm:ss"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="每人每日点赞次数" prop="nbCount">
<el-input-number
:min="0"
placeholder="请输入每人每日点赞次数"
style="width: 100%"
v-model="formData.nbCount"
></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="主题类型" prop="subjectTypes">
<el-row class="pb5" v-for="(type,index) in formData.subjectTypes" :key="type.id">
<el-col :span="20">
<el-input placeholder="请输入名称" v-model="type.typeName"></el-input>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addType(index)" icon="el-icon-plus" type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button
:disabled="index == 0"
@click="formData.subjectTypes.splice(index,1)"
icon="el-icon-delete"
type="danger"
></el-button>
</el-col>
<el-col class="pt5 pb5">
<el-row class="pb5" v-for="(worksType,worksIndex) in type.worksTypes" :key="worksType.id">
<el-col :span="18">
<el-col :span="2">
<span class="el-form-item__label" v-if="worksIndex === 0">作品类型</span>
<span v-else>&nbsp;</span>
</el-col>
<el-col :span="22">
<el-input placeholder="请输入名称" v-model="worksType.worksTypeName"></el-input>
</el-col>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="openSetting(worksType)" icon="el-icon-s-tools"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addWorksType(type, worksIndex)" icon="el-icon-plus"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="type.worksTypes.splice(worksIndex, 1)" icon="el-icon-delete"
type="danger"></el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="面向对象" prop="joinCnd">
<div style="display: flex; justify-content: space-between">
<div style="width: 99%">
<el-select clearable filterable placeholder="请选择参加人员范围" style="width: 99%"
v-model="formData.activityGroupId">
<el-option
:label="item.groupName"
:value="item.groupId"
v-for="item in activityGroupList"
:key="item.groupId"
></el-option>
</el-select>
</div>
<div>
<el-button @click="$refs.drawerUserScope.userScopeDialog = true" type="primary">设置
</el-button>
</div>
</div>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item prop="hostUnitIds" label="主办单位">
<el-select v-model="formData.hostUnitIds" clearable filterable multiple
placeholder="请选择主办单位"
style="width: 100%">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="undertakeUnitIds" label="承办单位">
<el-select v-model="formData.undertakeUnitIds" clearable filterable multiple
placeholder="请选择承办单位"
style="width: 100%">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="活动内容" prop="content">
<text-editor v-model="formData.content"></text-editor>
</el-form-item>
<!--<el-form-item label="活动介绍" prop="introduce">
<text-editor v-model="formData.introduce"></text-editor>
</el-form-item>
<el-form-item label="奖励方式" prop="rewardMethod">
<text-editor v-model="formData.rewardMethod"></text-editor>
</el-form-item>-->
<el-form-item label="活动封面">
<file-upload
:upload_number="1"
:value.sync="formData.cover"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval"
></file-upload>
</el-form-item>
<el-form-item label="配色" prop="activityColor">
<el-color-picker v-model="formData.activityColor" show-alpha></el-color-picker>
</el-form-item>
<el-form-item prop="enable" label="状态">
<el-radio-group v-model="formData.enable" size="small">
<el-radio :label="true" border>开启</el-radio>
<el-radio :label="false" border>关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="close">取消</el-button>
<el-button type="primary" v-if="copyTemplateMode || formData.isSubmit!=true" plain @click="doSave">{{ copyTemplateMode ? "另存为" : "保存" }}</el-button>
<el-button type="primary" @click="doSubmit">{{ copyTemplateMode ? "新活动提交" : "确定" }}</el-button>
</el-row>
</div>
</template>
</guava>
<drawer-user-scope v-if="formVisible" :group_id.sync="formData.activityGroupId" @group_change="getActivityGroup"
ref="drawerUserScope"></drawer-user-scope>
<el-dialog v-if="formVisible" :close-on-click-modal="false" :visible.sync="settingDialogVisible" title="更多设置" top="4%" width="40%">
<el-form :model="currentWorksType" label-width="160px" size="small">
<el-form-item label="作品介绍最多字数">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.maxWordCount"></el-input-number>
</el-form-item>
<el-form-item label="允许上传的文件类型" prop="allowFileTypes">
<el-checkbox-group v-model="currentWorksType.allowFileTypes">
<el-checkbox key="jpg" label="jpg">jpg</el-checkbox>
<el-checkbox key="jpeg" label="jpeg">jpeg</el-checkbox>
<el-checkbox key="png" label="png">png</el-checkbox>
<el-checkbox key="mp3" label="mp3">mp3</el-checkbox>
<el-checkbox key="mp4" label="mp4">mp4</el-checkbox>
<el-checkbox key="docx" label="docx">docx</el-checkbox>
<el-checkbox key="xlsx" label="xlsx">xlsx</el-checkbox>
<el-checkbox key="pdf" label="pdf">pdf</el-checkbox>
<el-checkbox key="zip" label="zip">zip</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="允许上传的文件数量" prop="allowFileNum">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.allowFileNum"></el-input-number>
</el-form-item>
<el-form-item label="单个文件大小(kb)" prop="allowSingleFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowSingleFileSize"></el-input-number>
</el-form-item>
<el-form-item label="总文件大小(kb)" prop="allowTotalFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowTotalFileSize"></el-input-number>
</el-form-item>
<!-- <div class="left-span-label">上传图片设置</div>-->
<!-- <el-form-item label="是否上传图片">-->
<!-- <el-radio :label="true" border v-model="currentWorksType.isUploadPhoto">上传</el-radio>-->
<!-- <el-radio :label="false" border v-model="currentWorksType.isUploadPhoto">不上传</el-radio>-->
<!-- </el-form-item>-->
<!-- <template v-if="currentWorksType.isUploadPhoto === true">-->
<!-- <el-form-item label="上传图片数量">-->
<!-- <el-input-number :max="5" :min="1" style="width: 100%" v-model="currentWorksType.photoNum"></el-input-number>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="上传图片格式">-->
<!-- <el-checkbox-group v-model="currentWorksType.photoType">-->
<!-- <el-checkbox border key="jpg" label="jpg">jpg</el-checkbox>-->
<!-- <el-checkbox border key="png" label="png">png</el-checkbox>-->
<!-- <el-checkbox border key="jpeg" label="jpeg">jpeg</el-checkbox>-->
<!-- </el-checkbox-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="每张图片大小">-->
<!-- <el-input placeholder="请填写每张图片大小" type="number" v-model="currentWorksType.photoSize">-->
<!-- <template slot="append">Mb</template>-->
<!-- </el-input>-->
<!-- </el-form-item>-->
<!-- </template>-->
<!-- <div class="left-span-label">上传音/视频、文档设置</div>-->
<!-- <el-form-item label="是否上传音/视频、文档">-->
<!-- <el-radio :label="true" border v-model="currentWorksType.isUploadOther">上传</el-radio>-->
<!-- <el-radio :label="false" border v-model="currentWorksType.isUploadOther">不上传</el-radio>-->
<!-- </el-form-item>-->
<!-- <template v-if="currentWorksType.isUploadOther === true">-->
<!-- <el-form-item label="文件上传数量">-->
<!-- <el-input-number :max="5" :min="1" style="width: 100%" v-model="currentWorksType.otherFileNum"></el-input-number>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="上传文件格式">-->
<!-- <el-checkbox-group v-model="currentWorksType.otherType">-->
<!-- <el-checkbox key="mp3" label="mp3">mp3</el-checkbox>-->
<!-- <el-checkbox key="mp4" label="mp4">mp4</el-checkbox>-->
<!-- <el-checkbox key="docx" label="docx">docx</el-checkbox>-->
<!-- <el-checkbox key="xlsx" label="xlsx">xlsx</el-checkbox>-->
<!-- <el-checkbox key="pdf" label="pdf">pdf</el-checkbox>-->
<!-- </el-checkbox-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="每个文件大小">-->
<!-- <el-input placeholder="请填写每个文件大小" type="number" v-model="currentWorksType.otherSize">-->
<!-- <template slot="append">Mb</template>-->
<!-- </el-input>-->
<!-- </el-form-item>-->
</template>
</el-form>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="settingDialogVisible=false">取消</el-button>
<el-button @click="settingDialogVisible = false" type="primary">确定</el-button>
</el-row>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -406,31 +130,9 @@ layout("/layouts/platform.html"){
activeTab: "activity",
tableHeight: 300,
tableResizeObserver: null,
activityGroupList: [],
dialogFormVisible: false,
formRules: {
name: [{required: true, message: "必填", trigger: ["change", "blur"]}],
activityTime: [{required: true, message: "必填", trigger: ["change", "blur"]}],
content: [{required: true, message: "必填", trigger: ["change", "blur"]}],
subjectTypes: [{required: true, message: "必填", trigger: ["change", "blur"]}],
enable: [{required: true, message: "必填", trigger: ["change", "blur"]}]
},
currentWorksType: {
allowFileTypes: [],
},
settingDialogVisible: false,
colorList: ["#081776", "#31cab1", "#e54242", "#ff8939", "#ffb712", "#1dc47b", "#4495f7", "#ff5e8b", "#5d6e7f"],
pageForm: {
activityTime: []
},
formVisible: false,
copyTemplateMode: false,
copyTemplateName: "",
formOptionsLoaded: false,
formOptionsPromise: null,
unitOptions: [],
clubOptions: [],
}
},
computed: {
@@ -439,53 +141,13 @@ layout("/layouts/platform.html"){
return this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
}
},
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
watch: {
"formData.activityGroupId": {
async handler(newVal, oldVal) {
this.activityGroupList = await this.getActivityGroup()
this.userScopeDialog = false
},
deep: true
}
},
methods: {
isNewPage() {
return window.location.pathname.toLowerCase() === "/platform/activity/workscollection/new"
},
backToManage() {
commonUtil.pjaxPush("/platform/activity/worksCollection/manage")
},
afterSaveSuccess() {
this.formVisible = false
if (this.isNewPage()) {
this.backToManage()
} else {
this.doSearch()
this.$refs.guava.index()
}
},
initNewPage() {
const params = new URLSearchParams(window.location.search)
const mode = params.get("mode")
const id = params.get("id")
if (mode === "edit" && id) {
this.openEdit(id, true)
} else {
this.openAdd()
}
},
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => res.data)
},
dropdownCommand(command) {
const {type, row} = command
if (type === "sendNotice") {
this.sendNotice(row.id)
} else if (type === "edit") {
this.openEdit(row.id)
commonUtil.pjaxPush("/platform/activity/worksCollection/new?mode=update&id=" + row.id)
} else if (type === "setTemplate") {
this.setTemplate(row)
} else if (type === "cancelTemplate") {
@@ -537,212 +199,8 @@ layout("/layouts/platform.html"){
})
})
},
async loadFormOptions() {
if (this.formOptionsLoaded) {
return
}
if (this.formOptionsPromise) {
return this.formOptionsPromise
}
this.formOptionsPromise = Promise.all([
this.getActivityGroup(),
this.getClubsByRole(),
this.$businessTool.listUnit()
]).then(([activityGroupList, clubOptions, units]) => {
this.activityGroupList = activityGroupList
this.clubOptions = clubOptions
this.clubOptions.map((v) => {
v.name = v.clubName
})
this.unitOptions = this.clubOptions.concat(units)
this.formOptionsLoaded = true
}).finally(() => {
this.formOptionsPromise = null
})
return this.formOptionsPromise
},
addType(index) {
let item = {}
this.initData(item)
this.formData.subjectTypes.splice(index + 1, 0, {worksTypes: [item]})
},
openSetting(worksType) {
this.currentWorksType = worksType
this.settingDialogVisible = true
},
addWorksType(type, worksIndex) {
let item = {}
this.initData(item)
type.worksTypes.splice(worksIndex + 1, 0, item)
},
initData(worksType) {
this.$set(worksType, "maxWordCount", worksType.maxWordCount ? worksType.maxWordCount : 100)
this.$set(worksType, "isUploadPhoto", worksType.isUploadPhoto ? worksType.isUploadPhoto : false)
this.$set(worksType, "photoType", worksType.photoType ? worksType.photoType : ["jpg"])
this.$set(worksType, "photoNum", worksType.photoNum ? worksType.photoNum : 1)
this.$set(worksType, "photoSize", worksType.photoSize ? worksType.photoSize : 2)
this.$set(worksType, "isUploadOther", worksType.isUploadOther ? worksType.isUploadOther : false)
this.$set(worksType, "otherType", worksType.otherType ? worksType.otherType : ["mp4", "docx"])
this.$set(worksType, "otherFileNum", worksType.otherFileNum ? worksType.otherFileNum : 1)
this.$set(worksType, "otherSize", worksType.otherSize ? worksType.otherSize : 2)
this.$set(worksType, "allowFileTypes", worksType.allowFileTypes ? worksType.allowFileTypes : [])
},
async openAdd() {
this.copyTemplateMode = false
this.copyTemplateName = ""
await this.loadFormOptions()
this.formVisible = true
this.$refs.guava.edit()
this.$nextTick(() => {
this.formData = {
nbCount: 0,
activityColor: this.colorList[0],
subjectTypes: [{worksTypes: [{}]}],
enable: true
}
this.initData(this.formData.subjectTypes[0].worksTypes[0])
})
},
cleanCopyTemplateData(data) {
if (!data) {
return
}
data.id = null
data.isTemplate = false
data.templateStatus = 0
data.createdBy = null
data.createdAt = null
data.updatedBy = null
data.updatedAt = null
data.delFlag = null
if (Array.isArray(data.subjectTypes)) {
data.subjectTypes = data.subjectTypes.map((subjectType) => {
subjectType.id = null
subjectType.activityId = null
subjectType.createdBy = null
subjectType.createdAt = null
subjectType.updatedBy = null
subjectType.updatedAt = null
subjectType.delFlag = null
if (Array.isArray(subjectType.worksTypes)) {
subjectType.worksTypes = subjectType.worksTypes.map((worksType) => {
worksType.id = null
worksType.subjectId = null
worksType.createdBy = null
worksType.createdAt = null
worksType.updatedBy = null
worksType.updatedAt = null
worksType.delFlag = null
return worksType
})
}
return subjectType
})
}
},
validateCopyTemplateName() {
if (!this.copyTemplateMode) {
return true
}
const currentName = (this.formData.name || "").trim()
const templateName = (this.copyTemplateName || "").trim()
if (currentName && templateName && currentName === templateName) {
this.$message.warning("请修改活动名称,不能与模板名称一致")
return false
}
return true
},
buildSubmitData() {
const data = clone(this.formData)
data.startDateTime = data.activityTime[0]
data.endDateTime = data.activityTime[1]
data.nbStartDateTime = data.nbTime ? data.nbTime[0] : null
data.nbEndDateTime = data.nbTime ? data.nbTime[1] : null
data.type = "1"
if (this.copyTemplateMode) {
this.cleanCopyTemplateData(data)
}
return data
},
async openEdit(id, copyTemplateMode = false) {
this.copyTemplateMode = !!copyTemplateMode
const [resp] = await Promise.all([
this.$axios.post("/platform/activity/worksCollection/manage/findOne", {id}),
this.loadFormOptions()
])
this.formVisible = true
this.formData = resp.data
if (this.copyTemplateMode) {
this.copyTemplateName = this.formData.name
this.cleanCopyTemplateData(this.formData)
} else {
this.copyTemplateName = ""
}
this.$set(this.formData, "activityTime", [this.formData.startDateTime, this.formData.endDateTime])
// this.formData.activityTime = [this.formData.startDateTime, this.formData.endDateTime]
if (this.formData.nbStartDateTime && this.formData.nbEndDateTime) {
// this.formData.nbTime = [this.formData.nbStartDateTime, this.formData.nbEndDateTime]
this.$set(this.formData, "nbTime", [this.formData.nbStartDateTime, this.formData.nbEndDateTime])
}
this.$nextTick(() => {
this.$refs.guava.edit()
})
},
close() {
this.formVisible = false
if (this.isNewPage()) {
this.backToManage()
} else {
this.$refs.guava.index()
}
},
doSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
if (!this.validateCopyTemplateName()) {
return
}
const submitData = this.buildSubmitData()
let loading = this.$loading({
lock: true,
text: "数据正在提交中,请稍后...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)"
})
this.$axios.post("/platform/activity/worksCollection/manage" + (this.copyTemplateMode || !this.formData.id ? "/insert" : "/update"), {data: JSON.stringify(submitData)}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.afterSaveSuccess()
} else {
this.$message.warning(res.msg)
}
loading.close()
})
}
})
},
doSave() {
if (!this.validateCopyTemplateName()) {
return
}
const submitData = this.buildSubmitData()
let loading = this.$loading({
lock: true,
text: "数据正在保存中,请稍后...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)"
})
this.$axios.post("/platform/activity/worksCollection/manage" + (this.copyTemplateMode || !this.formData.id ? "/save" : "/update"), {data: JSON.stringify(submitData)}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.afterSaveSuccess()
} else {
this.$message.warning(res.msg)
}
loading.close()
})
openAdd() {
commonUtil.pjaxPush("/platform/activity/worksCollection/new")
},
doDelete(id) {
const row = this.tableData.find((item) => item.id === id)
@@ -819,29 +277,15 @@ layout("/layouts/platform.html"){
})
})
},
getClubsByRole() {
return this.$axios.post("/platform/club/examine/apply/getClubsByRole").then((resp) => resp.data)
},
},
async created() {
if (!this.isNewPage() && this.canManageTemplate) {
created() {
if (this.canManageTemplate) {
// 有模板管理角色时默认进入活动列表,只展示未设置模板的活动。
this.$set(this.pageForm, "templateStatus", 0)
}
if (this.isNewPage()) {
await this.loadFormOptions()
this.$nextTick(() => {
this.initNewPage()
})
} else {
this.pageData()
this.loadFormOptions()
}
this.pageData()
},
mounted() {
if (this.isNewPage()) {
return
}
const listCardBody = this.$el.querySelector(".works-collection-list-card > .el-card__body")
if (listCardBody && window.ResizeObserver) {
// 查询区换行、页签权限或窗口变化时同步调整表格内部滚动区域。
@@ -0,0 +1,467 @@
const worksCollectionBasicForm = {
template: /*language=HTML*/ `
<div>
<custom-card shadow="never">
<div slot="header">{{pageTitle}}</div>
<el-form :model="formData" ref="formRef" label-width="130px" :rules="formRules">
<el-form-item label="活动主题" prop="name">
<el-input placeholder="请输入活动主题" v-model="formData.name" clearable></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动时间" prop="activityTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.activityTime"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点赞时间" prop="nbTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.nbTime"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="每人每日点赞次数" prop="nbCount">
<el-input-number
:min="0"
placeholder="请输入每人每日点赞次数"
style="width: 100%"
v-model="formData.nbCount">
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="主题类型" prop="subjectTypes">
<el-row class="pb5" v-for="(type,index) in formData.subjectTypes" :key="type.id || index">
<el-col :span="20">
<el-input placeholder="请输入名称" v-model="type.typeName"></el-input>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addType(index)" icon="el-icon-plus" type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button
:disabled="index === 0"
@click="removeType(index)"
icon="el-icon-delete"
type="danger">
</el-button>
</el-col>
<el-col class="pt5 pb5">
<el-row class="pb5" v-for="(worksType,worksIndex) in type.worksTypes"
:key="worksType.id || worksIndex">
<el-col :span="18">
<el-col :span="2">
<span class="el-form-item__label" v-if="worksIndex === 0">作品类型</span>
<span v-else>&nbsp;</span>
</el-col>
<el-col :span="22">
<el-input placeholder="请输入名称" v-model="worksType.worksTypeName"></el-input>
</el-col>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="openSetting(worksType)" icon="el-icon-s-tools"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addWorksType(type, worksIndex)" icon="el-icon-plus"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button :disabled="type.worksTypes.length === 1"
@click="removeWorksType(type, worksIndex)" icon="el-icon-delete"
type="danger"></el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="面向对象" prop="joinCnd">
<div style="display: flex; justify-content: space-between">
<div style="width: 99%">
<el-select clearable filterable placeholder="请选择参加人员范围" style="width: 99%"
v-model="formData.activityGroupId">
<el-option
:label="item.groupName"
:value="item.groupId"
v-for="item in activityGroupList"
:key="item.groupId">
</el-option>
</el-select>
</div>
<div>
<el-button @click="$refs.drawerUserScope.userScopeDialog = true" type="primary">设置</el-button>
</div>
</div>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item prop="hostUnitIds" label="主办单位">
<el-select v-model="formData.hostUnitIds" clearable filterable multiple
placeholder="请选择主办单位" style="width: 100%">
<el-option v-for="item in unitOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="undertakeUnitIds" label="承办单位">
<el-select v-model="formData.undertakeUnitIds" clearable filterable multiple
placeholder="请选择承办单位" style="width: 100%">
<el-option v-for="item in unitOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="活动内容" prop="content">
<text-editor v-model="formData.content"></text-editor>
</el-form-item>
<el-form-item label="活动封面">
<file-upload
:upload_number="1"
:value.sync="formData.cover"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval">
</file-upload>
</el-form-item>
<el-form-item label="配色" prop="activityColor">
<el-color-picker v-model="formData.activityColor" show-alpha></el-color-picker>
</el-form-item>
<el-form-item prop="enable" label="状态">
<el-radio-group v-model="formData.enable" size="small">
<el-radio :label="true" border>开启</el-radio>
<el-radio :label="false" border>关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template slot="footer">
<el-button :disabled="formLoading" @click="backToManage">取消</el-button>
<el-button :loading="formLoading" type="primary"
v-if="copyTemplateMode || formData.isSubmit !== true"
plain @click="doSave">{{copyTemplateMode ? "另存为" : "保存"}}</el-button>
<el-button :loading="formLoading" type="primary" @click="doSubmit">
{{copyTemplateMode ? "新活动提交" : "确定"}}
</el-button>
</template>
</custom-card>
<drawer-user-scope :group_id.sync="formData.activityGroupId" @group_change="refreshActivityGroup"
ref="drawerUserScope"></drawer-user-scope>
<el-dialog :close-on-click-modal="false" :visible.sync="settingDialogVisible"
title="更多设置" top="4%" width="40%">
<el-form :model="currentWorksType" label-width="160px" size="small">
<el-form-item label="作品介绍最多字数">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.maxWordCount"></el-input-number>
</el-form-item>
<el-form-item label="允许上传的文件类型" prop="allowFileTypes">
<el-checkbox-group v-model="currentWorksType.allowFileTypes">
<el-checkbox key="jpg" label="jpg">jpg</el-checkbox>
<el-checkbox key="jpeg" label="jpeg">jpeg</el-checkbox>
<el-checkbox key="png" label="png">png</el-checkbox>
<el-checkbox key="mp3" label="mp3">mp3</el-checkbox>
<el-checkbox key="mp4" label="mp4">mp4</el-checkbox>
<el-checkbox key="docx" label="docx">docx</el-checkbox>
<el-checkbox key="xlsx" label="xlsx">xlsx</el-checkbox>
<el-checkbox key="pdf" label="pdf">pdf</el-checkbox>
<el-checkbox key="zip" label="zip">zip</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="允许上传的文件数量" prop="allowFileNum">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.allowFileNum"></el-input-number>
</el-form-item>
<el-form-item label="单个文件大小(kb)" prop="allowSingleFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowSingleFileSize"></el-input-number>
</el-form-item>
<el-form-item label="总文件大小(kb)" prop="allowTotalFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowTotalFileSize"></el-input-number>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="settingDialogVisible = false">取消</el-button>
<el-button @click="settingDialogVisible = false" type="primary">确定</el-button>
</span>
</el-dialog>
</div>
`,
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
data() {
return {
formData: {
nbCount: 0,
activityColor: "#081776",
subjectTypes: [],
hostUnitIds: [],
undertakeUnitIds: [],
enable: true
},
formLoading: false,
copyTemplateMode: false,
copyTemplateName: "",
activityGroupList: [],
unitOptions: [],
currentWorksType: {allowFileTypes: []},
settingDialogVisible: false,
colorList: ["#081776", "#31cab1", "#e54242", "#ff8939", "#ffb712", "#1dc47b", "#4495f7", "#ff5e8b", "#5d6e7f"],
formRules: {
name: [{required: true, message: "必填", trigger: ["change", "blur"]}],
activityTime: [{required: true, message: "必填", trigger: ["change", "blur"]}],
content: [{required: true, message: "必填", trigger: ["change", "blur"]}],
subjectTypes: [{required: true, message: "必填", trigger: ["change", "blur"]}],
enable: [{required: true, message: "必填", trigger: ["change", "blur"]}]
}
}
},
computed: {
pageTitle() {
if (this.copyTemplateMode) {
return "从模板创建作品征集活动"
}
return this.formData.id ? "编辑作品征集活动" : "新建作品征集活动"
}
},
methods: {
/**
* 初始化独立活动表单。
* URL 参数:无 id 时创建空白活动;mode=update&id=活动ID 时编辑原活动;
* mode=edit&id=模板活动ID 时复制模板。完成后无返回值,通过组件事件通知页面返回。
*/
initData() {
Promise.all([this.loadFormOptions(), this.initForm()]).catch(() => {
this.$message.error("活动表单初始化失败,请稍后重试")
})
},
// 创建新增页面的默认表单数据,并初始化第一组主题和作品类型。
createEmptyFormData() {
const worksType = {}
this.initWorksType(worksType)
return {
nbCount: 0,
activityColor: "#081776",
subjectTypes: [{worksTypes: [worksType]}],
hostUnitIds: [],
undertakeUnitIds: [],
enable: true
}
},
initWorksType(worksType) {
this.$set(worksType, "maxWordCount", worksType.maxWordCount || 100)
this.$set(worksType, "isUploadPhoto", worksType.isUploadPhoto || false)
this.$set(worksType, "photoType", worksType.photoType || ["jpg"])
this.$set(worksType, "photoNum", worksType.photoNum || 1)
this.$set(worksType, "photoSize", worksType.photoSize || 2)
this.$set(worksType, "isUploadOther", worksType.isUploadOther || false)
this.$set(worksType, "otherType", worksType.otherType || ["mp4", "docx"])
this.$set(worksType, "otherFileNum", worksType.otherFileNum || 1)
this.$set(worksType, "otherSize", worksType.otherSize || 2)
this.$set(worksType, "allowFileTypes", worksType.allowFileTypes || [])
},
addType(index) {
const worksType = {}
this.initWorksType(worksType)
this.formData.subjectTypes.splice(index + 1, 0, {worksTypes: [worksType]})
},
removeType(index) {
this.formData.subjectTypes.splice(index, 1)
},
addWorksType(type, worksIndex) {
const worksType = {}
this.initWorksType(worksType)
type.worksTypes.splice(worksIndex + 1, 0, worksType)
},
removeWorksType(type, worksIndex) {
type.worksTypes.splice(worksIndex, 1)
},
openSetting(worksType) {
this.initWorksType(worksType)
this.currentWorksType = worksType
this.settingDialogVisible = true
},
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
.then((res) => res.data || [])
},
getClubsByRole() {
return this.$axios.post("/platform/club/examine/apply/getClubsByRole")
.then((res) => res.data || [])
},
loadFormOptions() {
return Promise.all([
this.getActivityGroup(),
this.getClubsByRole(),
this.$businessTool.listUnit()
]).then(([activityGroupList, clubOptions, units]) => {
this.activityGroupList = activityGroupList
clubOptions.forEach((item) => {
this.$set(item, "name", item.clubName)
})
this.unitOptions = clubOptions.concat(units || [])
})
},
refreshActivityGroup() {
this.getActivityGroup().then((activityGroupList) => {
this.activityGroupList = activityGroupList
})
},
// 模板仅作为新活动的数据来源,清理主表和子表标识以防止更新模板原记录。
cleanCopyTemplateData(data) {
data.id = null
data.isTemplate = false
data.templateStatus = 0
data.createdBy = null
data.createdAt = null
data.updatedBy = null
data.updatedAt = null
data.delFlag = null
;(data.subjectTypes || []).forEach((subjectType) => {
subjectType.id = null
subjectType.activityId = null
subjectType.createdBy = null
subjectType.createdAt = null
subjectType.updatedBy = null
subjectType.updatedAt = null
subjectType.delFlag = null
;(subjectType.worksTypes || []).forEach((worksType) => {
worksType.id = null
worksType.subjectId = null
worksType.createdBy = null
worksType.createdAt = null
worksType.updatedBy = null
worksType.updatedAt = null
worksType.delFlag = null
})
})
},
initForm() {
const mode = GetQueryString("mode")
const id = GetQueryString("id")
this.copyTemplateMode = mode === "edit" && !!id
if (!id) {
this.formData = this.createEmptyFormData()
return Promise.resolve()
}
return this.$axios.post("/platform/activity/worksCollection/manage/findOne", {id})
.then((res) => {
if (res.code !== 0 || !res.data) {
return
}
const data = res.data
if (this.copyTemplateMode) {
this.copyTemplateName = data.name
this.cleanCopyTemplateData(data)
}
this.$set(data, "activityTime", [data.startDateTime, data.endDateTime])
this.$set(data, "nbTime", data.nbStartDateTime && data.nbEndDateTime
? [data.nbStartDateTime, data.nbEndDateTime] : [])
this.$set(data, "hostUnitIds", data.hostUnitIds || [])
this.$set(data, "undertakeUnitIds", data.undertakeUnitIds || [])
if (!data.subjectTypes || !data.subjectTypes.length) {
const worksType = {}
this.initWorksType(worksType)
this.$set(data, "subjectTypes", [{worksTypes: [worksType]}])
}
;(data.subjectTypes || []).forEach((subjectType) => {
this.$set(subjectType, "worksTypes", subjectType.worksTypes || [])
if (!subjectType.worksTypes.length) {
const worksType = {}
this.initWorksType(worksType)
subjectType.worksTypes.push(worksType)
}
subjectType.worksTypes.forEach((worksType) => this.initWorksType(worksType))
})
this.formData = data
})
},
validateCopyTemplateName() {
if (!this.copyTemplateMode) {
return true
}
const currentName = (this.formData.name || "").trim()
const templateName = (this.copyTemplateName || "").trim()
if (currentName && templateName && currentName === templateName) {
this.$message.warning("请修改活动名称,不能与模板名称一致")
return false
}
return true
},
buildSubmitData() {
const data = clone(this.formData)
data.startDateTime = data.activityTime && data.activityTime.length ? data.activityTime[0] : null
data.endDateTime = data.activityTime && data.activityTime.length ? data.activityTime[1] : null
data.nbStartDateTime = data.nbTime && data.nbTime.length ? data.nbTime[0] : null
data.nbEndDateTime = data.nbTime && data.nbTime.length ? data.nbTime[1] : null
data.type = "1"
if (this.copyTemplateMode) {
this.cleanCopyTemplateData(data)
}
return data
},
submitRequest(path) {
if (!this.validateCopyTemplateName()) {
return
}
this.formLoading = true
this.$axios.post("/platform/activity/worksCollection/manage" + path, {
data: JSON.stringify(this.buildSubmitData())
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.backToManage()
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.formLoading = false
})
},
doSave() {
const path = this.copyTemplateMode || !this.formData.id ? "/save" : "/update"
this.submitRequest(path)
},
doSubmit() {
this.$refs.formRef.validate((valid) => {
if (!valid) {
return
}
const path = this.copyTemplateMode || !this.formData.id ? "/insert" : "/update"
this.submitRequest(path)
})
},
backToManage() {
this.$emit("back")
}
}
}
@@ -0,0 +1,35 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<works-collection-basic-form ref="formRef" @back="back"></works-collection-basic-form>
</div>
<script nonce="${cspNonce!}">
<!--#include("basicForm.js"){}#-->
new Vue({
el: "#app",
store,
components: {
"works-collection-basic-form": worksCollectionBasicForm
},
methods: {
back() {
commonUtil.pjaxPush("/platform/activity/worksCollection/manage")
},
initForm() {
this.$refs.formRef.initData()
}
},
mounted() {
this.$nextTick(() => {
this.initForm()
})
}
})
</script>
<!--#
}
#-->
@@ -1,47 +1,474 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app">
<!-- 导航栏 -->
<style>
body {
background-color: #f3f7fd;
}
<van-nav-bar title="作品上传" left-text="返回" left-arrow placeholder
@click-left="historyBack" fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/activity/worksCollection/upload/pageData"
:page_form.sync="pageForm"
ref="tableListRef"
title="name"
@ready="onReady"
>
<template v-slot="{index,row}">
<table-column label="主题类型">{{row.subjectName}}</table-column>
<table-column label="作品类型">{{row.worksName}}</table-column>
<table-column label="姓名">{{row.userName}}</table-column>
<table-column label="上传时间">{{$moment(row.createdAt).format('YYYY-MM-DD')}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
.works-mine-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background-color: #f3f7fd;
}
.works-mine-page .van-nav-bar {
background-color: #fff;
}
.works-mine-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
}
.works-mine-page .van-nav-bar .van-icon,
.works-mine-page .van-nav-bar__text {
color: #397fbd;
}
.works-mine-content {
padding: 12px 12px 0;
}
.works-mine-hero {
position: relative;
height: 134px;
overflow: hidden;
border-radius: 10px;
color: #fff;
background-color: #247fe8;
box-shadow: 0 8px 20px rgba(51, 117, 219, .18);
}
.works-mine-hero__image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.works-mine-hero__copy {
position: relative;
z-index: 1;
display: flex;
width: 66%;
height: 100%;
padding-left: 16px;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
background: linear-gradient(90deg, rgba(22, 112, 232, .98) 0%, rgba(22, 112, 232, .88) 52%, rgba(22, 112, 232, 0) 100%);
}
.works-mine-hero__title {
font-size: 25px;
line-height: 34px;
font-weight: 700;
letter-spacing: 1px;
}
.works-mine-hero__subtitle {
margin-top: 4px;
font-size: 13px;
line-height: 20px;
letter-spacing: .5px;
opacity: .95;
}
.works-mine-sticky {
padding: 10px 0 1px;
background-color: #f3f7fd;
}
.van-sticky--fixed .works-mine-sticky {
padding-right: 12px;
padding-left: 12px;
box-sizing: border-box;
}
.works-mine-search {
margin-bottom: 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.works-mine-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.works-mine-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.works-mine-filter.is-open {
border-radius: 10px 10px 0 0;
}
.works-mine-filter .van-dropdown-menu__bar {
height: 44px;
box-shadow: none;
}
.works-mine-filter .van-dropdown-menu__item {
min-width: 0;
overflow: hidden;
}
.works-mine-filter .van-dropdown-menu__title {
width: auto;
max-width: calc(100% - 24px);
padding: 0 12px 0 0;
overflow: hidden;
color: #66758a;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-mine-filter .van-dropdown-menu__title::after {
right: 0;
}
.works-mine-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
box-sizing: border-box;
}
.works-mine-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.works-mine-card {
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.works-mine-card__cover,
.works-mine-card__cover-state {
display: flex;
width: 100%;
height: 150px;
align-items: center;
justify-content: center;
background-color: #edf4fc;
}
.works-mine-card__cover {
display: block;
object-fit: cover;
}
.works-mine-card__cover-state {
flex-direction: column;
row-gap: 8px;
color: #8da1ba;
font-size: 13px;
}
.works-mine-card__cover-state .van-icon {
font-size: 34px;
}
.works-mine-card__cover-state.is-error {
color: #c27b75;
background-color: #fff3f1;
}
.works-mine-card__body {
padding: 12px 14px 14px;
}
.works-mine-card__title {
overflow: hidden;
color: #303846;
font-size: 15px;
line-height: 22px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-mine-card__activity,
.works-mine-card__time {
margin-top: 4px;
overflow: hidden;
color: #8794a7;
font-size: 12px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-mine-card__meta {
display: flex;
margin-top: 10px;
padding-top: 9px;
flex-wrap: wrap;
gap: 6px;
border-top: 1px solid #edf1f6;
}
.works-mine-card__tag {
display: inline-flex;
padding: 3px 8px;
border-radius: 4px;
color: #397de0;
background-color: #edf5ff;
font-size: 11px;
line-height: 16px;
}
.works-mine-card__tag-label {
margin-right: 4px;
color: #8aa7cf;
}
.works-mine-card__actions {
display: flex;
padding: 12px 14px;
align-items: center;
justify-content: flex-end;
gap: 8px;
border-top: 1px solid #edf1f6;
background-color: #f8fbff;
}
.works-mine-card__action {
display: inline-flex;
min-width: 72px;
height: 32px;
padding: 0 12px;
align-items: center;
justify-content: center;
border: 0;
border-radius: 16px;
color: #397de0;
background-color: #edf5ff;
font-size: 13px;
box-sizing: border-box;
}
.works-mine-card__action .van-icon {
margin-right: 5px;
font-size: 15px;
}
.works-mine-card__action.is-primary {
color: #fff;
background-color: #1989fa;
box-shadow: 0 5px 12px rgba(25, 137, 250, .18);
}
.works-mine-card__action.is-danger {
color: #e45c5c;
background-color: #fff0f0;
}
.works-mine-skeleton {
display: flex;
flex-direction: column;
gap: 10px;
}
.works-mine-skeleton__card {
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.works-mine-skeleton__cover,
.works-mine-skeleton__body .van-skeleton,
.works-mine-skeleton__actions .van-skeleton {
padding: 0;
}
.works-mine-skeleton__cover .van-skeleton__row {
width: 100% !important;
height: 150px;
margin: 0;
border-radius: 0;
}
.works-mine-skeleton__body {
padding: 13px 14px 14px;
}
.works-mine-skeleton__body .van-skeleton__title {
width: 52%;
height: 15px;
margin-bottom: 12px;
}
.works-mine-skeleton__body .van-skeleton__row {
height: 12px;
margin-top: 9px;
}
.works-mine-skeleton__body .van-skeleton__row:last-child {
width: 58%;
}
.works-mine-skeleton__actions {
padding: 12px 14px;
border-top: 1px solid #edf1f6;
background-color: #f8fbff;
}
.works-mine-skeleton__actions .van-skeleton__row {
width: 72%;
height: 32px;
margin: 0 0 0 auto;
border-radius: 16px;
}
.works-mine-empty {
display: flex;
min-height: 260px;
padding: 26px 18px 32px;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
}
.works-mine-empty img {
width: 82%;
max-width: 280px;
height: auto;
object-fit: contain;
}
.works-mine-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.works-mine-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
</style>
<div id="app" v-cloak>
<div class="works-mine-page">
<van-nav-bar title="我的作品" left-text="返回" left-arrow fixed placeholder @click-left="handlePageBack"></van-nav-bar>
<div class="works-mine-content">
<section class="works-mine-hero">
<img class="works-mine-hero__image" src="/assets/mobile/img/activity/hero/brand-hero-v2.png" alt="我的作品插画">
<div class="works-mine-hero__copy">
<div class="works-mine-hero__title">我的作品</div>
<div class="works-mine-hero__subtitle">作品征集 · 提交记录</div>
</div>
</section>
<van-sticky :offset-top="46">
<div class="works-mine-sticky">
<van-search v-model="pageForm.searchKeyword" class="works-mine-search" placeholder="搜索作品或征集活动"
shape="round" clearable @search="doSearch" @clear="doSearch"></van-search>
<van-dropdown-menu class="works-mine-filter" :class="{'is-open': filterOpened}" active-color="#1989fa">
<year-van-dropdown-item :num="5" v-model="pageForm.year" @change="doSearch"
@open="onFilterOpen" @close="onFilterClose"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.activityType" :options="typeOptions" :multiple="false"
@change="doSearch" @open="onFilterOpen" @close="onFilterClose"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<div v-if="skeletonLoading" class="works-mine-skeleton">
<div v-for="index in 3" :key="'mine-skeleton-' + index" class="works-mine-skeleton__card">
<van-skeleton class="works-mine-skeleton__cover" :row="1" animated></van-skeleton>
<div class="works-mine-skeleton__body">
<van-skeleton title :row="3" animated></van-skeleton>
</div>
<div class="works-mine-skeleton__actions">
<van-skeleton :row="1" animated></van-skeleton>
</div>
</div>
</div>
<div class="action-btn" @click="onEdit(row)">
<i class="fa fa-edit"></i>
<span>编辑</span>
</div>
<div class="action-btn delete" @click="onDelete(row)">
<i class="fa fa-trash"></i>
<span>删除</span>
</div>
</template>
</table-list>
<van-pull-refresh v-show="!skeletonLoading" v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" :immediate-check="false" @load="onLoad">
<div class="works-mine-list">
<article v-for="row in tableData" :key="row.id" class="works-mine-card">
<img v-if="hasCover(row) && !imageLoadErrors[row.id]" class="works-mine-card__cover"
:src="row.cover" :alt="row.activityName" @error="handleImageError(row)">
<div v-else class="works-mine-card__cover-state" :class="{'is-error': hasCover(row)}">
<van-icon name="photo-o"></van-icon>
<span>{{hasCover(row) ? '图片加载失败' : '暂无图片'}}</span>
</div>
<div class="works-mine-card__body">
<div class="works-mine-card__title">{{row.name}}</div>
<div class="works-mine-card__activity">所属活动:{{row.activityName || '暂无活动信息'}}</div>
<div class="works-mine-card__time" v-if="row.createdAt">
提交时间:{{$moment(row.createdAt).format('YYYY-MM-DD HH:mm')}}
</div>
<div class="works-mine-card__meta" v-if="row.subjectName || row.worksName">
<span class="works-mine-card__tag" v-if="row.subjectName">
<span class="works-mine-card__tag-label">主题</span>{{row.subjectName}}
</span>
<span class="works-mine-card__tag" v-if="row.worksName">
<span class="works-mine-card__tag-label">类型</span>{{row.worksName}}
</span>
</div>
</div>
<div class="works-mine-card__actions">
<button type="button" class="works-mine-card__action" @click="onView(row)">
<van-icon name="eye-o"></van-icon>查看
</button>
<button type="button" class="works-mine-card__action is-primary" @click="onEdit(row)">
<van-icon name="edit"></van-icon>编辑
</button>
<button type="button" class="works-mine-card__action is-danger" @click="onDelete(row)">
<van-icon name="delete-o"></van-icon>删除
</button>
</div>
</article>
</div>
</van-list>
<div v-if="!loading && tableData.length === 0" class="works-mine-empty">
<img src="/assets/mobile/img/activity/culture-empty.png" alt="作品记录空状态插画">
<div class="works-mine-empty__title">暂无作品记录</div>
<div class="works-mine-empty__hint">当前筛选条件下暂时没有已提交作品</div>
</div>
</van-pull-refresh>
</div>
</div>
<van-dialog title="删除确认" message="确认删除当前作品吗?" :value="deleteConfirmVisible"
:close-on-click-overlay="false" show-cancel-button cancel-button-text="取消" confirm-button-text="确认删除"
@cancel="closeDeleteConfirm" @confirm="confirmDelete"></van-dialog>
<apply-form ref="applyFormRef"></apply-form>
<info ref="infoRef"></info>
</div>
@@ -51,22 +478,59 @@ layout("/layouts/platform_h5.html"){
new Vue({
el: "#app",
store,
components: {
'apply-form': applyForm,
'info': INFO,
},
data() {
return {
tableData: [],
loading: false,
skeletonLoading: true,
finished: false,
refreshing: false,
imageLoadErrors: {},
filterOpened: false,
deleteConfirmVisible: false,
deleteLoading: false,
pendingDeleteRow: {},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
year: new Date().getFullYear(),
activityType: 1,
},
typeOptions: [
{text: '全部', value: 1},
{text: '征集中', value: 2},
{text: '已结束', value: 3},
],
historyLayerPageKey: 'works-collection-mine-h5',
historyLayerUnsubscribe: null,
}
},
components: {
'apply-form': applyForm,
'info': INFO,
},
methods: {
hasCover(row) {
return !!(row && row.cover && String(row.cover).trim())
},
handleImageError(row) {
this.$set(this.imageLoadErrors, row.id, true)
},
onFilterOpen() {
this.$set(this, 'filterOpened', true)
},
onFilterClose() {
this.$set(this, 'filterOpened', false)
},
handlePageBack() {
if (window.h5HistoryLayerManager.stack.length) {
window.h5HistoryLayerManager.close()
return
}
this.historyBack()
},
onView(row) {
this.$refs.infoRef.onOpen(row)
},
@@ -78,30 +542,89 @@ layout("/layouts/platform_h5.html"){
this.$refs.applyFormRef.onOpenEdit(row)
},
onDelete(row) {
this.$dialog.confirm({
title: '温馨提示',
message: '您确定要删除吗?',
}).then(() => {
this.$axios.post("/platform/activity/worksCollection/upload/delete", {id:row.id}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
// on cancel
});
this.$set(this, 'pendingDeleteRow', row)
window.h5HistoryLayerManager.open('works-collection-mine-delete-confirm')
},
onReady() {
this.doSearch()
closeDeleteConfirm() {
window.h5HistoryLayerManager.close('works-collection-mine-delete-confirm')
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
confirmDelete() {
const row = this.pendingDeleteRow
window.h5HistoryLayerManager.close('works-collection-mine-delete-confirm', () => {
this.deleteRow(row)
})
},
// 删除接口接收作品 id,成功后重置分页列表;接口返回 Result JSON。
deleteRow(row) {
if (!row || !row.id || this.deleteLoading) return
this.$set(this, 'deleteLoading', true)
this.$axios.post('/platform/activity/worksCollection/upload/delete', {id: row.id}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || '删除成功')
this.loadWorks(true)
}
}).finally(() => {
this.$set(this, 'deleteLoading', false)
})
},
onLoad() {
if (!this.finished) this.loadWorks(false)
},
onRefresh() {
this.loadWorks(true)
},
/**
* 加载当前用户作品分页数据。
* reset 为 true 时从第一页重新查询;接口返回 Paginationlist 为作品数组、totalCount 为总数。
*/
loadWorks(reset) {
const showSkeleton = !!reset
if (reset) {
this.$set(this, 'skeletonLoading', true)
this.$set(this.pageForm, 'pageNumber', 1)
this.$set(this.pageForm, 'totalCount', 0)
this.$set(this, 'tableData', [])
this.$set(this, 'finished', false)
this.$set(this, 'imageLoadErrors', {})
}
this.$set(this, 'loading', true)
this.$axios.post('/platform/activity/worksCollection/upload/pageData', this.pageForm, {
h5SkeletonLoading: showSkeleton
}).then((res) => {
if (res.code === 0) {
const list = res.data && res.data.list ? res.data.list : []
this.$set(this, 'tableData', this.pageForm.pageNumber === 1 ? list : this.tableData.concat(list))
this.$set(this.pageForm, 'totalCount', res.data && res.data.totalCount ? res.data.totalCount : 0)
this.$set(this, 'finished', this.tableData.length >= this.pageForm.totalCount)
if (!this.finished) {
this.$set(this.pageForm, 'pageNumber', this.pageForm.pageNumber + 1)
}
}
}).finally(() => {
this.$set(this, 'loading', false)
this.$set(this, 'refreshing', false)
if (showSkeleton) this.$set(this, 'skeletonLoading', false)
})
},
doSearch() {
this.loadWorks(true)
},
syncHistoryLayers(stack) {
this.$set(this, 'deleteConfirmVisible', stack.includes('works-collection-mine-delete-confirm'))
}
},
created() {
window.h5HistoryLayerManager.register(this.historyLayerPageKey, (stack) => {
this.syncHistoryLayers(stack)
})
this.$set(this, 'historyLayerUnsubscribe', window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayers(stack)
}))
this.loadWorks(true)
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
}
})
</script>
@@ -2,63 +2,189 @@ const INFO = {
template: /*language=HTML*/
`
<div>
<van-action-sheet v-model="visible" :style="{ 'background-color': '#F7F8FA' }" title="报名信息">
<div class="detail-container">
<van-cell-group>
<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.name }}
</van-cell>
<van-cell title="活动主题">
{{ viewData.activityName }}
</van-cell>
<van-cell title="主题类型">
{{ viewData.subjectName }}
</van-cell>
<van-cell title="作品类型">
{{ viewData.worksName }}
</van-cell>
<van-cell title="作品描述" class="direction-column-cell">
{{ viewData.description }}
</van-cell>
<van-cell title="作品附件" class="direction-column-cell">
<file-preview :files="viewData.files" complete_result></file-preview>
</van-cell>
</van-cell-group>
<van-popup class="works-detail-popup" :style="{height: '100%', width: '100%'}" position="right"
safe-area-inset-bottom :close-on-click-overlay="false" v-model="visible">
<div class="works-detail-page">
<van-sticky>
<van-nav-bar title="作品详情" left-text="返回" left-arrow @click-left="closeInfo"></van-nav-bar>
</van-sticky>
<div class="works-detail-scroll">
<section class="works-detail-card">
<h2 class="works-detail-card__title">基础信息</h2>
<van-cell-group :border="false">
<van-cell title="姓名" :value="viewData.userName || '-' "></van-cell>
<van-cell title="工号" :value="viewData.loginName || '-' "></van-cell>
<van-cell title="单位" :value="viewData.unitName || '-' "></van-cell>
<van-cell title="分工会" :value="viewData.unionName || '-' "></van-cell>
</van-cell-group>
</section>
<section class="works-detail-card">
<h2 class="works-detail-card__title">作品信息</h2>
<van-cell-group :border="false">
<van-cell title="所属活动" :value="viewData.activityName || '-' "></van-cell>
<van-cell title="主题类型" :value="viewData.subjectName || '-' "></van-cell>
<van-cell title="作品类型" :value="viewData.worksName || '-' "></van-cell>
<van-cell title="作品名称" :value="viewData.name || '-' "></van-cell>
</van-cell-group>
</section>
<section class="works-detail-card">
<h2 class="works-detail-card__title">作品描述</h2>
<div class="works-detail-description">{{viewData.description || '暂无作品描述'}}</div>
</section>
<section class="works-detail-card">
<h2 class="works-detail-card__title">作品附件</h2>
<div v-if="viewData.files && viewData.files.length" class="works-detail-files">
<file-preview :files="viewData.files" complete_result></file-preview>
</div>
<div v-else class="works-detail-empty">暂无作品附件</div>
</section>
</div>
</div>
</van-action-sheet>
</van-popup>
</div>
`,
style: /*language=CSS*/ `
/deep/ .works-detail-popup,
/deep/ .works-detail-page {
background-color: #f3f7fd;
}
/deep/ .works-detail-page {
min-height: 100vh;
}
/deep/ .works-detail-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
}
/deep/ .works-detail-page .van-nav-bar .van-icon,
/deep/ .works-detail-page .van-nav-bar__text {
color: #397fbd;
}
/deep/ .works-detail-scroll {
height: calc(100vh - 46px);
padding: 12px 12px calc(24px + env(safe-area-inset-bottom));
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
}
/deep/ .works-detail-card {
margin-top: 12px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
/deep/ .works-detail-scroll > .works-detail-card:first-child {
margin-top: 0;
}
/deep/ .works-detail-card__title {
position: relative;
margin: 0;
padding: 13px 14px 9px 22px;
color: #354255;
font-size: 15px;
line-height: 21px;
font-weight: 600;
}
/deep/ .works-detail-card__title::before {
position: absolute;
top: 16px;
bottom: 12px;
left: 12px;
width: 2px;
border-radius: 2px;
background-color: #1989fa;
content: "";
}
/deep/ .works-detail-card .van-cell {
padding: 10px 14px;
color: #596779;
}
/deep/ .works-detail-card .van-cell__title {
flex: 0 0 82px;
font-size: 14px;
}
/deep/ .works-detail-card .van-cell__value {
color: #4f5d70;
font-size: 14px;
word-break: break-word;
}
/deep/ .works-detail-description,
/deep/ .works-detail-files,
/deep/ .works-detail-empty {
padding: 4px 14px 16px;
color: #536174;
font-size: 14px;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-word;
}
/deep/ .works-detail-empty {
color: #9aa7b8;
}
`,
data() {
return {
viewData: {},
visible: false,
historyLayerUnsubscribe: null,
}
},
methods: {
/**
* 查询并打开作品详情。
* row 需传作品 id;接口返回作品、活动、主题、类型及附件信息的 Result JSON。
*/
onOpen(row) {
this.$set(this, "viewData", Object.assign({}, row, {files: this.normalizeFiles(row.files)}))
window.h5HistoryLayerManager.open("works-collection-mine-info")
this.$axios.post("/platform/activity/worksCollection/common/findOne", {id: row.id}).then((res) => {
if (res.code === 0) {
this.viewData = res.data
try {
this.viewData.files = JSON.parse(this.viewData.files)
} catch (err) {
}
const viewData = res.data || {}
viewData.files = this.normalizeFiles(viewData.files)
this.$set(this, "viewData", viewData)
}
})
this.visible = true
},
normalizeFiles(files) {
if (Array.isArray(files)) return files
if (!files) return []
try {
const result = JSON.parse(files)
return Array.isArray(result) ? result : []
} catch (err) {
return []
}
},
closeInfo() {
window.h5HistoryLayerManager.close("works-collection-mine-info")
},
syncHistoryLayers(stack) {
this.$set(this, "visible", stack.includes("works-collection-mine-info"))
}
},
created() {
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayers(stack)
}))
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
}
}
@@ -1,66 +1,495 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app">
<!-- 导航栏 -->
<style>
body {
background-color: #f3f7fd;
}
<van-nav-bar title="作品阅览" left-text="返回" left-arrow placeholder
@click-left="historyBack" fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
:reverse-color="false"
:show-action="false"
@search="doSearch"
input-align="left"
placeholder="请输入姓名/工号搜索"
v-model="pageForm.searchKeyword"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.activityId" :options="activityOptions"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.pageOrderName"
@change="doSearch">
<van-tab title="按上传时间正序排序" name="createdAt"></van-tab>
<van-tab title="按点赞多少倒序排序" name="num"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/activity/worksCollection/read/h5/pageData"
:page_form.sync="pageForm"
ref="tableListRef"
title="name"
@ready="onReady"
>
<template v-slot="{index,row}">
<table-column label="主题类型">{{row.subjectName}}</table-column>
<table-column label="作品类型">{{row.worksName}}</table-column>
<table-column label="姓名">{{row.userName}}</table-column>
<table-column label="上传时间">{{$moment(row.createdAt).format('YYYY-MM-DD')}}</table-column>
<table-column label="点赞数">{{row.num}}</table-column>
<table-column label="附件">
<template v-if="row.files">
<file-preview :files="JSON.parse(row.files)" complete_result></file-preview>
</template>
</table-column>
.works-read-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background-color: #f3f7fd;
}
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
.works-read-page .van-nav-bar {
background-color: #fff;
}
.works-read-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.works-read-page .van-nav-bar .van-icon,
.works-read-page .van-nav-bar__text {
color: #1989fa;
}
.works-read-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background-color: #f3f7fd;
}
.works-read-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.works-read-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.works-read-search .van-field__control {
color: #263548;
font-size: 14px;
}
.works-read-search .van-field__control::placeholder {
color: #a7b0bf;
}
.works-read-filter {
margin: 0 0 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.works-read-filter.is-open {
border-radius: 10px 10px 0 0;
}
.works-read-filter .van-dropdown-menu__bar {
height: 44px;
box-shadow: none;
}
.works-read-filter .van-dropdown-menu__item {
min-width: 0;
overflow: hidden;
}
.works-read-filter .van-dropdown-menu__title {
width: auto;
max-width: calc(100% - 24px);
padding: 0 12px 0 0;
overflow: hidden;
color: #66758a;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-read-filter .van-dropdown-menu__title::after {
right: 0;
}
.works-read-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
box-sizing: border-box;
}
.works-read-content {
padding: 0 12px;
}
.works-read-list .empty-state {
display: none;
}
.works-read-skeleton {
padding-top: 12px;
}
.works-read-skeleton__card {
margin-bottom: 12px;
padding: 14px;
overflow: hidden;
border: 1px solid #eef1f6;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
box-sizing: border-box;
}
.works-read-skeleton__card .van-skeleton {
padding: 0;
}
.works-read-skeleton__card .van-skeleton__title {
width: 42%;
height: 16px;
margin-bottom: 13px;
}
.works-read-skeleton__card .van-skeleton__row {
height: 12px;
margin-top: 10px;
}
.works-read-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 72%;
}
.works-read-skeleton__card .van-skeleton__row:last-child {
width: 48%;
height: 28px;
margin-top: 16px;
border-radius: 14px;
}
.works-read-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.works-read-empty img {
display: block;
width: 86%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.works-read-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.works-read-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.works-read-card-header {
display: flex;
margin-bottom: 7px;
align-items: flex-start;
justify-content: space-between;
}
.works-read-card-heading {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.works-read-card-title {
overflow: hidden;
color: #253247;
font-size: 15px;
line-height: 22px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-read-card-activity {
margin-top: 1px;
overflow: hidden;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-read-like-tag {
display: inline-flex;
min-height: 24px;
padding: 3px 9px;
align-items: center;
flex: none;
border: 0;
border-radius: 12px;
color: #397de0;
background-color: #edf5ff;
font-size: 12px;
line-height: 18px;
box-sizing: border-box;
}
.works-read-like-tag .van-icon {
margin-right: 4px;
font-size: 14px;
}
.works-read-files-row {
width: 100%;
min-width: 0;
margin-top: 8px;
padding-top: 10px;
border-top: 1px solid #edf1f6;
box-sizing: border-box;
}
.works-read-files-label {
display: flex;
margin-bottom: 7px;
align-items: center;
justify-content: space-between;
gap: 10px;
color: #98a3b3;
font-size: 12px;
line-height: 18px;
}
.works-read-files-hint {
flex: none;
color: #7893af;
font-size: 11px;
white-space: nowrap;
}
.works-read-files-scroll {
width: 100%;
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.works-read-files-scroll::-webkit-scrollbar {
display: none;
}
.works-read-list .works-read-files-scroll .file-preview-container {
display: flex !important;
width: max-content;
min-width: 100%;
padding: 0;
grid-template-columns: none !important;
gap: 8px;
box-sizing: border-box;
}
.works-read-list .works-read-files-scroll .file-preview-container .file-item {
width: 88px;
height: 88px;
flex: 0 0 88px;
aspect-ratio: auto;
}
.works-read-action-icon {
font-size: 15px;
}
.works-read-like-action-wrap {
position: relative;
display: flex;
overflow: visible;
}
.works-read-like-hearts {
position: absolute;
z-index: 3;
bottom: 24px;
left: 50%;
width: 1px;
height: 1px;
pointer-events: none;
}
.works-read-like-heart {
position: absolute;
bottom: 0;
left: 0;
color: #ee3f4d;
font-size: 18px;
line-height: 1;
opacity: 0;
text-shadow: 0 3px 8px rgba(238, 63, 77, .32);
animation: works-read-heart-bubble 1s ease-out forwards;
}
.works-read-like-heart.is-heart-1 {
--heart-x: -26px;
--heart-y: -62px;
font-size: 15px;
animation-delay: 0s;
}
.works-read-like-heart.is-heart-2 {
--heart-x: -11px;
--heart-y: -82px;
font-size: 21px;
animation-delay: .08s;
}
.works-read-like-heart.is-heart-3 {
--heart-x: 7px;
--heart-y: -68px;
font-size: 14px;
animation-delay: .16s;
}
.works-read-like-heart.is-heart-4 {
--heart-x: 24px;
--heart-y: -88px;
font-size: 18px;
animation-delay: .24s;
}
.works-read-like-heart.is-heart-5 {
--heart-x: -18px;
--heart-y: -102px;
font-size: 13px;
animation-delay: .32s;
}
.works-read-like-heart.is-heart-6 {
--heart-x: 15px;
--heart-y: -108px;
font-size: 16px;
animation-delay: .4s;
}
@keyframes works-read-heart-bubble {
0% {
opacity: 0;
transform: translate(-50%, 5px) scale(.35);
}
20% {
opacity: 1;
transform: translate(-50%, -8px) scale(1.15);
}
62% {
opacity: 1;
transform: translate(-50%, -42px) scale(1);
}
100% {
opacity: 0;
transform: translate(calc(-50% + var(--heart-x)), var(--heart-y)) scale(.72);
}
}
@media (max-width: 350px) {
.works-read-content,
.works-read-sticky {
padding-right: 10px;
padding-left: 10px;
}
}
</style>
<div id="app" v-cloak>
<div class="works-read-page">
<van-nav-bar title="作品阅览" left-text="返回" left-arrow placeholder fixed
@click-left="handlePageBack"></van-nav-bar>
<van-sticky :offset-top="46">
<div class="works-read-sticky">
<van-search v-model="pageForm.searchKeyword" class="works-read-search"
placeholder="请输入姓名或工号搜索" shape="round" clearable
@search="doSearch" @clear="doSearch"></van-search>
<van-dropdown-menu class="works-read-filter" :class="{'is-open': filterOpened}" active-color="#1989fa">
<van-dropdown-item v-model="pageForm.activityId" :options="activityOptions" :multiple="false"
@change="doSearch" @open="onFilterOpen" @close="onFilterClose"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.pageOrderName" :options="orderOptions" :multiple="false"
@change="doSearch" @open="onFilterOpen" @close="onFilterClose"></van-dropdown-item>
</van-dropdown-menu>
</div>
<template v-if="row.nbCount>0">
<div class="action-btn" @click="onLike(row)" v-if="!row.isThisLike">
<i class="fa fa-thumbs-o-up"></i>
<span>点赞</span>
</van-sticky>
<main class="works-read-content">
<div class="works-read-list">
<div v-if="skeletonLoading" class="works-read-skeleton">
<div v-for="index in 3" :key="'read-skeleton-' + index" class="works-read-skeleton__card">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<div class="action-btn" @click="onDeleteLike(row)" v-if="row.isThisLike">
<i class="fa fa-thumbs-up"></i>
<span>取消点赞</span>
<div v-if="!skeletonLoading && pageForm.totalCount === 0" class="works-read-empty">
<img src="/assets/mobile/img/activity/culture-empty.png" alt="暂无作品记录插画">
<div class="works-read-empty__title">暂无作品记录</div>
<div class="works-read-empty__hint">当前筛选条件下暂时没有可阅览作品</div>
</div>
</template>
</template>
</table-list>
<table-list api="/platform/activity/worksCollection/read/h5/pageData"
:page_form.sync="pageForm" ref="tableListRef" title="name" v-show="!skeletonLoading"
:show_loading_overlay="false" @ready="onReady" @loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="works-read-card-header">
<div class="works-read-card-heading">
<div class="works-read-card-title">{{row.name || '未命名作品'}}</div>
<div class="works-read-card-activity">{{row.activityName || '暂无活动信息'}}</div>
</div>
<div class="works-read-like-tag">
<van-icon name="good-job-o"></van-icon>{{row.num || 0}}
</div>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="作品类型">{{row.worksName || '--'}}</table-column>
<table-column label="作者">{{row.userName || '--'}}</table-column>
<table-column label="工号">{{row.loginName || '--'}}</table-column>
<table-column label="上传时间">{{$moment(row.createdAt).format('YYYY-MM-DD')}}</table-column>
<div v-if="hasListFiles(row.files)" class="works-read-files-row">
<div class="works-read-files-label">
<span>上传附件</span>
<span v-if="getAttachmentCount(row.files) > 1" class="works-read-files-hint">
共{{getAttachmentCount(row.files)}}个附件 · 左滑查看更多
</span>
</div>
<div class="works-read-files-scroll">
<file-preview :files="row.files" complete_result></file-preview>
</div>
</div>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<van-icon class="works-read-action-icon" name="eye-o"></van-icon>
<span>查看</span>
</div>
<div v-if="row.nbCount > 0" class="works-read-like-action-wrap">
<div class="action-btn" v-if="!row.isThisLike" @click="onLike(row)">
<van-icon class="works-read-action-icon" name="good-job-o"></van-icon>
<span>点赞</span>
</div>
<div class="action-btn delete" v-else @click="onDeleteLike(row)">
<van-icon class="works-read-action-icon" name="good-job"></van-icon>
<span>取消点赞</span>
</div>
<span v-if="likeHeartEffects[row.id]"
:key="'like-heart-' + row.id + '-' + (likeHeartEffectKeys[row.id] || 0)"
class="works-read-like-hearts" aria-hidden="true">
<span v-for="heartIndex in 6" :key="heartIndex"
class="works-read-like-heart" :class="'is-heart-' + heartIndex"></span>
</span>
</div>
</template>
</table-list>
</div>
</main>
</div>
<info ref="infoRef"></info>
</div>
@@ -69,6 +498,9 @@ layout("/layouts/platform_h5.html"){
new Vue({
el: "#app",
store,
components: {
'info': INFO,
},
data() {
return {
pageForm: {
@@ -77,79 +509,148 @@ layout("/layouts/platform_h5.html"){
totalCount: 0,
searchKeyword: '',
pageOrderName: 'createdAt',
pageOrderNameText: '',
activityId: '',
year: new Date().getFullYear(),
},
activityOptions: []
activityOptions: [],
orderOptions: [
{text: '按上传时间排序', value: 'createdAt'},
{text: '按点赞数量排序', value: 'num'},
],
filterOpened: false,
likeLoading: false,
likeHeartEffects: {},
likeHeartEffectKeys: {},
likeHeartEffectTimers: {},
skeletonLoading: true,
historyLayerPageKey: 'works-collection-read-h5',
}
},
components: {
'info': INFO,
},
methods: {
// state.initial 表示查询前列表为空,仅在首次或重置查询时显示整组卡片骨架。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, 'skeletonLoading', state.loading)
},
// 判断列表附件是否包含有效记录;files 支持上传组件保存的 JSON 字符串或数组。
hasListFiles(files) {
if (Array.isArray(files)) return files.length > 0
if (!files) return false
try {
const fileList = JSON.parse(files)
return Array.isArray(fileList) && fileList.length > 0
} catch (err) {
return false
}
},
// 获取当前作品的全部附件数量;files 支持 JSON 字符串或数组,返回 Number 类型。
getAttachmentCount(files) {
if (Array.isArray(files)) return files.length
if (!files) return 0
try {
const fileList = JSON.parse(files)
return Array.isArray(fileList) ? fileList.length : 0
} catch (err) {
return 0
}
},
onLike(row) {
this.$dialog.confirm({
title: '温馨提示',
message: '您确定要点赞吗?',
}).then(() => {
this.$axios.post("/platform/activity/worksCollection/read/h5/doLike", {uploadId: row.id}).then((res) => {
if (res.code === 0) {
row.num = row.num + 1
row.isThisLike = true
this.$toast.success(res.msg)
}
})
}).catch(() => {
// on cancel
});
this.executeLikeAction(row, 'like')
},
onDeleteLike(row) {
this.$dialog.confirm({
title: '温馨提示',
message: '您确定要取消点赞吗?',
}).then(() => {
this.$axios.post("/platform/activity/worksCollection/read/h5/doDeleteLike", {uploadId: row.id}).then((res) => {
if (res.code === 0) {
row.num = row.num - 1
row.isThisLike = false
this.$toast.success(res.msg)
}
})
}).catch(() => {
// on cancel
});
this.executeLikeAction(row, 'cancel')
},
/**
* 执行作品点赞或取消点赞。
* row 需包含作品 idaction 传 like 或 cancel;接口返回 Result JSON,方法无返回值。
*/
executeLikeAction(row, action) {
if (!row || !row.id || this.likeLoading) return
const isCancel = action === 'cancel'
const url = isCancel
? '/platform/activity/worksCollection/read/h5/doDeleteLike'
: '/platform/activity/worksCollection/read/h5/doLike'
this.$set(this, 'likeLoading', true)
this.$axios.post(url, {uploadId: row.id}).then((res) => {
if (res.code === 0) {
const currentNum = Number(row.num) || 0
this.$set(row, 'num', isCancel ? Math.max(currentNum - 1, 0) : currentNum + 1)
this.$set(row, 'isThisLike', !isCancel)
if (!isCancel) this.playLikeHeart(row.id)
}
}).finally(() => {
this.$set(this, 'likeLoading', false)
})
},
// 点赞成功后为当前作品播放多颗红色爱心错峰上浮动画,重复触发时重新开始计时。
playLikeHeart(rowId) {
if (!rowId) return
if (this.likeHeartEffectTimers[rowId]) {
clearTimeout(this.likeHeartEffectTimers[rowId])
}
this.$set(this.likeHeartEffects, rowId, false)
this.$set(this.likeHeartEffectKeys, rowId, (this.likeHeartEffectKeys[rowId] || 0) + 1)
this.$nextTick(() => {
this.$set(this.likeHeartEffects, rowId, true)
const timer = setTimeout(() => {
this.$set(this.likeHeartEffects, rowId, false)
this.$delete(this.likeHeartEffectTimers, rowId)
}, 1400)
this.$set(this.likeHeartEffectTimers, rowId, timer)
})
},
onView(row) {
this.$refs.infoRef.onOpen(row)
},
async listActivity() {
const res = await this.$axios.post("/platform/activity/worksCollection/common/listActivity")
if (res.code === 0) {
res.data.forEach(v => {
v.text = v.name
v.value = v.id
})
this.activityOptions = res.data
if (this.activityOptions.length > 0) {
this.pageForm.activityId = this.activityOptions[0].id
}
}
onFilterOpen() {
this.$set(this, 'filterOpened', true)
},
async onReady() {
await this.listActivity()
this.doSearch()
onFilterClose() {
this.$set(this, 'filterOpened', false)
},
handlePageBack() {
if (window.h5HistoryLayerManager.stack.length) {
window.h5HistoryLayerManager.close()
return
}
this.historyBack()
},
// 活动接口接收年度,返回 id、name 组成的活动列表;完成后触发首次作品查询。
listActivity() {
return this.$axios.post('/platform/activity/worksCollection/common/listActivity', {
year: this.pageForm.year
}, {
h5SkeletonLoading: true
}).then((res) => {
if (res.code === 0) {
const options = (res.data || []).map((item) => {
return {text: item.name, value: item.id}
})
this.$set(this, 'activityOptions', options)
this.$set(this.pageForm, 'activityId', options.length > 0 ? options[0].value : '')
}
})
},
onReady() {
this.listActivity().then(() => {
this.doSearch()
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$set(this.pageForm, 'pageNumber', 1)
this.$set(this.pageForm, 'totalCount', 0)
this.$refs.tableListRef.doSearch()
})
},
}
},
created() {
window.h5HistoryLayerManager.register(this.historyLayerPageKey, null)
},
beforeDestroy() {
Object.keys(this.likeHeartEffectTimers).forEach((rowId) => {
clearTimeout(this.likeHeartEffectTimers[rowId])
})
window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
}
})
</script>
@@ -3,189 +3,539 @@ const applyForm = {
/*language=HTML*/
`
<div>
<van-action-sheet v-model="visible" :style="{ 'background-color': '#F7F8FA' }" title="报名信息">
<van-form ref="formRef" class="form-container">
<van-cell-group title="基础信息" class="form-section">
<van-field label="姓名" readonly v-model="formData.username"></van-field>
<van-field label="工号" readonly v-model="formData.loginname"></van-field>
<van-field label="所在单位" readonly v-model="formData.unitName"></van-field>
<van-field label="所属工会" readonly v-model="formData.unionName"></van-field>
<van-field label="性别" readonly v-model="formData.sex"></van-field>
</van-cell-group>
<van-cell-group title="报名信息" class="form-section">
<van-field label="主题类型"
required
:rules="[{ required: true, message: '请选择主题类型' }]"
readonly
is-link
@click="showSubjectPicker = true"
placeholder="请选择主题类型"
name="subjectName"
v-model="formData.subjectName">
</van-field>
<van-popup v-model="showSubjectPicker" position="bottom">
<van-picker
show-toolbar
:columns="subjectTypesOptions.map(v=>v.typeName)"
@confirm="onSubjectConfirm"
@cancel="showSubjectPicker=false"
></van-picker>
</van-popup>
<van-popup class="works-apply-popup" :style="{height: '100%', width: '100%'}" position="right"
safe-area-inset-bottom :close-on-click-overlay="false" v-model="visible">
<div class="works-apply-page">
<van-sticky>
<van-nav-bar title="作品提交" left-text="返回" left-arrow @click-left="closeApply"></van-nav-bar>
</van-sticky>
<van-form ref="formRef" class="works-apply-form form-container">
<div class="works-apply-scroll">
<section class="works-apply-section">
<h2 class="works-apply-section__title">基础信息</h2>
<van-cell-group :border="false">
<van-field label="姓名" readonly v-model="formData.username"></van-field>
<van-field label="工号" readonly v-model="formData.loginname"></van-field>
<van-field label="所在单位" readonly v-model="formData.unitName"></van-field>
<van-field label="所属工会" readonly v-model="formData.unionName"></van-field>
<van-field label="性别" readonly v-model="formData.sex"></van-field>
</van-cell-group>
</section>
<van-field label="作品类型"
required
:rules="[{ required: true, message: '请选择作品类型' }]"
readonly
is-link
@click="showWorksPicker = true"
placeholder="请选择作品类型"
name="worksName"
v-model="formData.worksName">
</van-field>
<van-popup v-model="showWorksPicker" position="bottom">
<van-picker
show-toolbar
:columns="worksTypeOptions.map(v=>v.worksTypeName)"
@confirm="onWorksConfirm"
@cancel="showWorksPicker=false"
></van-picker>
</van-popup>
<section class="works-apply-section">
<h2 class="works-apply-section__title">作品信息</h2>
<van-cell-group :border="false">
<van-field label="主题类型" required readonly is-link name="subjectName"
placeholder="请选择主题类型" :rules="[{required: true, message: '请选择主题类型'}]"
v-model="formData.subjectName" @click="openSubjectPicker"></van-field>
<van-field label="作品类型" required readonly is-link name="worksName"
placeholder="请先选择主题类型" :rules="[{required: true, message: '请选择作品类型'}]"
v-model="formData.worksName" @click="openWorksPicker"></van-field>
<van-field label="作品名称" required name="name" maxlength="100" show-word-limit
placeholder="请填写作品名称" :rules="[{required: true, message: '请填写作品名称'}]"
v-model="formData.name"></van-field>
<van-field class="more-text" label="作品描述" required type="textarea" name="description"
rows="4" autosize show-word-limit :maxlength="descriptionMaxLength"
placeholder="请填写作品描述" :rules="[{required: true, message: '请填写作品描述'}]"
v-model="formData.description"></van-field>
</van-cell-group>
</section>
<van-field label="作品名称"
required
v-model="formData.name"
name="name"
placeholder="请填写作品名称"
:rules="[{ required: true, message: '请填写作品名称' }]"></van-field>
<van-field label="作品描述"
v-model="formData.description"
type="textarea"
name="description"
rows="4"
autosize
maxlength="150"
:rules="[{ required: true, message: '请填写作品描述' }]"
class="more-text"
placeholder="请填写作品描述"></van-field>
</van-cell-group>
<van-cell-group title="附件">
<van-field class="more-text"
name="files"
:rules="[{ required: true,message:'请上传附件' }]"
label=""
required>
<template #input>
<h5-file-upload
slot="input"
:value.sync="formData.files"
:upload_number="chooseWorksType && chooseWorksType.allowFileNum"
upload_mode="file"
upload_result_category="array"
upload_result_type="url"
complete_result
:accept="fileAccept"
></h5-file-upload>
</template>
</van-field>
</van-cell-group>
<section class="works-apply-section">
<h2 class="works-apply-section__title">作品附件</h2>
<div class="works-apply-section__hint">附件说明:请根据所选作品类型上传符合要求的文件</div>
<van-cell-group :border="false">
<van-field class="more-text works-apply-upload-field" name="files" label="" required
:rules="[{required: true, message: '请上传附件'}]">
<template #input>
<h5-file-upload slot="input" :value.sync="formData.files"
:upload_number="chooseWorksType && chooseWorksType.allowFileNum"
upload_mode="file" upload_result_category="array"
upload_result_type="url" complete_result :accept="fileAccept">
</h5-file-upload>
</template>
</van-field>
</van-cell-group>
</section>
</div>
<div class="works-apply-footer">
<van-button type="primary" block round :loading="formLoading" loading-text="提交中..."
@click="onSubmit">提交作品</van-button>
</div>
</van-form>
</div>
</van-popup>
<div class="form-actions">
<van-button @click="onSubmit" round type="info">提交</van-button>
</div>
</van-form>
</van-action-sheet>
<van-popup class="works-option-popup" v-model="showOptionPicker" position="bottom" round
:close-on-click-overlay="false">
<div class="works-option-popup__header">
<button type="button" class="works-option-popup__action" @click="cancelOptionPopup">取消</button>
<div class="works-option-popup__title">{{optionPopupTitle}}</div>
<button type="button" class="works-option-popup__action"
:disabled="pendingOptionValue === null || pendingOptionValue === undefined || pendingOptionValue === ''"
@click="confirmOptionPopup">确认</button>
</div>
<div class="works-option-popup__list">
<div v-if="optionPopupOptions.length === 0" class="works-option-popup__empty">暂无可选内容</div>
<button v-for="option in optionPopupOptions" :key="option.value" type="button"
class="works-option-popup__option"
:class="{'is-selected': pendingOptionValue === option.value}"
:aria-pressed="pendingOptionValue === option.value"
@click="selectOptionPopupItem(option)">
<span class="works-option-popup__option-text">{{option.text}}</span>
</button>
</div>
</van-popup>
<van-dialog class="works-apply-confirm" title="提交确认" message="确认提交当前作品信息吗?"
:value="submitConfirmVisible" :close-on-click-overlay="false" show-cancel-button
cancel-button-text="取消" confirm-button-text="确认提交"
@cancel="closeSubmitConfirm" @confirm="confirmSubmit"></van-dialog>
</div>
`,
style: /*language=CSS*/ `
/deep/ .works-apply-popup {
background-color: #f3f7fd;
}
/deep/ .works-apply-page {
position: relative;
min-height: 100vh;
background-color: #f3f7fd;
}
/deep/ .works-apply-page .van-nav-bar {
background-color: #fff;
}
/deep/ .works-apply-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
}
/deep/ .works-apply-page .van-nav-bar .van-icon,
/deep/ .works-apply-page .van-nav-bar__text {
color: #397fbd;
}
/deep/ .works-apply-form.form-container {
min-height: 100%;
padding: 0;
box-sizing: border-box;
background: #f3f7fc;
}
/deep/ .works-apply-scroll {
height: calc(100vh - 46px);
padding: 12px 15px calc(84px + env(safe-area-inset-bottom));
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
background: #f3f7fc;
}
/deep/ .works-apply-section {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
/deep/ .works-apply-section__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
line-height: 20px;
font-weight: 600;
background: transparent;
}
/deep/ .works-apply-section__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
/deep/ .works-apply-section__hint {
padding: 0 14px 8px 21px;
color: orangered;
font-size: 14px;
line-height: 18px;
font-weight: normal;
}
/deep/ .works-apply-section .van-cell-group {
margin: 0;
background: transparent;
}
/deep/ .works-apply-section .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
/deep/ .works-apply-section .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
/deep/ .works-apply-section .van-field__label {
width: 84px;
color: #596779;
font-size: 14px;
line-height: 24px;
}
/deep/ .works-apply-section .van-field__value,
/deep/ .works-apply-section .van-field__control {
color: #4f5d70;
font-size: 14px;
line-height: 24px;
}
/deep/ .works-apply-section .van-field__control::placeholder {
color: #b1bac6;
}
/deep/ .works-apply-section .van-cell--required::before {
left: 6px;
color: #f06464;
}
/deep/ .works-apply-upload-field .van-field__value {
overflow: visible;
}
/deep/ .works-apply-footer {
position: fixed;
z-index: 100;
right: 0;
bottom: 0;
left: 0;
display: flex;
padding: 10px 18px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background: rgba(255, 255, 255, .98);
box-shadow: 0 -4px 14px rgba(50, 78, 112, .06);
}
/deep/ .works-apply-footer .van-button {
flex: 1;
height: 40px;
margin: 0;
border-color: #1989fa;
border-radius: 14px !important;
font-size: 16px;
background: #1989fa;
}
/deep/ .works-option-popup {
display: flex;
height: 50%;
padding-bottom: env(safe-area-inset-bottom);
flex-direction: column;
box-sizing: border-box;
background: #f5f7fa;
}
/deep/ .works-option-popup__header {
display: flex;
min-height: 52px;
padding: 0 16px;
align-items: center;
justify-content: space-between;
flex: none;
border-bottom: 1px solid #edf1f6;
background: #fff;
}
/deep/ .works-option-popup__title {
color: #263548;
font-size: 17px;
line-height: 24px;
font-weight: 600;
}
/deep/ .works-option-popup__action {
min-width: 48px;
padding: 8px 0;
border: 0;
color: #1989fa;
font-size: 15px;
line-height: 22px;
text-align: left;
background: transparent;
}
/deep/ .works-option-popup__action:last-child {
text-align: right;
}
/deep/ .works-option-popup__action:disabled {
color: #b7c0cc;
}
/deep/ .works-option-popup__list {
min-height: 0;
padding: 12px;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
/deep/ .works-option-popup__empty {
padding: 44px 18px;
color: #98a4b3;
font-size: 14px;
line-height: 22px;
text-align: center;
}
/deep/ .works-option-popup__option {
display: flex;
width: 100%;
min-height: 58px;
margin-bottom: 10px;
padding: 12px 14px;
align-items: center;
border: 1px solid #e3e9f1;
border-radius: 10px;
color: #536174;
text-align: left;
background: #fff;
}
/deep/ .works-option-popup__option:last-child {
margin-bottom: 0;
}
/deep/ .works-option-popup__option.is-selected {
border-color: #1989fa;
color: #176fbd;
background: #eef7ff;
box-shadow: 0 3px 10px rgba(25, 137, 250, .1);
}
/deep/ .works-option-popup__option-text {
min-width: 0;
flex: 1;
font-size: 15px;
line-height: 23px;
word-break: break-word;
overflow-wrap: anywhere;
}
/deep/ .works-apply-confirm .van-dialog__header {
color: #263548;
font-size: 17px;
font-weight: 600;
}
/deep/ .works-apply-confirm .van-dialog__message {
padding: 24px 20px;
color: #536174;
font-size: 15px;
line-height: 24px;
text-align: center;
}
`,
data() {
return {
row: {},
visible: false,
formData: {},
subjectTypesOptions: [],
showSubjectPicker: false,
worksTypeOptions: [],
showWorksPicker: false,
fileAccept: null,
chooseWorksType: {}
row: {}, visible: false, formData: {}, formLoading: false,
subjectTypesOptions: [], worksTypeOptions: [],
showOptionPicker: false, optionPopupType: "", optionPopupTitle: "",
optionPopupOptions: [], pendingOptionValue: "",
submitConfirmVisible: false, fileAccept: null, chooseWorksType: {},
historyLayerPageKey: "works-collection-upload-h5",
historyLayerUnsubscribe: null, ownsHistoryRegistration: false
}
},
computed: {
descriptionMaxLength() {
return this.chooseWorksType && this.chooseWorksType.maxWordCount ? this.chooseWorksType.maxWordCount : 150
}
},
methods: {
async onOpen(row) {
this.$set(this.formData, 'username', this.$store.state.user.username)
this.$set(this.formData, 'loginname', this.$store.state.user.loginname)
this.$set(this.formData, 'unionName', this.$store.state.user.union.name)
this.$set(this.formData, 'unitName', this.$store.state.user.unit.name)
this.$set(this.formData, 'sex', this.$store.state.user.sex)
this.$set(this.formData, 'activityId', row.id)
await this.activityChange(row.id)
this.row = row
this.visible = true
resetFormData() {
const user = this.$store.state.user || {}
const union = user.union || {}
const unit = user.unit || {}
this.$set(this, "formData", {})
this.$set(this.formData, "username", user.username)
this.$set(this.formData, "loginname", user.loginname)
this.$set(this.formData, "unionName", union.name)
this.$set(this.formData, "unitName", unit.name)
this.$set(this.formData, "sex", user.sex)
this.$set(this.formData, "files", [])
this.$set(this, "subjectTypesOptions", [])
this.$set(this, "worksTypeOptions", [])
this.$set(this, "chooseWorksType", {})
this.$set(this, "fileAccept", null)
},
async onOpenEdit(row) {
onOpen(row) {
this.resetFormData()
this.$set(this, "row", row)
this.$set(this.formData, "activityId", row.id)
this.activityChange(row.id).then(() => {
window.h5HistoryLayerManager.open("works-collection-apply")
})
},
onOpenEdit(row) {
const formData = clone(row)
formData.files = JSON.parse(formData.files)
await this.activityChange(formData.activityId)
await this.subjectChange(formData.subjectId)
this.row = formData
this.formData = formData
this.$set(this.formData, 'username', this.$store.state.user.username)
this.$set(this.formData, 'loginname', this.$store.state.user.loginname)
this.$set(this.formData, 'sex', this.$store.state.user.sex)
this.chooseWorksType = this.worksTypeOptions.find((o) => o.id === formData.worksId)
if (this.chooseWorksType.allowFileTypes && this.chooseWorksType.allowFileTypes.length > 0) {
this.fileAccept = this.chooseWorksType.allowFileTypes.map((item) => "." + item).join(",")
}
this.visible = true
formData.files = this.normalizeFiles(formData.files)
this.resetFormData()
this.$set(this, "row", formData)
this.$set(this, "formData", formData)
const user = this.$store.state.user || {}
this.$set(this.formData, "username", user.username)
this.$set(this.formData, "loginname", user.loginname)
this.$set(this.formData, "sex", user.sex)
this.activityChange(formData.activityId).then(() => {
// 编辑时以主题主键匹配当前活动配置,避免列表未携带主题名称时无法回显。
const subjectType = this.subjectTypesOptions.find((item) => item.id === formData.subjectId)
if (subjectType) this.$set(this.formData, "subjectName", subjectType.typeName)
return this.subjectChange(formData.subjectId)
}).then(() => {
this.setSelectedWorksType(formData.worksId)
window.h5HistoryLayerManager.open("works-collection-apply")
})
},
onWorksConfirm(value, index) {
this.$set(this.formData, 'worksName', value)
this.$set(this.formData, 'worksId', this.worksTypeOptions[index].id)
this.showWorksPicker = false
this.chooseWorksType = this.worksTypeOptions.find((o) => o.id === this.worksTypeOptions[index].id)
if (this.chooseWorksType.allowFileTypes && this.chooseWorksType.allowFileTypes.length > 0) {
this.fileAccept = this.chooseWorksType.allowFileTypes.map((item) => "." + item).join(",")
// 附件可能由列表 SQL 返回 JSON 字符串,也可能已被序列化为数组;统一转换为上传组件需要的数组。
normalizeFiles(files) {
if (Array.isArray(files)) return files
if (!files) return []
try {
const result = JSON.parse(files)
return Array.isArray(result) ? result : []
} catch (err) {
return []
}
},
async onSubjectConfirm(value, index) {
this.$set(this.formData, 'subjectName', value)
this.$set(this.formData, 'subjectId', this.subjectTypesOptions[index].id)
await this.subjectChange(this.subjectTypesOptions[index].id)
this.showSubjectPicker = false
closeApply() { window.h5HistoryLayerManager.close("works-collection-apply") },
// 打开统一选择弹框:主题选项由活动配置提供,确认后再写入表单。
openSubjectPicker() {
this.$set(this, "optionPopupType", "subject")
this.$set(this, "optionPopupTitle", "选择主题类型")
this.$set(this, "optionPopupOptions", this.subjectTypesOptions.map((item) => {
return {value: item.id, text: item.typeName, source: item}
}))
this.$set(this, "pendingOptionValue", this.formData.subjectId || "")
window.h5HistoryLayerManager.open("works-collection-option-picker")
},
openWorksPicker() {
if (!this.formData.subjectId) {
this.$toast("请先选择主题类型")
return
}
this.$set(this, "optionPopupType", "works")
this.$set(this, "optionPopupTitle", "选择作品类型")
this.$set(this, "optionPopupOptions", this.worksTypeOptions.map((item) => {
return {value: item.id, text: item.worksTypeName, source: item}
}))
this.$set(this, "pendingOptionValue", this.formData.worksId || "")
window.h5HistoryLayerManager.open("works-collection-option-picker")
},
cancelOptionPopup() { window.h5HistoryLayerManager.close("works-collection-option-picker") },
selectOptionPopupItem(option) { this.$set(this, "pendingOptionValue", option.value) },
/**
* 确认类型选项并回填作品表单。
* optionPopupType 为 subject 或 workspendingOptionValue 为对应业务主键;方法无返回值。
*/
confirmOptionPopup() {
const selectedOption = this.optionPopupOptions.find((item) => {
return item.value === this.pendingOptionValue
})
if (!selectedOption) return
if (this.optionPopupType === "subject") {
this.onSubjectConfirm(selectedOption)
return
}
this.onWorksConfirm(selectedOption)
},
onWorksConfirm(selectedOption) {
this.$set(this.formData, "worksName", selectedOption.text)
this.$set(this.formData, "worksId", selectedOption.value)
this.setSelectedWorksType(selectedOption.value)
this.cancelOptionPopup()
},
onSubjectConfirm(selectedOption) {
this.$set(this.formData, "subjectName", selectedOption.text)
this.$set(this.formData, "subjectId", selectedOption.value)
this.$set(this.formData, "worksName", "")
this.$set(this.formData, "worksId", "")
this.$set(this.formData, "files", [])
this.$set(this, "chooseWorksType", {})
this.$set(this, "fileAccept", null)
this.subjectChange(selectedOption.value).then(() => { this.cancelOptionPopup() })
},
setSelectedWorksType(worksId) {
const worksType = this.worksTypeOptions.find((item) => item.id === worksId) || {}
this.$set(this, "chooseWorksType", worksType)
if (worksType.allowFileTypes && worksType.allowFileTypes.length > 0) {
this.$set(this, "fileAccept", worksType.allowFileTypes.map((item) => "." + item).join(","))
return
}
this.$set(this, "fileAccept", null)
},
onSubmit() {
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.$toast.loading({
message: '提交中...',
forbidClick: true,
});
this.$axios.post("/platform/activity/worksCollection/upload" + (this.formData.id ? "/update" : "/insert"), {data: JSON.stringify(this.formData)}).then((res) => {
if (res.code === 0) {
this.$toast.clear();
this.$toast.success("提交成功")
pjaxReplace("/platform/activity/worksCollection/mine/h5")
}
})
})
}).catch();
window.h5HistoryLayerManager.open("works-collection-submit-confirm")
}).catch(() => {
// 表单校验失败时由字段规则直接展示提示,不进入提交确认。
})
},
async activityChange(value) {
const resp = await this.$axios.post("/platform/activity/worksCollection/common/getSubjectTypes", {activityId: value})
if (resp.code === 0) {
this.subjectTypesOptions = resp.data
}
closeSubmitConfirm() { window.h5HistoryLayerManager.close("works-collection-submit-confirm") },
confirmSubmit() {
window.h5HistoryLayerManager.close("works-collection-submit-confirm", () => { this.submitForm() })
},
async subjectChange(value) {
const resp = await this.$axios.post("/platform/activity/worksCollection/common/getWorksTypes", {subjectId: value})
if (resp.code === 0) {
this.worksTypeOptions = resp.data
}
submitForm() {
this.$set(this, "formLoading", true)
this.$axios.post("/platform/activity/worksCollection/upload" + (this.formData.id ? "/update" : "/insert"), {
data: JSON.stringify(this.formData)
}).then((res) => {
if (res.code === 0) {
this.$toast.success("提交成功")
window.h5HistoryLayerManager.closeAllAndNavigate("/platform/activity/worksCollection/mine/h5")
}
}).finally(() => { this.$set(this, "formLoading", false) })
},
activityChange(value) {
return this.$axios.post("/platform/activity/worksCollection/common/getSubjectTypes", {activityId: value}).then((res) => {
if (res.code === 0) this.$set(this, "subjectTypesOptions", res.data || [])
})
},
subjectChange(value) {
return this.$axios.post("/platform/activity/worksCollection/common/getWorksTypes", {subjectId: value}).then((res) => {
if (res.code === 0) this.$set(this, "worksTypeOptions", res.data || [])
})
},
syncHistoryLayers(stack) {
this.$set(this, "visible", stack.includes("works-collection-apply"))
this.$set(this, "showOptionPicker", stack.includes("works-collection-option-picker"))
this.$set(this, "submitConfirmVisible", stack.includes("works-collection-submit-confirm"))
}
},
created() {
if (!window.h5HistoryLayerManager.pageKey) {
window.h5HistoryLayerManager.ensureRegistered(this.historyLayerPageKey)
this.$set(this, "ownsHistoryRegistration", true)
}
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayers(stack)
}))
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
if (this.ownsHistoryRegistration) window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
}
}
@@ -1,108 +1,690 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.info-title {
font-weight: bold;
background-color: white;
padding: 13px;
box-shadow: 0 8px 12px #ebedf0;
text-align: center !important;
<style>
body {
background-color: #f3f7fd;
}
.info-container {
height: calc(100vh - 46px - 44px);
min-height: calc(100vh - 46px - 44px);
overflow-y: auto;
.works-collection-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background-color: #f3f7fd;
}
.info-img {
display: block;
.works-collection-page .van-nav-bar {
background-color: #fff;
}
.van-count-down {
.works-collection-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
}
.works-collection-page .van-nav-bar .van-icon,
.works-collection-page .van-nav-bar__text {
color: #397fbd;
}
.works-collection-content {
padding: 12px 12px 0;
}
.works-collection-hero {
position: relative;
height: 134px;
overflow: hidden;
border-radius: 10px;
color: #fff;
background-color: #247fe8;
box-shadow: 0 8px 20px rgba(51, 117, 219, .18);
}
.works-collection-hero__image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.works-collection-hero__copy {
position: relative;
z-index: 1;
display: flex;
width: 64%;
height: 100%;
padding-left: 16px;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
background: linear-gradient(90deg, rgba(22, 112, 232, .98) 0%, rgba(22, 112, 232, .88) 52%, rgba(22, 112, 232, 0) 100%);
}
.works-collection-hero__title {
font-size: 25px;
line-height: 34px;
font-weight: 700;
letter-spacing: 1px;
text-shadow: 0 2px 6px rgba(17, 86, 182, .18);
}
.works-collection-hero__subtitle {
margin-top: 4px;
font-size: 13px;
line-height: 20px;
letter-spacing: .5px;
opacity: .95;
}
.works-collection-sticky {
padding: 10px 0 1px;
background-color: #f3f7fd;
}
.van-sticky--fixed .works-collection-sticky {
padding-right: 12px;
padding-left: 12px;
box-sizing: border-box;
}
.works-collection-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.works-collection-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.works-collection-search .van-field__control {
color: #263548;
font-size: 14px;
}
.works-collection-search .van-field__control::placeholder {
color: #a7b0bf;
}
.works-collection-filter {
margin: 0 0 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.works-collection-filter.is-open {
border-radius: 10px 10px 0 0;
}
.works-collection-filter .van-dropdown-menu__bar {
height: 44px;
box-shadow: none;
}
.works-collection-filter .van-dropdown-menu__item {
min-width: 0;
overflow: hidden;
}
.works-collection-filter .van-dropdown-menu__title {
width: auto;
max-width: calc(100% - 24px);
padding: 0 12px 0 0;
overflow: hidden;
color: #66758a;
font-size: 14px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
}
.works-collection-filter .van-dropdown-menu__title::after {
right: 0;
}
.works-collection-filter .van-dropdown-menu__title--active,
.works-collection-filter .van-dropdown-item__option--active,
.works-collection-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.works-collection-filter .van-dropdown-menu__title--down::after {
border-color: transparent transparent #1989fa #1989fa;
}
.works-collection-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.works-collection-filter .van-dropdown-item__option {
min-height: 48px;
padding: 0 16px;
color: #46566c;
font-size: 14px;
}
.works-collection-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.works-collection-card {
position: relative;
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
cursor: pointer;
}
.works-collection-card__cover,
.works-collection-card__cover-state {
display: flex;
width: 100%;
height: 150px;
align-items: center;
justify-content: center;
box-sizing: border-box;
background-color: #edf4fc;
}
.works-collection-card__cover {
display: block;
object-fit: cover;
}
.works-collection-card__cover-state {
flex-direction: column;
row-gap: 8px;
color: #8da1ba;
font-size: 13px;
}
.works-collection-card__cover-state .van-icon {
font-size: 34px;
}
.works-collection-card__cover-state.is-error {
color: #c27b75;
background-color: #fff3f1;
}
.works-collection-card__body {
padding: 12px 38px 14px 14px;
}
.works-collection-card__title {
min-width: 0;
overflow: hidden;
color: #303846;
font-size: 14px;
line-height: 21px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-collection-card__time {
margin-top: 4px;
overflow: hidden;
color: #98a2b2;
font-size: 12px;
line-height: 17px;
text-overflow: ellipsis;
white-space: nowrap;
}
.works-collection-card__meta {
display: flex;
margin-top: 10px;
padding-top: 9px;
flex-wrap: wrap;
gap: 6px;
border-top: 1px solid #edf1f6;
}
.works-collection-card__tag {
display: inline-flex;
padding: 3px 8px;
align-items: center;
border-radius: 4px;
color: #397de0;
background-color: #edf5ff;
font-size: 11px;
line-height: 16px;
}
.works-collection-card__tag-label {
margin-right: 4px;
color: #8aa7cf;
}
.works-collection-card__arrow {
position: absolute;
top: 164px;
right: 14px;
color: #b4bdca;
font-size: 16px;
}
.works-collection-empty {
display: flex;
min-height: 260px;
padding: 26px 18px 32px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.works-collection-empty img {
display: block;
width: 82%;
max-width: 280px;
height: auto;
object-fit: contain;
}
.works-collection-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.works-collection-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.works-collection-page .van-list__loading {
color: #9aa7b8;
font-size: 13px;
line-height: 44px;
}
.works-collection-skeleton {
display: flex;
flex-direction: column;
gap: 10px;
}
.works-collection-skeleton__card {
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.works-collection-skeleton__cover,
.works-collection-skeleton__body .van-skeleton {
padding: 0;
}
.works-collection-skeleton__cover .van-skeleton__row {
width: 100% !important;
height: 150px;
margin: 0;
border-radius: 0;
}
.works-collection-skeleton__body {
padding: 13px 14px 15px;
}
.works-collection-skeleton__body .van-skeleton__title {
width: 54%;
height: 15px;
margin-bottom: 12px;
}
.works-collection-skeleton__body .van-skeleton__row {
height: 12px;
margin-top: 9px;
}
.works-collection-skeleton__body .van-skeleton__row:last-child {
width: 46%;
}
.works-intro-popup,
.works-intro-page {
background-color: #f3f7fd;
}
.works-intro-page {
position: relative;
min-height: 100vh;
}
.works-intro-page .van-nav-bar {
background-color: #fff;
}
.works-intro-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
}
.works-intro-page .van-nav-bar .van-icon,
.works-intro-page .van-nav-bar__text {
color: #397fbd;
}
.works-intro-scroll {
height: calc(100vh - 46px);
padding: 12px 12px calc(92px + env(safe-area-inset-bottom));
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
}
.works-intro-hero {
display: flex;
min-height: 82px;
padding: 16px;
align-items: center;
box-sizing: border-box;
border-radius: 12px;
color: #fff;
background: linear-gradient(120deg, #2d7eea 0%, #5ba6f7 100%);
box-shadow: 0 8px 20px rgba(45, 126, 234, .18);
}
.works-intro-hero__icon {
display: flex;
width: 44px;
height: 44px;
margin-right: 12px;
flex: 0 0 44px;
align-items: center;
justify-content: center;
border-radius: 11px;
color: #2d7eea;
background-color: rgba(255, 255, 255, .92);
font-size: 25px;
}
.works-intro-hero__title {
font-size: 17px;
line-height: 24px;
font-weight: 600;
}
.works-intro-hero__subtitle {
margin-top: 3px;
font-size: 12px;
line-height: 18px;
opacity: .9;
}
.works-intro-card {
margin-top: 12px;
padding: 18px 16px;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.works-intro-content,
.works-intro-content div,
.works-intro-content p,
.works-intro-content span,
.works-intro-content li {
color: #3f4c5e !important;
font-size: 14px !important;
line-height: 1.8 !important;
white-space: normal;
word-break: break-word;
}
.works-intro-content h1,
.works-intro-content h2,
.works-intro-content h3 {
margin: 16px 0 10px !important;
color: #1f2f43 !important;
font-size: 19px !important;
line-height: 1.5 !important;
font-weight: 600 !important;
}
.works-intro-content > :first-child {
margin-top: 0 !important;
}
.works-intro-content p {
margin-top: 0 !important;
margin-bottom: 8px !important;
}
.works-intro-content img {
max-width: 100% !important;
height: auto !important;
border-radius: 8px;
}
.works-intro-content table {
display: block;
max-width: 100%;
overflow-x: auto;
border-collapse: collapse;
}
.works-intro-empty {
display: flex;
min-height: 180px;
margin-top: 12px;
align-items: center;
justify-content: center;
flex-direction: column;
row-gap: 10px;
border-radius: 12px;
color: #9aa7b8;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
font-size: 14px;
}
.works-intro-empty .van-icon {
color: #83b5f4;
font-size: 36px;
}
.works-intro-footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 5;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background-color: rgba(255, 255, 255, .96);
box-sizing: border-box;
}
.works-intro-footer .van-button {
height: 42px;
border: 0;
border-radius: 21px;
font-size: 15px;
background-color: #1989fa;
box-shadow: 0 6px 16px rgba(25, 137, 250, .22);
}
.works-intro-footer .van-button--disabled {
color: #8b98aa !important;
background-color: #eef2f7 !important;
opacity: 1;
box-shadow: none;
}
.works-intro-footer .van-count-down {
display: inline-block;
margin-left: 4px;
color: #fff;
font-size: 13px;
vertical-align: middle;
}
</style>
<div id="app">
<!-- 导航栏 -->
<van-nav-bar title="作品上传" left-text="返回" left-arrow placeholder
@click-left="historyBack" fixed></van-nav-bar>
<div id="app" v-cloak>
<div class="works-collection-page">
<van-nav-bar title="作品征集" left-text="返回" left-arrow @click-left="handlePageBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.activityType" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<div class="works-collection-content">
<section class="works-collection-hero">
<img class="works-collection-hero__image" src="/assets/mobile/img/activity/hero/brand-hero-v2.png" alt="作品征集插画">
<div class="works-collection-hero__copy">
<div class="works-collection-hero__title">作品征集</div>
<div class="works-collection-hero__subtitle">汇聚创意 · 展示风采</div>
</div>
</section>
<van-sticky :offset-top="46">
<div class="works-collection-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="works-collection-search"
placeholder="搜索作品征集活动"
shape="round"
clearable
@search="doSearch"
@clear="doSearch"
></van-search>
<van-dropdown-menu class="works-collection-filter" :class="{'is-open': filterOpened}" active-color="#1989fa">
<year-van-dropdown-item :num="5" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.activityType" :options="typeOptions" :multiple="false" @change="doSearch" @open="onFilterOpen" @close="onFilterClose"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<table-list api="/platform/activity/worksCollection/upload/h5/activityPageData"
:page_form.sync="pageForm"
ref="tableListRef"
title="name"
img="cover"
@ready="onReady"
>
<template v-slot="{index,row}">
<table-column label="面向对象">{{row.activityGroupName}}</table-column>
<table-column label="报名时间">
{{$moment(row.startDateTime).format('MM/DD HH:mm')
+ '~' + $moment(row.endDateTime).format('MM/DD HH:mm')}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看介绍</span>
<div v-if="skeletonLoading" class="works-collection-skeleton">
<div v-for="index in 3" :key="'upload-skeleton-' + index" class="works-collection-skeleton__card">
<van-skeleton class="works-collection-skeleton__cover" :row="1" animated></van-skeleton>
<div class="works-collection-skeleton__body">
<van-skeleton title :row="2" animated></van-skeleton>
</div>
</div>
</div>
<div class="action-btn" @click="onApply(row)">
<i class="fa fa-edit"></i>
<span>去报名</span>
</div>
</template>
</table-list>
<van-action-sheet v-model="infoVisible" title="详细信息">
<div class="info-container">
<van-image class="info-img" height="186" width="100%" :src="infoRow.cover"></van-image>
<div class="info-title">{{infoRow.name}}</div>
<pdf-preview style="height: calc(100vh - 46px - 44px - 186px - 57px)"
:content="infoRow.content"></pdf-preview>
<van-pull-refresh v-show="!skeletonLoading" v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" :immediate-check="false" @load="onLoad">
<div class="works-collection-list">
<div v-for="row in tableData" :key="row.id" class="works-collection-card" @click="onView(row)">
<img
v-if="hasCover(row) && !imageLoadErrors[row.id]"
class="works-collection-card__cover"
:src="row.cover"
:alt="row.name"
@error="handleImageError(row)"
>
<div v-else class="works-collection-card__cover-state" :class="{'is-error': hasCover(row)}">
<van-icon name="photo-o"></van-icon>
<span>{{ hasCover(row) ? '图片加载失败' : '暂无图片' }}</span>
</div>
<div class="works-collection-card__body">
<div class="works-collection-card__title">{{ row.name }}</div>
<div class="works-collection-card__time" v-if="row.startDateTime && row.endDateTime">
征集时间:{{ $moment(row.startDateTime).format('MM/DD HH:mm') }}~{{ $moment(row.endDateTime).format('MM/DD HH:mm') }}
</div>
<div class="works-collection-card__meta" v-if="row.activityGroupName">
<span class="works-collection-card__tag" v-if="row.activityGroupName">
<span class="works-collection-card__tag-label">面向对象</span>{{ row.activityGroupName }}
</span>
</div>
</div>
<van-icon class="works-collection-card__arrow" name="arrow"></van-icon>
</div>
</div>
</van-list>
<div v-if="!loading && tableData.length === 0" class="works-collection-empty">
<img src="/assets/mobile/img/activity/culture-empty.png" alt="作品征集空状态插画">
<div class="works-collection-empty__title">暂无作品征集活动</div>
<div class="works-collection-empty__hint">当前筛选条件下暂时没有可展示的活动</div>
</div>
</van-pull-refresh>
</div>
<div class="form-container">
<div class="form-actions">
</div>
<van-button @click="onApply(infoRow)" type="primary" block>
<span v-if="time >= 0">
去报名
</span>
<template v-else>
距离开始
<van-count-down :time="Math.abs(time)" class="count-down" format="DD 天 HH 时 mm 分 ss 秒"
@finish="time = 0"></van-count-down>
</template>
<van-popup class="works-intro-popup" :style="{height: '100%', width: '100%'}" position="right" safe-area-inset-bottom :close-on-click-overlay="false" v-model="infoVisible">
<div class="works-intro-page">
<van-sticky>
<van-nav-bar title="征集介绍" left-text="返回" left-arrow @click-left="closeInfo"></van-nav-bar>
</van-sticky>
<div class="works-intro-scroll">
<div class="works-intro-hero">
<div class="works-intro-hero__icon">
<van-icon name="description"></van-icon>
</div>
<div>
<div class="works-intro-hero__title">作品征集须知</div>
<div class="works-intro-hero__subtitle">请仔细阅读征集内容与作品要求</div>
</div>
</div>
<article v-if="infoRow.content" class="works-intro-card">
<div class="works-intro-content" v-html="infoRow.content"></div>
</article>
<div v-else class="works-intro-empty">
<van-icon name="description"></van-icon>
<span>暂无征集介绍</span>
</div>
</div>
<div class="works-intro-footer">
<van-button v-if="canApply(infoRow)" @click="onApply(infoRow)" type="primary" block round>上传作品</van-button>
<van-button v-else-if="time < 0" @click="onApply(infoRow)" type="primary" block round>
距离开始
<van-count-down :time="Math.abs(time)" format="DD 天 HH 时 mm 分 ss 秒" @finish="onCountdownFinish"></van-count-down>
</van-button>
<van-button v-else disabled block round>{{ registrationStatusText(infoRow) }}</van-button>
</div>
</div>
</van-action-sheet>
</van-popup>
<apply-form ref="applyFormRef"></apply-form>
</div>
<script nonce="${cspNonce!}">
<!--#include('applyForm.js'){}#-->
new Vue({
<!--#include("applyForm.js"){}#-->
const vue = new Vue({
el: "#app",
store,
components: {
"apply-form": applyForm
},
data() {
return {
time: 0,
tableData: [],
loading: false,
skeletonLoading: true,
finished: false,
refreshing: false,
imageLoadErrors: {},
filterOpened: false,
pageForm: {
pageNumber: 1,
pageSize: 10,
@@ -114,53 +696,139 @@ layout("/layouts/platform_h5.html"){
typeOptions: [
{text: '全部', value: 1},
{text: '即将开始', value: 4},
{text: '报名中', value: 2},
{text: '征集中', value: 2},
{text: '已结束', value: 3},
],
infoVisible: false,
infoRow: {},
historyLayerPageKey: "works-collection-upload-h5",
historyLayerUnsubscribe: null
}
},
components: {
'apply-form': applyForm,
"pdf-preview": httpVueLoader("/components/plugins/sysFilePreview/PdfIndex.vue?v=" + new Date().getTime())
},
methods: {
onApply(row) {
if (this.$moment().isBefore(this.$moment(row.startDateTime))) {
this.onView(row)
hasCover(row) {
return !!(row && row.cover && String(row.cover).trim())
},
handleImageError(row) {
this.$set(this.imageLoadErrors, row.id, true)
},
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
onCountdownFinish() {
this.$set(this, "time", 0)
},
isRegistrationClosed(row) {
return !!(row && row.endDateTime && !this.$moment().isBefore(this.$moment(row.endDateTime)))
},
canApply(row) {
if(!row || !row.startDateTime || !row.endDateTime) {
return false
}
const now = Date.now()
return now >= this.$moment(row.startDateTime).valueOf()
&& now <= this.$moment(row.endDateTime).valueOf()
},
registrationStatusText(row) {
if(!row || !row.startDateTime || !row.endDateTime) {
return '当前不可参与'
}
if(Date.now() < this.$moment(row.startDateTime).valueOf()) {
return '征集未开始'
}
return '征集已结束'
},
onView(row) {
this.$set(this, "infoRow", row)
this.$set(this, "time", this.$moment().diff(this.$moment(row.startDateTime), "milliseconds"))
window.h5HistoryLayerManager.open("works-collection-info")
},
closeInfo() {
window.h5HistoryLayerManager.close("works-collection-info")
},
handlePageBack() {
if (window.h5HistoryLayerManager.stack.length) {
window.h5HistoryLayerManager.close()
return
}
if (this.$moment().isAfter(this.$moment(row.endDateTime))) {
this.$toast.fail("活动已结束")
this.historyBack()
},
syncHistoryLayers(stack) {
this.$set(this, "infoVisible", stack.includes("works-collection-info"))
},
onApply(row) {
if(this.isRegistrationClosed(row)) {
this.$toast.fail("征集已结束")
return
}
if(this.$moment().isBefore(this.$moment(row.startDateTime))) {
this.onView(row)
return
}
this.$refs.applyFormRef.onOpen(row)
},
onView(row) {
this.infoRow = row
this.time = this.$moment().diff(this.$moment(row.startDateTime), 'milliseconds')
this.infoVisible = true
onLoad() {
if(!this.finished) {
this.loadActivities(false)
}
},
onReady() {
this.doSearch()
onRefresh() {
this.loadActivities(true)
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
loadActivities(reset) {
const showSkeleton = !!reset
if(reset) {
this.$set(this, "skeletonLoading", true)
this.$set(this.pageForm, "pageNumber", 1)
this.$set(this.pageForm, "totalCount", 0)
this.$set(this, "tableData", [])
this.$set(this, "finished", false)
this.$set(this, "imageLoadErrors", {})
}
this.$set(this, "loading", true)
this.$axios.post('/platform/activity/worksCollection/upload/h5/activityPageData', this.pageForm, {
h5SkeletonLoading: showSkeleton
}).then((res) => {
if(res.code === 0) {
const list = res.data && res.data.list ? res.data.list : []
this.$set(this, "tableData", this.pageForm.pageNumber === 1 ? list : this.tableData.concat(list))
this.$set(this.pageForm, "totalCount", res.data && res.data.totalCount ? res.data.totalCount : 0)
this.$set(this, "finished", this.tableData.length >= this.pageForm.totalCount)
if(!this.finished) {
this.$set(this.pageForm, "pageNumber", this.pageForm.pageNumber + 1)
}
}
}).finally(() => {
this.$set(this, "loading", false)
this.$set(this, "refreshing", false)
if(showSkeleton) this.$set(this, "skeletonLoading", false)
})
},
doSearch() {
this.loadActivities(true)
}
},
created() {
window.h5HistoryLayerManager.register(this.historyLayerPageKey, (stack) => {
this.syncHistoryLayers(stack)
})
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayers(stack)
}))
this.loadActivities(true)
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) {
this.historyLayerUnsubscribe()
}
window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
}
})
</script>
<!--#
}
#-->
@@ -4,12 +4,12 @@ layout("/layouts/platform_h5.html"){
<style>
body {
background: #f5f6fa;
background: #f3f7fd;
}
#app {
min-height: 100vh;
background: #f5f6fa;
background: #f3f7fd;
}
.custom-nav {
@@ -394,17 +394,22 @@ layout("/layouts/platform_h5.html"){
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="报销申请" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed z-index="999"
<style id="style-union-reimburse-h5">
<!--#include('../common/unionReimburse.css'){}#-->
</style>
<div id="app" v-cloak class="union-reimburse-page union-reimburse-form">
<van-nav-bar title="报销申请" left-text="返回" left-arrow @click-left="onNavBack" placeholder fixed z-index="999"
class="custom-nav"></van-nav-bar>
<div class="form-container">
<van-form ref="formRef">
<van-cell-group title="信息填报" class="form-section">
<section class="form-section">
<h2 class="form-section__title">基本信息</h2>
<van-cell-group :border="false">
<van-field label="工号" v-model="formData.loginName" placeholder="从信息中心获取" readonly></van-field>
<van-field label="经办人" v-model="formData.userName" placeholder="从信息中心获取" readonly></van-field>
<!-- 联系方式 -->
<van-field label="联系方式" v-model="formData.mobile" required placeholder="请输入联系方式"
<!-- 经办人联系方式,使用当前登录用户的 mobile 初始化。 -->
<van-field label="联系方式" name="mobile" v-model="formData.mobile" required placeholder="请输入联系方式"
:rules="[
{ required: true, message: '请输入联系方式' },
{ pattern: /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/, message: '请输入正确的联系方式' }
@@ -422,7 +427,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showReimburseFundSourcePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showReimburseFundSourcePicker">
<van-picker :columns="reimburseFundSourceColumns" @cancel="closePopup('showReimburseFundSourcePicker')"
@confirm="onReimburseFundSourceConfirm" show-toolbar></van-picker>
</van-popup>
@@ -444,7 +449,7 @@ layout("/layouts/platform_h5.html"){
<span style="color: #ee0a24; text-align: left;">报销提示:{{getProjectTipByProject(formData.reimburseProject)}}</span>
</template>
</van-cell>
<van-popup position="bottom" round v-model:show="showReimburseProjectPicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showReimburseProjectPicker">
<van-picker :columns="reimburseProjectColumns" @cancel="closePopup('showReimburseProjectPicker')"
@confirm="onReimburseProjectConfirm" show-toolbar></van-picker>
</van-popup>
@@ -461,7 +466,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showClubPicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showClubPicker">
<van-picker :columns="clubColumns" @cancel="closePopup('showClubPicker')"
@confirm="onClubConfirm" show-toolbar></van-picker>
</van-popup>
@@ -532,8 +537,11 @@ layout("/layouts/platform_h5.html"){
</van-action-sheet>
-->
</van-cell-group>
<van-cell-group :title="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1' ? '慰问信息' : '活动信息'" class="form-section">
</van-cell-group>
</section>
<section class="form-section">
<h2 class="form-section__title">{{!formData.reimburseProject ? '报销信息' : (formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1' ? '慰问信息' : '活动信息')}}</h2>
<van-cell-group :border="false">
<template v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
<van-field
@@ -548,7 +556,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-action-sheet v-model="showCondolenceSearch" title="慰问对象" class="height100">
<van-action-sheet v-model="showCondolenceSearch" :close-on-click-overlay="true" title="慰问对象" class="height100">
<van-search
v-model="condolenceSearchKeyword"
:show-action="false"
@@ -587,7 +595,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showCondolenceTypePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showCondolenceTypePicker">
<van-picker :columns="condolenceTypeColumns" @cancel="closePopup('showCondolenceTypePicker')"
@confirm="onCondolenceTypeConfirm" show-toolbar></van-picker>
</van-popup>
@@ -604,7 +612,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showCondolenceWayPicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showCondolenceWayPicker">
<van-picker :columns="condolenceWayColumns" @cancel="closePopup('showCondolenceWayPicker')"
@confirm="onCondolenceWayConfirm" show-toolbar></van-picker>
</van-popup>
@@ -627,7 +635,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showCondolenceTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showCondolenceTimePicker">
<van-datetime-picker type="date" title="选择慰问时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showCondolenceTimePicker')"
@confirm="onCondolenceTimeConfirm"></van-datetime-picker>
@@ -644,7 +652,7 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showMarryTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showMarryTimePicker">
<van-datetime-picker type="date" title="选择结婚时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showMarryTimePicker')"
@confirm="onDateConfirm('marryTime', 'showMarryTimePicker', $event)"></van-datetime-picker>
@@ -661,7 +669,7 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showFertilityTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showFertilityTimePicker">
<van-datetime-picker type="date" title="选择生育时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showFertilityTimePicker')"
@confirm="onDateConfirm('fertilityTime', 'showFertilityTimePicker', $event)"></van-datetime-picker>
@@ -681,7 +689,7 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showHospitalizationStartTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showHospitalizationStartTimePicker">
<van-datetime-picker type="date" title="选择住院开始时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showHospitalizationStartTimePicker')"
@confirm="onDateConfirm('hospitalizationStartTime', 'showHospitalizationStartTimePicker', $event)"></van-datetime-picker>
@@ -696,7 +704,7 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showHospitalizationEndTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showHospitalizationEndTimePicker">
<van-datetime-picker type="date" title="选择住院结束时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showHospitalizationEndTimePicker')"
@confirm="onDateConfirm('hospitalizationEndTime', 'showHospitalizationEndTimePicker', $event)"></van-datetime-picker>
@@ -716,7 +724,7 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showDeathTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showDeathTimePicker">
<van-datetime-picker type="date" title="选择去世时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showDeathTimePicker')"
@confirm="onDateConfirm('deathTime', 'showDeathTimePicker', $event)"></van-datetime-picker>
@@ -731,13 +739,13 @@ layout("/layouts/platform_h5.html"){
clickable
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showRelationshipPicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showRelationshipPicker">
<van-picker :columns="relationshipColumns" @cancel="closePopup('showRelationshipPicker')"
@confirm="onRelationshipConfirm" show-toolbar></van-picker>
</van-popup>
</template>
<van-field label="参加随行人员"
<van-field class="more-text" label="参加随行人员"
v-model="formData.participants"
placeholder="请填写参加随行人员"
type="textarea"
@@ -745,13 +753,13 @@ layout("/layouts/platform_h5.html"){
autosize
:rules="[{ required: true, message: '请填写参加随行人员' }]"
required></van-field>
<van-field label="报销事由"
<van-field class="more-text" label="报销事由"
v-model="formData.paymentNotes"
placeholder="请填写报销事由"
type="textarea"
rows="2"
autosize></van-field>
<van-field label="备注"
<van-field class="more-text" label="备注"
v-model="formData.notes"
placeholder="需要填写:发票编号、开具方、金额"
type="textarea"
@@ -790,7 +798,7 @@ layout("/layouts/platform_h5.html"){
required
is-link
></van-field>
<van-popup position="bottom" round v-model:show="showActivityTimePicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showActivityTimePicker">
<van-datetime-picker type="date" title="选择活动时间" :min-date="minDate" :max-date="maxDate"
@cancel="closePopup('showActivityTimePicker')"
@confirm="onActivityTimeConfirm"></van-datetime-picker>
@@ -811,7 +819,8 @@ layout("/layouts/platform_h5.html"){
></h5-file-upload>
</template>
</van-field>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group class="form-section">
<div class="payment-section">
<div class="payment-toolbar">
@@ -900,11 +909,11 @@ layout("/layouts/platform_h5.html"){
<span class="payment-dot" :class="{active: index === currentPaymentIndex}" v-for="(item, index) in paymentInfoRows"></span>
</div>
</div>
<van-popup position="bottom" round v-model:show="showPaymentWayPicker">
<van-popup position="bottom" round :close-on-click-overlay="true" v-model:show="showPaymentWayPicker">
<van-picker :columns="paymentWayColumns" @cancel="closePopup('showPaymentWayPicker')"
@confirm="onPaymentWayConfirm" show-toolbar></van-picker>
</van-popup>
<van-action-sheet v-model="payerSelectShow" title="付款人" class="height100">
<van-action-sheet v-model="payerSelectShow" :close-on-click-overlay="true" title="付款人" class="height100">
<van-search
v-model="payerSearchKeyword"
:show-action="false"
@@ -926,7 +935,8 @@ layout("/layouts/platform_h5.html"){
<van-empty description="暂无数据" v-else></van-empty>
</van-action-sheet>
</van-cell-group>
<van-cell-group title="电子发票" class="form-section" v-if="formData.reimburseProject">
<van-cell-group class="form-section" v-if="formData.reimburseProject">
<div class="form-section__title">电子发票</div>
<van-field label="报销金额"
v-model="formData.money"
placeholder="自动汇总发票金额"
@@ -936,7 +946,7 @@ layout("/layouts/platform_h5.html"){
placeholder="自动统计发票数量"
readonly></van-field>
<!-- 支付内容 -->
<van-field label="支付内容"
<van-field class="more-text" label="支付内容"
v-model="formData.paymentNotes"
placeholder="请填写支付内容"
type="textarea"
@@ -952,6 +962,8 @@ layout("/layouts/platform_h5.html"){
<template #right-icon>
<van-switch v-model="formData.invoiceVerifyEnabled"
size="22px"
active-color="#1989fa"
inactive-color="#d8e0ea"
@change="handleVerifySwitchChange"></van-switch>
</template>
</van-cell>
@@ -966,9 +978,9 @@ layout("/layouts/platform_h5.html"){
<div class="invoice-detail-line">项目名称:{{item.itemName || '未填写'}}</div>
<div class="invoice-detail-line" v-if="getInvoiceFile(item)">文件:{{getInvoiceFile(item).name || getInvoiceFile(item).filename || '已上传'}}</div>
<div class="invoice-detail-actions">
<van-button native-type="button" size="mini" plain type="info" @click="previewInvoiceFile(item)">预览</van-button>
<van-button native-type="button" size="mini" plain type="primary" @click="openInvoiceDialog('edit', item, index)">编辑</van-button>
<van-button native-type="button" size="mini" plain type="danger" @click="removeInvoiceDetail(index)">删除</van-button>
<van-button native-type="button" size="mini" plain type="info" icon="eye-o" @click="previewInvoiceFile(item)">预览</van-button>
<van-button native-type="button" size="mini" plain type="primary" icon="edit" @click="openInvoiceDialog('edit', item, index)">编辑</van-button>
<van-button native-type="button" size="mini" plain type="danger" icon="delete-o" @click="removeInvoiceDetail(index)">删除</van-button>
</div>
</div>
</template>
@@ -985,22 +997,22 @@ layout("/layouts/platform_h5.html"){
<!-- 提交按钮 -->
<div class="form-actions">
<van-button plain @click="onSave" type="info">保存</van-button>
<van-button @click="onSubmit" type="primary" v-if="!taskId">提交</van-button>
<van-button @click="onSubmitAgain" type="primary" v-else>提交</van-button>
<van-button plain @click="onSave" type="info">保存申请</van-button>
<van-button @click="onSubmit" type="primary" v-if="!taskId">提交申请</van-button>
<van-button @click="onSubmitAgain" type="primary" v-else>提交申请</van-button>
</div>
</van-form>
</div>
<van-popup v-model:show="invoiceDialogVisible" position="bottom" round class="invoice-popup">
<van-popup v-model:show="invoiceDialogVisible" position="bottom" round :close-on-click-overlay="true" class="invoice-popup">
<van-nav-bar :title="invoiceDialogMode === 'edit' ? '编辑发票明细' : '新增发票明细'"
left-text="取消"
left-arrow
@click-left="invoiceDialogVisible = false"></van-nav-bar>
@click-left="closePopup('invoiceDialogVisible')"></van-nav-bar>
<div class="invoice-popup-body">
<div class="invoice-recognizing-mask" v-if="recognizingInvoice">
<van-loading type="spinner" color="#1677ff" size="24px">发票识别中...</van-loading>
</div>
<van-cell-group>
<van-cell-group class="invoice-popup-card">
<van-field class="more-text" label="发票文件" required>
<template #input>
<h5-file-upload
@@ -1040,13 +1052,15 @@ layout("/layouts/platform_h5.html"){
rows="2"
autosize></van-field>
</van-cell-group>
<div class="invoice-tip" style="padding: 8px 16px;">上传发票文件后会自动识别发票信息;开启自动校验时会同步校验发票是否重复。</div>
<div class="invoice-tip invoice-popup-tip">上传发票文件后会自动识别发票信息;开启自动校验时会同步校验发票是否重复。</div>
</div>
<div class="invoice-popup-actions">
<van-button native-type="button" block plain @click="invoiceDialogVisible = false">取消</van-button>
<van-button native-type="button" block plain type="info" @click="closePopup('invoiceDialogVisible')">取消</van-button>
<van-button native-type="button" block type="primary" @click="saveInvoiceDetail">保存</van-button>
</div>
</van-popup>
<van-dialog :value="submitConfirmVisible" title="温馨提示" message="您确定要提交吗?审核通过后可前往 PC 端我的报销打印。" show-cancel-button :confirm-button-loading="formLoading" @confirm="submitConfirmed" @cancel="closeSubmitConfirm"></van-dialog>
<van-dialog :value="removeInvoiceConfirmVisible" title="提示" message="确定删除这条发票明细吗?" show-cancel-button @confirm="confirmRemoveInvoiceDetail" @cancel="closeRemoveInvoiceConfirm"></van-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
@@ -1127,6 +1141,20 @@ layout("/layouts/platform_h5.html"){
files: []
},
invoiceDialogVisible: false,
submitConfirmVisible: false,
removeInvoiceConfirmVisible: false,
formLoading: false,
pendingSubmitAction: "",
pendingPaymentDetails: [],
pendingInvoiceRemoveIndex: -1,
historyLayerUnsubscribe: null,
popupKeys: [
"showReimburseFundSourcePicker", "showReimburseProjectPicker", "showClubPicker", "showCondolenceSearch",
"showCondolenceTypePicker", "showCondolenceWayPicker", "showCondolenceTimePicker", "showMarryTimePicker",
"showFertilityTimePicker", "showHospitalizationStartTimePicker", "showHospitalizationEndTimePicker",
"showDeathTimePicker", "showRelationshipPicker", "showActivityTimePicker", "showPaymentWayPicker",
"payerSelectShow", "invoiceDialogVisible"
],
invoiceDialogMode: "add",
detailFormIndex: -1,
lastRecognizedFileId: "",
@@ -1239,7 +1267,7 @@ layout("/layouts/platform_h5.html"){
const file = newVal && newVal.length ? newVal[0] : null
const currentFileId = this.getInvoiceFileId(file)
if (!currentFileId) {
this.lastRecognizedFileId = ""
this.$set(this, "lastRecognizedFileId", "")
return
}
if (currentFileId === this.lastRecognizedFileId) {
@@ -1298,6 +1326,49 @@ layout("/layouts/platform_h5.html"){
closePopup(key) {
this.$set(this, key, false)
},
// 所有选择器与发票弹窗共用全局历史栈,遮罩关闭和浏览器返回键都只关闭当前弹层。
syncPopupHistory(key, visible) {
const manager = window.h5HistoryLayerManager
if (!manager) return
const layerName = "union-reimburse-apply-" + key
if (visible) { manager.open(layerName); return }
manager.close(layerName)
},
onNavBack() {
const manager = window.h5HistoryLayerManager
if (manager && manager.stack.length) { manager.close(); return }
historyBack()
},
openSubmitConfirm(action, paymentDetails) {
this.$set(this, "pendingSubmitAction", action)
this.$set(this, "pendingPaymentDetails", paymentDetails)
const manager = window.h5HistoryLayerManager
if (manager) { manager.open("union-reimburse-apply-submit-confirm"); return }
this.$set(this, "submitConfirmVisible", true)
},
closeSubmitConfirm(afterClose) {
const manager = window.h5HistoryLayerManager
if (manager) { manager.close("union-reimburse-apply-submit-confirm", afterClose); return }
this.$set(this, "submitConfirmVisible", false)
if (afterClose) afterClose()
},
openRemoveInvoiceConfirm(index) {
this.$set(this, "pendingInvoiceRemoveIndex", index)
const manager = window.h5HistoryLayerManager
if (manager) { manager.open("union-reimburse-apply-remove-invoice-confirm"); return }
this.$set(this, "removeInvoiceConfirmVisible", true)
},
closeRemoveInvoiceConfirm(afterClose) {
const manager = window.h5HistoryLayerManager
if (manager) { manager.close("union-reimburse-apply-remove-invoice-confirm", afterClose); return }
this.$set(this, "removeInvoiceConfirmVisible", false)
if (afterClose) afterClose()
},
navigateToMine() {
const manager = window.h5HistoryLayerManager
if (manager && manager.closeAllAndNavigate("/platform/unionReimburse/mine/h5")) return
this.$pjaxReplace("/platform/unionReimburse/mine/h5")
},
openDatePicker(key) {
this.openPopup(key)
},
@@ -1740,10 +1811,11 @@ layout("/layouts/platform_h5.html"){
})
},
removeInvoiceDetail(index) {
this.$dialog.confirm({
title: "提示",
message: "确定删除这条发票明细吗?"
}).then(() => {
this.openRemoveInvoiceConfirm(index)
},
confirmRemoveInvoiceDetail() {
const index = this.pendingInvoiceRemoveIndex
this.closeRemoveInvoiceConfirm(() => {
this.formData.invoiceDetails.splice(index, 1)
this.rebuildInvoiceSummary()
})
@@ -1815,11 +1887,12 @@ layout("/layouts/platform_h5.html"){
this.$set(this, "searchKeyword", "")
this.$set(this, "userOptions", [])
},
async userRemoteMethod(event) {
userRemoteMethod(event) {
if (event) {
const userOptions = await this.selectQueryUser(event)
this.$set(this, "userOptions", userOptions)
this.$set(this, "certifierUserSelectShow", true)
this.selectQueryUser(event).then((userOptions) => {
this.$set(this, "userOptions", userOptions)
this.$set(this, "certifierUserSelectShow", true)
})
}
},
selectQueryUser(keyword) {
@@ -1991,29 +2064,29 @@ layout("/layouts/platform_h5.html"){
}
})
},
async onSave() {
onSave() {
this.normalizeContactFields()
this.normalizeCondolenceDateFields()
this.rebuildInvoiceSummary()
const paymentDetails = this.preparePaymentDetailsForSubmit()
this.$set(this, "formLoading", true)
this.$axios.post("/platform/unionReimburse/apply/save", { data: JSON.stringify(this.buildSubmitFormData(paymentDetails)) })
.then((res) => {
this.$toast.clear()
if (res.code === 0) {
this.$toast.success(res.msg)
this.$pjaxReplace("/platform/unionReimburse/mine/h5")
this.navigateToMine()
} else {
this.$toast.fail(res.msg || "保存失败")
}
})
.catch(err => {
this.$toast.clear()
this.$toast.fail(this.getRequestErrorMessage(err, "保存失败"))
}).catch((err) => {
this.$toast.clear(); this.$toast.fail(this.getRequestErrorMessage(err, "保存失败"))
}).finally(() => {
this.$set(this, "formLoading", false)
})
},
// 提交
async onSubmit() {
onSubmit() {
this.normalizeContactFields()
this.normalizeCondolenceDateFields()
this.rebuildInvoiceSummary()
@@ -2029,30 +2102,11 @@ layout("/layouts/platform_h5.html"){
if (!this.validateInvoiceDetailsBeforeSubmit()) {
return
}
this.$dialog.confirm({
title: "温馨提示",
message: "您确定要提交吗?审核通过后可前往pc端我的报销打印!"
}).then(() => {
this.$axios.post("/platform/unionReimburse/apply/submit", { data: JSON.stringify(this.buildSubmitFormData(paymentDetails)) })
.then(res => {
this.$toast.clear()
if (res.code === 0) {
this.$toast.success("提交成功")
this.$pjaxReplace("/platform/unionReimburse/mine/h5")
} else {
this.$toast.fail(res.msg || "提交失败")
}
})
.catch(err => {
this.$toast.clear()
this.$toast.fail(this.getRequestErrorMessage(err, "提交失败"))
})
})
this.openSubmitConfirm("submit", paymentDetails)
})
},
// 再次提交
async onSubmitAgain() {
onSubmitAgain() {
this.normalizeContactFields()
this.normalizeCondolenceDateFields()
this.rebuildInvoiceSummary()
@@ -2068,27 +2122,24 @@ layout("/layouts/platform_h5.html"){
if (!this.validateInvoiceDetailsBeforeSubmit()) {
return
}
this.$dialog.confirm({
title: "温馨提示",
message: "您确定要提交吗?审核通过后可前往pc端我的报销打印!"
}).then(() => {
this.$axios.post("/platform/unionReimburse/apply/submitAgain", {
data: JSON.stringify(this.buildSubmitFormData(paymentDetails)),
taskId: GetQueryString("taskId")
}).then(res => {
this.$toast.clear()
if (res.code === 0) {
this.$toast.success("提交成功")
setTimeout(() => {
this.$pjaxReplace("/platform/unionReimburse/mine/h5")
}, 1500)
} else {
this.$toast.fail(res.msg || "提交失败")
}
}).catch(err => {
this.$toast.clear()
this.$toast.fail(this.getRequestErrorMessage(err, "提交失败"))
})
this.openSubmitConfirm("submitAgain", paymentDetails)
})
},
submitConfirmed() {
const action = this.pendingSubmitAction
const paymentDetails = this.pendingPaymentDetails
this.closeSubmitConfirm(() => {
this.$set(this, "formLoading", true)
const api = action === "submitAgain" ? "/platform/unionReimburse/apply/submitAgain" : "/platform/unionReimburse/apply/submit"
const params = {data: JSON.stringify(this.buildSubmitFormData(paymentDetails))}
if (action === "submitAgain") params.taskId = GetQueryString("taskId")
this.$axios.post(api, params).then((res) => {
this.$toast.clear()
if (res.code === 0) { this.$toast.success("提交成功"); this.navigateToMine() } else { this.$toast.fail(res.msg || "提交失败") }
}).catch((err) => {
this.$toast.clear(); this.$toast.fail(this.getRequestErrorMessage(err, "提交失败"))
}).finally(() => {
this.$set(this, "formLoading", false)
})
})
},
@@ -2218,46 +2269,34 @@ layout("/layouts/platform_h5.html"){
const project = this.descOptions.find(item => item.reimburseProject === projectCode)
return project ? project.projectTip : ""
},
async initDictOptions() {
// 报销类别
const reimburseTypeData = await this.$businessTool.getDictOptions("UNION_REIMBURSE_TYPE")
this.reimburseTypeColumns = reimburseTypeData.map(item => {
return { value: item.code, text: item.name }
initDictOptions() {
return this.$businessTool.getDictOptions("UNION_REIMBURSE_TYPE").then((reimburseTypeData) => {
this.$set(this, "reimburseTypeColumns", reimburseTypeData.map(item => ({value: item.code, text: item.name})))
return this.$businessTool.getDictOptions("UNION_REIMBURSE_PAYMENT_WAY")
}).then((paymentWayData) => {
this.$set(this, "paymentWayColumns", paymentWayData.map(item => ({value: item.code, text: item.name})))
return this.$businessTool.getDictOptions("UNION_REIMBURSE_PROJECT")
}).then((reimburseProjectData) => {
this.$set(this, "reimburseProjectColumns", reimburseProjectData.map(item => ({value: item.code, text: item.name})))
this.$set(this, "reimburseProjectList", reimburseProjectData)
return this.$businessTool.getDictOptions("UNION_REIMBURSE_FUND_SOURCE")
}).then((reimburseFundSourceData) => {
this.$set(this, "reimburseFundSourceColumns", reimburseFundSourceData.map(item => ({value: item.code, text: item.name})))
this.filterReimburseFundSources()
})
// 支付方式
const paymentWayData = await this.$businessTool.getDictOptions("UNION_REIMBURSE_PAYMENT_WAY")
this.paymentWayColumns = paymentWayData.map(item => {
return { value: item.code, text: item.name }
})
// 报销项目
const reimburseProjectData = await this.$businessTool.getDictOptions("UNION_REIMBURSE_PROJECT")
this.reimburseProjectColumns = reimburseProjectData.map(item => {
return { value: item.code, text: item.name }
})
this.reimburseProjectList = reimburseProjectData // 保存原始数据
// 报销经费来源
const reimburseFundSourceData = await this.$businessTool.getDictOptions("UNION_REIMBURSE_FUND_SOURCE")
this.reimburseFundSourceColumns = reimburseFundSourceData.map(item => {
return { value: item.code, text: item.name }
})
// 添加经费来源区分
await this.filterReimburseFundSources()
},
// 查询慰问对象
queryCondolence(keyword) {
this.$axios.post("/platform/UnionReimburse/apply/listUser", { keyword: keyword }).then((res) => {
if (res.code === 0) {
this.condolenceOptions = res.data
this.condolenceColumns = res.data.map(item => {
this.$set(this, "condolenceOptions", res.data)
this.$set(this, "condolenceColumns", res.data.map(item => {
return {
text: item.userName + "" + item.loginName + "" + "" + item.unitName + "" + "" + item.sex + "",
value: item.id
}
})
}))
}
})
},
@@ -2305,10 +2344,10 @@ layout("/layouts/platform_h5.html"){
queryCondolenceType() {
return this.$axios.post("/platform/unionReimburse/apply/queryCondolenceType")
.then((resp) => {
this.typeOptions = resp.data
this.condolenceTypeColumns = resp.data.map(item => {
this.$set(this, "typeOptions", resp.data)
this.$set(this, "condolenceTypeColumns", resp.data.map(item => {
return { text: item.name, value: item.id }
})
}))
})
},
// 慰问时间确认事件
@@ -2328,10 +2367,10 @@ layout("/layouts/platform_h5.html"){
// 查询社团
queryClubs() {
return this.$businessTool.listClubByRole().then((res) => {
this.clubOptions = res
this.clubColumns = res.map(item => {
this.$set(this, "clubOptions", res)
this.$set(this, "clubColumns", res.map(item => {
return { text: item.clubName, value: item.id }
})
}))
if (res && res.length === 1 && this.formData.reimburseFundSource === "UNION_REIMBURSE_FUND_SOURCE_3" && !this.formData.clubId) {
this.$set(this.formData, "clubId", res[0].id)
this.$set(this.formData, "clubName", res[0].clubName)
@@ -2404,7 +2443,7 @@ layout("/layouts/platform_h5.html"){
})
},
// 添加经费来源区分
async filterReimburseFundSources() {
filterReimburseFundSources() {
const filteredSources = []
// 原角色过滤逻辑保留如下,手机端现统一改为按权限控制,与 PC 端保持一致
@@ -2459,16 +2498,16 @@ layout("/layouts/platform_h5.html"){
}
// 更新经费来源选项
this.reimburseFundSourceColumns = filteredSources
this.$set(this, "reimburseFundSourceColumns", filteredSources)
},
init() {
this.bizId = GetQueryString("bizId")
this.$set(this, "bizId", GetQueryString("bizId"))
if (this.bizId) {
this.$axios.post("/platform/unionReimburse/apply/info", { id: this.bizId }).then((res) => {
if (res.code === 0) {
// 先保存原始数据
const originalData = { ...res.data }
this.formData = this.ensureFormData(res.data)
this.$set(this, "formData", this.ensureFormData(res.data))
this.normalizeLoadedFormData(originalData)
// 处理报销类别显示
@@ -2560,7 +2599,7 @@ layout("/layouts/platform_h5.html"){
const user = this.$store.state.user
this.$set(this.formData, "userName", user.username)
this.$set(this.formData, "loginName", user.loginname)
this.$set(this.formData, "mobile", user.mobile)
this.$set(this.formData, "mobile", user.mobile || "")
this.$set(this.formData, "userId", user.id)
this.$set(this.formData, "unitId", user.unitId)
this.$set(this.formData, "unitName", user.unit.name)
@@ -2579,12 +2618,27 @@ layout("/layouts/platform_h5.html"){
}
},
async created() {
await this.initDictOptions()
await this.queryProjectDesc()
await this.queryCondolenceType()
await this.queryClubs()
await this.init()
created() {
const manager = window.h5HistoryLayerManager
if (manager) {
manager.ensureRegistered("union-reimburse-apply")
this.$set(this, "historyLayerUnsubscribe", manager.subscribe((layers) => {
this.popupKeys.forEach((key) => {
this.$set(this, key, layers.includes("union-reimburse-apply-" + key))
})
this.$set(this, "submitConfirmVisible", layers.includes("union-reimburse-apply-submit-confirm"))
this.$set(this, "removeInvoiceConfirmVisible", layers.includes("union-reimburse-apply-remove-invoice-confirm"))
}))
}
this.popupKeys.forEach((key) => {
this.$watch(key, (visible) => { this.syncPopupHistory(key, visible) })
})
this.initDictOptions().then(() => this.queryProjectDesc()).then(() => this.queryCondolenceType()).then(() => this.queryClubs()).then(() => this.init())
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
const manager = window.h5HistoryLayerManager
if (manager) manager.unregister("union-reimburse-apply")
}
})
</script>
@@ -2,71 +2,57 @@
layout("/layouts/platform_h5.html"){
#-->
<style>
.state-primary {
color: #409eff;
}
.state-success {
color: #67c23a;
}
.state-danger {
color: #f56c6c;
}
<style id="style-union-reimburse-h5">
<!--#include('../common/unionReimburse.css'){}#-->
</style>
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="报销统计" placeholder fixed></van-nav-bar>
<div id="app" v-cloak class="union-reimburse-page">
<van-nav-bar @click-left="onNavBack" left-arrow left-text="返回" title="报销统计" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="yearList" @change="doSearch" v-model="pageForm.year"></van-dropdown-item>
</van-dropdown-menu>
<van-search
:reverse-color="false"
:show-action="false"
@search="doSearch"
input-align="left"
placeholder="请输入经办人搜索"
v-model="pageForm.userName"
></van-search>
<div class="union-reimburse-sticky">
<van-search class="union-reimburse-search" :reverse-color="false" :show-action="false" @search="doSearch" @clear="doSearch" input-align="left" placeholder="请输入姓名或工号搜索" v-model="pageForm.searchKeyword"></van-search>
<van-dropdown-menu class="union-reimburse-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="yearList" @change="doSearch" v-model="pageForm.year"></van-dropdown-item>
<van-dropdown-item :options="reimburseProjectOptions" @change="doSearch" v-model="pageForm.reimburseProject"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<table-list api="/platform/unionReimburse/collect/pageData" :page_form.sync="pageForm" ref="tableListRef"
@ready="doSearch" >
<main class="union-reimburse-list-content">
<table-list api="/platform/unionReimburse/collect/pageData" :page_form.sync="pageForm" ref="tableListRef" :show_loading_overlay="false"
@ready="doSearch" @loading-change="onLoadingChange">
<template #header="{row}"><div v-if="row" class="union-reimburse-card-header"><div class="union-reimburse-card-header__heading"><div class="union-reimburse-card-header__title">{{row.userName || '--'}}</div><div class="union-reimburse-card-header__sub">工号:{{row.loginName || '--'}}</div></div><van-tag :class="['union-reimburse-card-header__status', getStatusClass(row.stateId)]">{{getStateName(row.stateId)}}</van-tag></div></template>
<template v-slot="{index,row}">
<table-column label="经办人工号">{{row.loginName}}</table-column>
<table-column label="姓名">{{row.userName}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="所属单位">{{row.unitName}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime)}}</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject)}}</table-column>
<table-column label="备注">{{getRemarkText(row)}}</table-column>
<table-column label="申请状态">
<span :class="getStateClass(row.stateId)">{{getStateName(row.stateId)}}</span>
</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject) || '--'}}</table-column>
<table-column label="" label_suffix="">{{getRemarkText(row) || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName || '--'}}</table-column>
<table-column label="所属单位">{{row.unitName || '--'}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime) || '--'}}</table-column>
<table-column label="申请金额">{{formatMoney(row.money)}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<!-- <div class="action-btn" @click="onEdit(row)" v-if="row.taskKey === 'startTask' || !row.instanceId">-->
<!-- <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>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
<div class="action-btn delete" @click="onDelete(row)" v-if="row.taskKey === 'startTask' || !row.instanceId">
<i class="fa fa-trash"></i>
<van-icon name="delete-o"></van-icon>
<span>删除</span>
</div>
</template>
<template #empty><div v-if="listLoading" class="union-reimburse-list-skeleton"><div v-for="item in 2" :key="item" class="union-reimburse-skeleton-card"><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--title"></div><div class="union-reimburse-skeleton-line"></div><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--short"></div></div></div><div v-else class="union-reimburse-empty"><img src="/assets/mobile/img/unionReimburse/union-reimburse-empty.png" alt="暂无报销统计记录插画"><div class="union-reimburse-empty__title">暂无报销统计记录</div><div class="union-reimburse-empty__hint">当前筛选条件下暂时没有报销记录</div></div></template>
</table-list>
</main>
<union-reimburse-info ref="unionReimburseInfoRef"></union-reimburse-info>
<union-reimburse-info ref="unionReimburseInfoRef" history-layer-name="union-reimburse-collect-info"></union-reimburse-info>
<van-dialog :value="confirmVisible" title="提示" :message="confirmMessage" show-cancel-button :confirm-button-loading="formLoading" @confirm="confirmPendingAction" @cancel="closeConfirm"></van-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -80,18 +66,32 @@ layout("/layouts/platform_h5.html"){
},
data() {
return {
viewShow: false,
listLoading: true,
formLoading: false,
confirmVisible: false,
pendingAction: "",
pendingRow: null,
historyLayerUnsubscribe: null,
yearList: [],
reimburseProjectOptions: [
{text: "全部项目", value: ""},
{text: "慰问", value: "UNION_REIMBURSE_PROJECT_1"},
{text: "文体活动", value: "UNION_REIMBURSE_PROJECT_2"},
{text: "日常活动", value: "UNION_REIMBURSE_PROJECT_3"},
{text: "专项活动", value: "UNION_REIMBURSE_PROJECT_4"}
],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: "",
year: "",
reimburseProject: ""
},
infoShow: false
}
},
computed: { confirmMessage() { return this.pendingAction === "revoke" ? "您确定要撤回申请吗?" : "您确定要删除吗?" } },
methods: {
// 手机端统计列表字段展示与我的报销列表保持一致。
getReimburseProjectName(reimburseProject) {
@@ -133,58 +133,35 @@ layout("/layouts/platform_h5.html"){
}
return stateId || ""
},
getStateClass(stateId) {
if (stateId == 3) {
return "state-success"
}
if (stateId == 4) {
return "state-danger"
}
return "state-primary"
},
getStatusClass(stateId) { if (stateId == 3) return "union-reimburse-card-header__status--success"; if (stateId == 4 || stateId == 5) return "union-reimburse-card-header__status--danger"; return "union-reimburse-card-header__status--pending" },
formatCreateTime(value) {
return value && this.$moment(value).isValid() ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : ""
},
formatMoney(value) {
if (value === null || value === undefined || value === "") return "--"
const amount = Number(value)
return isNaN(amount) ? value : amount.toFixed(2) + "元"
},
onView(row) {
this.showApprovalForm = false
this.$refs.unionReimburseInfoRef.onOpen(row)
this.$refs.unionReimburseInfoRef.onOpen(row, "报销统计详情")
},
onEdit(row) {
this.$pjaxReplace("/platform/unionReimburse/apply/h5?bizId=" + row.id + "&taskId=" + row.startTaskId)
},
onRevoke(row){
this.$dialog
.confirm({
title: "提示",
message: "您确定要撤销申请吗?"
})
.then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.startTaskId}).then((resp) => {
this.$toast.success(resp.msg)
this.doSearch()
})
})
},
onDelete(row) {
this.$dialog
.confirm({
title: "提示",
message: "您确定要删除吗?"
})
.then(() => {
this.$axios.post("/platform/unionReimburse/mine/delete", {id: row.id}).then((resp) => {
if (resp.code === 0) {
this.$toast.success(resp.msg)
this.doSearch()
}
})
})
},
onLoadingChange(state) { this.$set(this, "listLoading", state.loading) },
onNavBack() { const manager = window.h5HistoryLayerManager; if (manager && manager.stack.length) { manager.close(); return } historyBack() },
openConfirm(action, row) { this.$set(this, "pendingAction", action); this.$set(this, "pendingRow", row); const manager = window.h5HistoryLayerManager; if (manager) { manager.open("union-reimburse-collect-confirm"); return } this.$set(this, "confirmVisible", true) },
closeConfirm(afterClose) { const manager = window.h5HistoryLayerManager; if (manager) { manager.close("union-reimburse-collect-confirm", afterClose); return } this.$set(this, "confirmVisible", false); if (afterClose) afterClose() },
confirmPendingAction() { const action = this.pendingAction; const row = this.pendingRow; this.closeConfirm(() => { if (action === "revoke") { this.revoke(row) } else { this.deleteApplication(row) } }) },
onRevoke(row) { this.openConfirm("revoke", row) },
revoke(row) { this.$set(this, "formLoading", true); this.$axios.post("/flow/common/revokeTask", {taskId: row.startTaskId}).then((resp) => { if (resp.code === 0) { this.$toast.success(resp.msg); this.doSearch() } else { this.$toast.fail(resp.msg) } }).finally(() => { this.$set(this, "formLoading", false) }) },
onDelete(row) { this.openConfirm("delete", row) },
deleteApplication(row) { this.$set(this, "formLoading", true); this.$axios.post("/platform/unionReimburse/mine/delete", {id: row.id}).then((resp) => { if (resp.code === 0) { this.$toast.success(resp.msg); this.doSearch() } else { this.$toast.fail(resp.msg) } }).finally(() => { this.$set(this, "formLoading", false) }) },
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$set(this.pageForm, "pageNumber", 1)
this.$set(this.pageForm, "totalCount", 0)
this.$refs.tableListRef.doSearch()
})
},
@@ -198,7 +175,13 @@ layout("/layouts/platform_h5.html"){
},
created() {
this.initData()
const manager = window.h5HistoryLayerManager
if (manager) { manager.ensureRegistered("union-reimburse-collect"); this.$set(this, "historyLayerUnsubscribe", manager.subscribe((layers) => { this.$set(this, "confirmVisible", layers.includes("union-reimburse-collect-confirm")) })) }
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
const manager = window.h5HistoryLayerManager
if (manager) manager.unregister("union-reimburse-collect")
}
})
</script>
@@ -0,0 +1,472 @@
.union-reimburse-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
color: #40526a;
}
.union-reimburse-page .van-nav-bar,
.union-reimburse-page .van-sticky--fixed { background: #fff; }
.union-reimburse-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.union-reimburse-page .van-nav-bar .van-icon,
.union-reimburse-page .van-nav-bar .van-nav-bar__text { color: #1989fa; }
.union-reimburse-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.union-reimburse-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.union-reimburse-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.union-reimburse-search .van-field__control { color: #263548; font-size: 14px; }
.union-reimburse-search .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-filter {
margin-bottom: 8px;
overflow: hidden;
border-radius: 10px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.union-reimburse-filter .van-dropdown-menu__bar { height: 44px; box-shadow: none; }
.union-reimburse-filter .van-dropdown-menu__item,
.union-reimburse-filter .van-dropdown-menu__title { height: 44px; }
.union-reimburse-filter .van-dropdown-menu__item { align-items: center; justify-content: center; }
.union-reimburse-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
color: #66758a;
font-size: 14px;
line-height: 20px;
}
.union-reimburse-filter .van-dropdown-menu__title--active,
.union-reimburse-filter .van-dropdown-item__option--active,
.union-reimburse-filter .van-dropdown-item__option--active .van-dropdown-item__icon { color: #1989fa; }
.union-reimburse-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.union-reimburse-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.union-reimburse-list-content { padding: 0 12px; }
.union-reimburse-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.union-reimburse-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.union-reimburse-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
.union-reimburse-card-header__heading { min-width: 0; padding-right: 10px; flex: 1; }
.union-reimburse-card-header__title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.union-reimburse-card-header__sub { margin-top: 1px; color: #9ba6b6; font-size: 12px; line-height: 18px; }
.union-reimburse-card-header__status { display: inline-flex; flex: none; }
.union-reimburse-card-header__status.van-tag,
.union-reimburse-card-header__status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-card-header__status--pending { color: #1989fa; background: #eaf4ff; }
/* 报销成功与社团审核页 ProcessInstanceStateEnum 的“已完成”保持一致。 */
.union-reimburse-card-header__status--success { color: #67c23a; background: #f0f9eb; }
.union-reimburse-card-header__status--danger { color: #e35d5d; background: #fff0f0; }
.union-reimburse-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.union-reimburse-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-page .table-list-container .table-list-item .action-btn.delete { color: #f04444; background: #fff0f0; }
.union-reimburse-page .table-list-container .table-list-item .action-btn.review { color: #1989fa; background: #eaf4ff; }
.union-reimburse-page .table-list-container .table-list-item .action-btn .van-icon { margin-right: 5px; font-size: 13px; }
.union-reimburse-page .empty-state { padding: 0; }
.union-reimburse-list-skeleton { padding: 0; }
.union-reimburse-skeleton-card {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
}
.union-reimburse-skeleton-line {
height: 13px;
margin-bottom: 12px;
border-radius: 7px;
background: linear-gradient(90deg, #f2f3f5 25%, #e6e8eb 37%, #f2f3f5 63%);
background-size: 400% 100%;
animation: union-reimburse-skeleton-loading 1.4s ease infinite;
}
.union-reimburse-skeleton-line--title { width: 46%; height: 16px; }
.union-reimburse-skeleton-line--short { width: 60%; }
.union-reimburse-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.union-reimburse-empty img { display: block; width: 86%; max-width: 290px; height: auto; object-fit: contain; }
.union-reimburse-empty__title { margin-top: 14px; color: #50627a; font-size: 15px; font-weight: 600; line-height: 22px; }
.union-reimburse-empty__hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
/* 申请页沿用社团申请的卡片层级,保留报销业务原有字段与交互。 */
.union-reimburse-form { padding-bottom: 0; }
.union-reimburse-form .custom-nav { box-shadow: none; }
.union-reimburse-form .form-container { padding: 12px 15px calc(86px + env(safe-area-inset-bottom)); }
.union-reimburse-form .form-section {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-form .form-section .van-cell-group__title,
.union-reimburse-form .payment-toolbar,
.union-reimburse-form .invoice-toolbar {
position: relative;
min-height: 42px;
margin: 0;
padding: 12px 14px 8px 21px;
box-sizing: border-box;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
background: #fff;
}
.union-reimburse-form .form-section__title {
position: relative;
min-height: 41px;
margin: 0;
padding: 13px 15px 8px 24px;
box-sizing: border-box;
color: #1f2d3d;
font-size: 14px;
font-weight: 600;
line-height: 20px;
background: #fff;
}
.union-reimburse-form .form-section__title::before {
position: absolute;
top: 16px;
left: 15px;
width: 3px;
height: 14px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-form .form-section .van-cell-group__title::before,
.union-reimburse-form .payment-toolbar::before,
.union-reimburse-form .invoice-toolbar::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-form .form-section .van-cell { min-height: 46px; padding: 11px 15px; background: transparent; }
.union-reimburse-form .form-section .van-cell::after { right: 15px; left: 15px; border-color: #eef2f7; }
.union-reimburse-form .form-section .van-field__label { width: 105px; color: #596779; font-size: 13px; line-height: 24px; }
.union-reimburse-form .payment-detail-item .van-cell--required::before { left: 6px; color: #f06464; }
.union-reimburse-form .form-section .van-field__control { color: #40526a; font-size: 14px; line-height: 24px; }
.union-reimburse-form .form-section .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-form .more-text { display: block; }
.union-reimburse-form .more-text .van-field__label { width: auto; margin-bottom: 7px; }
.union-reimburse-form .more-text .van-field__control {
min-height: 88px;
padding: 10px 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.union-reimburse-form .payment-section,
.union-reimburse-form .invoice-section { padding: 0 0 14px; }
.union-reimburse-form .payment-toolbar,
.union-reimburse-form .invoice-toolbar { display: flex; align-items: center; justify-content: space-between; }
.union-reimburse-form .payment-toolbar .van-button,
.union-reimburse-form .invoice-toolbar .van-button {
height: 28px;
padding: 0 12px;
border: 0;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
}
.union-reimburse-form .payment-detail-item,
.union-reimburse-form .invoice-detail-item {
margin: 8px 14px 0;
overflow: hidden;
border: 1px solid #e6ebf2;
border-radius: 10px;
background: #fff;
box-shadow: none;
}
.union-reimburse-form .invoice-detail-item { padding: 0; }
.union-reimburse-form .invoice-detail-title {
min-height: 38px;
padding: 0 12px;
box-sizing: border-box;
color: #40526a;
font-size: 14px;
font-weight: 600;
line-height: 22px;
background: #f7f9fd;
border-bottom: 1px solid #eef2f7;
}
.union-reimburse-form .invoice-detail-line {
margin: 0;
padding: 8px 12px 0;
color: #718096;
font-size: 13px;
line-height: 20px;
word-break: break-all;
}
.union-reimburse-form .invoice-detail-line:last-of-type { padding-bottom: 8px; }
.union-reimburse-form .invoice-detail-actions,
.union-reimburse-form .payment-detail-actions {
min-height: 28px;
margin-top: 8px;
padding: 9px 12px;
box-sizing: border-box;
border-top: 1px solid #eef2f7;
background: #fff;
}
.union-reimburse-form .invoice-detail-actions { justify-content: flex-end; gap: 6px; }
.union-reimburse-form .invoice-detail-actions .van-button,
.union-reimburse-form .payment-detail-actions .payment-delete-btn {
height: 28px;
min-width: 52px;
padding: 0 10px;
border: 0;
border-radius: 14px;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-form .invoice-detail-actions .van-button--info,
.union-reimburse-form .invoice-detail-actions .van-button--primary {
color: #1989fa;
background: #eaf4ff;
}
.union-reimburse-form .invoice-detail-actions .van-button--danger,
.union-reimburse-form .payment-detail-actions .payment-delete-btn {
color: #f04444;
background: #fff0f0;
}
.union-reimburse-form .invoice-detail-actions .van-icon,
.union-reimburse-form .payment-detail-actions .payment-delete-btn .van-icon {
margin-right: 4px;
font-size: 13px;
}
.union-reimburse-form .payment-detail-item .van-cell { padding-right: 12px; padding-left: 12px; }
.union-reimburse-form .payment-detail-title {
min-height: 38px;
padding: 0 12px;
color: #40526a;
background: #f7f9fd;
border-bottom-color: #eef2f7;
}
.union-reimburse-form .invoice-tip { margin: 8px 14px 10px; border-color: #dce9f8; color: #718096; background: #f5f9ff; }
.union-reimburse-form .form-actions {
gap: 10px;
padding: 10px 15px calc(10px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(43, 73, 112, .09);
}
.union-reimburse-form .form-actions .van-button { height: 40px; border-radius: 8px; font-size: 14px; font-weight: 500; }
.union-reimburse-form .form-actions .van-button--primary { border-color: #1989fa; background: #1989fa; box-shadow: none; }
.union-reimburse-form .form-actions .van-button--info.van-button--plain { color: #1989fa; border-color: #1989fa; }
/* 新增、编辑发票弹框与申请页共用卡片、字段和操作按钮视觉规范。 */
.union-reimburse-form .invoice-popup {
overflow: hidden;
background: #f3f7fd;
}
.union-reimburse-form .invoice-popup .van-nav-bar { flex: none; box-shadow: 0 1px 6px rgba(43, 73, 112, .08); }
.union-reimburse-form .invoice-popup .van-nav-bar__title { color: #1f2d3d; font-size: 16px; font-weight: 600; }
.union-reimburse-form .invoice-popup-body {
padding: 12px 15px;
box-sizing: border-box;
background: #f3f7fd;
}
.union-reimburse-form .invoice-popup-card {
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-form .invoice-popup-card .van-cell { min-height: 46px; padding: 11px 15px; background: transparent; }
.union-reimburse-form .invoice-popup-card .van-cell::after { right: 15px; left: 15px; border-color: #eef2f7; }
.union-reimburse-form .invoice-popup-card .van-field__label { width: 105px; color: #596779; font-size: 13px; line-height: 24px; }
.union-reimburse-form .invoice-popup-card .van-field__control { color: #40526a; font-size: 14px; line-height: 24px; }
.union-reimburse-form .invoice-popup-card .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-form .invoice-popup-card .more-text { display: block; }
.union-reimburse-form .invoice-popup-card .more-text .van-field__label { width: auto; margin-bottom: 7px; }
.union-reimburse-form .invoice-popup-card .more-text .van-field__control {
min-height: 88px;
padding: 10px 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.union-reimburse-form .invoice-popup-tip {
margin: 10px 0 0;
padding: 9px 11px;
border-color: #dce9f8;
color: #718096;
background: #f5f9ff;
}
.union-reimburse-form .invoice-popup-actions {
flex: none;
gap: 10px;
padding: 10px 15px calc(10px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(43, 73, 112, .09);
}
.union-reimburse-form .invoice-popup-actions .van-button { height: 40px; border-radius: 8px; font-size: 14px; font-weight: 500; }
.union-reimburse-form .invoice-popup-actions .van-button--primary { border-color: #1989fa; background: #1989fa; box-shadow: none; }
.union-reimburse-form .invoice-popup-actions .van-button--info.van-button--plain { color: #1989fa; border-color: #1989fa; background: #fff; }
/* 审核卡片与社团审核保持相同的标题、必填标记和按钮规格。 */
.union-reimburse-approval { padding: 0; background: #f3f7fd; }
.union-reimburse-approval-card {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-approval-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.union-reimburse-approval-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-approval-field-label {
position: relative;
margin: 0;
padding: 10px 14px 6px;
color: #596779;
font-size: 12px;
line-height: 24px;
}
.union-reimburse-approval-required { position: absolute; left: 6px; color: #ee0a24; font-size: 12px; }
.union-reimburse-approval-card .van-field.union-reimburse-approval-field {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.union-reimburse-approval-card .van-field.union-reimburse-approval-field .van-field__control {
min-height: 150px;
color: #253247;
font-size: 12px;
line-height: 22px;
}
.union-reimburse-approval-card .van-field.union-reimburse-approval-field::after { display: none; }
.union-reimburse-approval-actions {
display: flex;
column-gap: 10px;
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
}
.union-reimburse-approval-actions .van-button { height: 40px; border-radius: 14px; font-size: 14px; }
@keyframes union-reimburse-skeleton-loading {
0% { background-position: 100% 50%; }
100% { background-position: 0 50%; }
}
@@ -2,14 +2,10 @@ const UNION_REIMBURSE_INFO = {
template:
/*language=HTML*/
`
<van-action-sheet v-model="visible" class="union-reimburse-info-sheet">
<div class="union-info-header">
<div class="union-info-header-title">查看详情</div>
<div class="union-info-close" @click="onClose">
<van-icon name="cross"></van-icon>
</div>
</div>
<div class="detail-container">
<van-popup v-model="visible" position="right" :style="{width: '100%', height: '100%'}" get-container="#app" :close-on-click-overlay="false" class="union-reimburse-info-sheet">
<div class="union-reimburse-info-detail">
<van-nav-bar :title="detailTitle" left-text="返回" left-arrow fixed @click-left="onClose"></van-nav-bar>
<div class="detail-container">
<div class="union-info-scroll">
<div class="union-info-section">
<div class="union-info-section-title">报销申请</div>
@@ -59,6 +55,8 @@ const UNION_REIMBURSE_INFO = {
</div>
<van-swipe ref="paymentInfoSwipe" :loop="false" :show-indicators="false" @change="handlePaymentSwipeChange">
<van-swipe-item v-for="(item, index) in paymentInfoRows" :key="index">
<div class="union-payment-card">
<div class="union-payment-card-title">支付信息{{ index + 1 }}</div>
<div class="union-info-row">
<div class="union-info-label">支付方式</div>
<div class="union-info-value">{{ getPaymentWayText(item.paymentWay) }}</div>
@@ -85,6 +83,7 @@ const UNION_REIMBURSE_INFO = {
<div class="union-info-value">{{ item.bankCardNumber }}</div>
</div>
</template>
</div>
</van-swipe-item>
</van-swipe>
</template>
@@ -272,12 +271,12 @@ const UNION_REIMBURSE_INFO = {
<!-- </van-cell-group>-->
<template v-for="(task,index) in doneTasks">
<div class="union-info-section">
<div class="process-title">{{ task.displayName }}</div>
<template v-if="task.ext.isFirstTaskNode">
<section class="union-info-section union-info-task-card">
<h3 class="union-info-section-title union-info-task-card__title">{{ task.displayName || '审核节点' }}</h3>
<template v-if="task.ext && task.ext.isFirstTaskNode">
<div class="union-info-row">
<div class="union-info-label">申请用户</div>
<div class="union-info-value">{{ task.ext.initiatorName}}({{task.ext.initiatorAccount}})</div>
<div class="union-info-value">{{ task.ext.initiatorName || '--' }}{{task.ext.initiatorAccount || '--'}}</div>
</div>
<div class="union-info-row">
<div class="union-info-label">申请时间</div>
@@ -286,14 +285,14 @@ const UNION_REIMBURSE_INFO = {
<div class="union-info-row">
<div class="union-info-label">办理结果</div>
<div class="union-info-value">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
</div>
</div>
</template>
<template v-else>
<div class="union-info-row">
<div class="union-info-label">办理用户</div>
<div class="union-info-value">{{ task.taskFormData.userName}}({{task.taskFormData.loginName}})</div>
<div class="union-info-value">{{ getTaskUserText(task) }}</div>
</div>
<div class="union-info-row">
<div class="union-info-label">办理时间</div>
@@ -302,36 +301,44 @@ const UNION_REIMBURSE_INFO = {
<div class="union-info-row">
<div class="union-info-label">办理结果</div>
<div class="union-info-value">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
</div>
</div>
<div class="union-info-row" v-if="!task.ext.isFirstTaskNode">
<div class="union-info-row">
<div class="union-info-label">办理意见</div>
<div class="union-info-value">{{ task.taskFormData.opinion }}</div>
<div class="union-info-value">{{ getTaskOpinion(task) }}</div>
</div>
<div class="union-info-row" v-if="!task.ext.isFirstTaskNode && task.ext.tf_userSign">
<div class="union-info-row" v-if="task.ext && task.ext.tf_userSign">
<div class="union-info-label">签字</div>
<div class="union-info-value">
<van-image :src="task.ext.tf_userSign" class="signature-image"></van-image>
</div>
</div>
</template>
</div>
</section>
</template>
<slot></slot>
</div>
</div>
<slot></slot>
</van-action-sheet>
</div>
</van-popup>
`,
dicts: ["UNION_REIMBURSE_TYPE", "UNION_REIMBURSE_PAYMENT_WAY", "UNION_REIMBURSE_FUND_SOURCE", "UNION_REIMBURSE_PROJECT", "PROCESS_TASK_SUBMIT_TYPE"],
props: {
historyLayerName: {
type: String,
default: "union-reimburse-info"
}
},
data() {
return {
visible: false,
viewData: {},
doneTasks: [],
row: null,
currentPaymentIndex: 0
currentPaymentIndex: 0,
detailTitle: "报销详情",
historyLayerUnsubscribe: null
}
},
computed: {
@@ -339,8 +346,29 @@ const UNION_REIMBURSE_INFO = {
return this.viewData.paymentDetails || []
}
},
watch: {
visible(value) {
const manager = window.h5HistoryLayerManager
if (!manager) return
if (value) {
manager.open(this.historyLayerName)
return
}
manager.close(this.historyLayerName)
}
},
mounted() {
this.injectInfoStyle()
const manager = window.h5HistoryLayerManager
if (manager) {
manager.ensureRegistered("union-reimburse-info")
this.$set(this, "historyLayerUnsubscribe", manager.subscribe((layers) => {
this.$set(this, "visible", layers.includes(this.historyLayerName))
}))
}
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
},
methods: {
injectInfoStyle() {
@@ -355,16 +383,19 @@ const UNION_REIMBURSE_INFO = {
styleNode.appendChild(document.createTextNode(this.$options.style))
document.head.appendChild(styleNode)
},
onOpen(row) {
this.row = row
this.visible = true
this.currentPaymentIndex = 0
onOpen(row, title) {
this.$set(this, "row", row)
this.$set(this, "detailTitle", title || "报销详情")
this.$set(this, "visible", true)
this.$set(this, "currentPaymentIndex", 0)
this.getInfo()
this.getDoneTasks()
},
// 关闭
// 由全局历史栈关闭详情,保证遮罩、关闭按钮和浏览器返回键遵循同一顺序。
onClose() {
this.visible = false
const manager = window.h5HistoryLayerManager
if (manager && manager.close(this.historyLayerName)) return
this.$set(this, "visible", false)
},
formatCreateTime(value) {
return value && this.$moment(value).isValid() ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : ""
@@ -386,6 +417,16 @@ const UNION_REIMBURSE_INFO = {
const matchedPaymentWay = this.dict.type.UNION_REIMBURSE_PAYMENT_WAY.find(item => item.code === paymentWay)
return matchedPaymentWay ? matchedPaymentWay.name : ""
},
getTaskUserText(task) {
const formData = task && task.taskFormData ? task.taskFormData : {}
const userName = formData.userName || formData.username || "--"
const loginName = formData.loginName || formData.loginname || "--"
return userName + "" + loginName + ""
},
getTaskOpinion(task) {
const formData = task && task.taskFormData ? task.taskFormData : {}
return formData.reviewOpinion || formData.opinion || formData.tf_opinion || "--"
},
buildViewData(data) {
const viewData = Object.assign({
files: [],
@@ -425,15 +466,15 @@ const UNION_REIMBURSE_INFO = {
}
},
handlePaymentSwipeChange(index) {
this.currentPaymentIndex = index
this.$set(this, "currentPaymentIndex", index)
},
swipeToPayment(index) {
if (!this.paymentInfoRows.length) {
this.currentPaymentIndex = 0
this.$set(this, "currentPaymentIndex", 0)
return
}
const targetIndex = Math.max(0, Math.min(index, this.paymentInfoRows.length - 1))
this.currentPaymentIndex = targetIndex
this.$set(this, "currentPaymentIndex", targetIndex)
this.$nextTick(() => {
if (this.$refs.paymentInfoSwipe && this.$refs.paymentInfoSwipe.swipeTo) {
this.$refs.paymentInfoSwipe.swipeTo(targetIndex)
@@ -456,8 +497,8 @@ const UNION_REIMBURSE_INFO = {
getInfo() {
this.$axios.post("/platform/unionReimburse/apply/info", { id: this.row.id }).then((res) => {
if (res.code === 0) {
this.viewData = this.buildViewData(res.data)
this.currentPaymentIndex = 0
this.$set(this, "viewData", this.buildViewData(res.data))
this.$set(this, "currentPaymentIndex", 0)
}
})
},
@@ -471,120 +512,131 @@ const UNION_REIMBURSE_INFO = {
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
this.$set(this, "doneTasks", res.data || [])
}
})
}
},
style: /*language=CSS*/ `
.union-reimburse-info-sheet {
height: 92vh;
display: flex;
flex-direction: column;
overflow: hidden;
background: #f3f7fd;
}
.union-reimburse-info-sheet .van-action-sheet__content {
.union-reimburse-info-detail {
height: 100%;
display: flex;
padding-top: 46px;
box-sizing: border-box;
flex-direction: column;
overflow: hidden;
background: #f3f7fd;
}
.union-info-header {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 20px;
.union-reimburse-info-detail > .van-nav-bar {
z-index: 20;
background: #fff;
border-top: 4px solid #1677ff;
border-bottom: 1px solid #e7ebf0;
}
.union-info-header-title {
color: #111827;
.union-reimburse-info-detail > .van-nav-bar .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 700;
font-weight: 600;
}
.union-info-close {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
color: #8b95a3;
font-size: 20px;
.union-reimburse-info-detail > .van-nav-bar .van-icon,
.union-reimburse-info-detail > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.detail-container {
flex: 1;
overflow: hidden;
background: #f2f4f7;
background: #f3f7fd;
}
.union-info-scroll {
height: 100%;
overflow-y: auto;
padding-bottom: 16px;
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.union-info-section {
overflow: hidden;
margin-bottom: 11px;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
margin-bottom: 10px;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-info-section-title {
display: flex;
align-items: center;
height: 44px;
padding: 0 16px;
color: #1f2937;
font-size: 14px;
font-weight: 700;
background: #f5f7fa;
border-bottom: 1px solid #e7ebf0;
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.union-info-section-title::before {
content: "";
width: 4px;
height: 16px;
margin-right: 8px;
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1677ff;
background: #1989fa;
content: "";
}
.union-info-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
min-height: 44px;
padding: 12px 20px;
border-bottom: 1px solid #edf0f4;
min-height: 43px;
padding: 10px 14px;
border-bottom: 1px solid #eef2f7;
box-sizing: border-box;
}
/* 查看详情中的流程节点字段与社团审核详情卡片保持一致的行高和内边距。 */
.union-info-task-card > .union-info-task-card__title {
margin: 0;
padding: 12px 14px 8px 21px;
}
.union-info-task-card .union-info-row {
min-height: 44px;
padding: 10px 14px;
}
.union-info-task-card .union-info-label,
.union-info-task-card .union-info-value {
line-height: 24px;
}
.union-info-row:last-child {
border-bottom: 0;
}
.union-info-label {
flex: 0 0 96px;
color: #8a94a6;
font-size: 14px;
line-height: 20px;
flex: 0 0 102px;
color: #596779;
font-size: 12px;
line-height: 22px;
}
.union-info-value {
flex: 1;
min-width: 0;
color: #1f2937;
font-size: 14px;
line-height: 20px;
text-align: right;
color: #4f5d70;
font-size: 12px;
line-height: 22px;
text-align: left;
word-break: break-all;
white-space: pre-line;
}
@@ -597,7 +649,7 @@ const UNION_REIMBURSE_INFO = {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 20px 4px;
padding: 10px 14px 3px;
background: #fff;
}
@@ -617,21 +669,44 @@ const UNION_REIMBURSE_INFO = {
background: #1677ff;
}
.union-payment-card {
margin: 8px 12px 12px;
overflow: hidden;
border: 1px solid #e6ebf2;
border-radius: 9px;
background: #fff;
}
.union-payment-card-title {
padding: 9px 12px;
color: #40526a;
font-size: 13px;
font-weight: 600;
line-height: 20px;
background: #f7f9fd;
border-bottom: 1px solid #eef2f7;
}
.union-payment-card .union-info-row {
padding-right: 12px;
padding-left: 12px;
}
.union-empty-text {
padding: 14px 20px;
padding: 12px 14px;
color: #9aa3af;
font-size: 13px;
}
.union-file-preview {
padding: 12px 20px 16px;
padding: 10px 14px 14px;
}
.union-invoice-subtitle {
display: flex;
align-items: center;
height: 40px;
padding: 0 20px;
padding: 0 14px;
color: #6b7280;
font-size: 13px;
background: #f5f7fa;
@@ -640,13 +715,13 @@ const UNION_REIMBURSE_INFO = {
}
.union-invoice-card-list {
padding: 12px 16px 4px;
padding: 10px 12px 0;
}
.union-invoice-card {
margin-bottom: 12px;
padding: 14px 14px 12px;
border-radius: 12px;
padding: 12px 12px 10px;
border-radius: 9px;
border: 1px solid #e6ebf2;
background: #fff;
box-shadow: 0 1px 3px rgba(31, 35, 41, 0.05);
@@ -663,17 +738,6 @@ const UNION_REIMBURSE_INFO = {
padding: 12px 0 0;
}
.process-title {
display: flex;
align-items: center;
height: 40px;
padding: 0 20px;
color: #1f2937;
font-size: 14px;
font-weight: 700;
background: #f5f7fa;
}
.signature-image {
width: 100%;
height: 120px;
@@ -2,63 +2,65 @@
layout("/layouts/platform_h5.html"){
#-->
<style>
.state-primary {
color: #409eff;
}
.state-success {
color: #67c23a;
}
.state-danger {
color: #f56c6c;
}
<style id="style-union-reimburse-h5">
<!--#include('../common/unionReimburse.css'){}#-->
</style>
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="我的报销" placeholder fixed></van-nav-bar>
<div id="app" v-cloak class="union-reimburse-page">
<van-nav-bar @click-left="onNavBack" left-arrow left-text="返回" title="我的报销" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="yearList" @change="doSearch" v-model="pageForm.year"></van-dropdown-item>
</van-dropdown-menu>
<div class="union-reimburse-sticky">
<van-search v-model="pageForm.searchKeyword" class="union-reimburse-search" placeholder="请输入姓名或工号搜索" shape="round" clearable @search="doSearch" @clear="doSearch"></van-search>
<van-dropdown-menu class="union-reimburse-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="yearList" @change="doSearch" v-model="pageForm.year"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<table-list api="/platform/unionReimburse/mine/pageData" :page_form.sync="pageForm" ref="tableListRef"
@ready="doSearch" >
<main class="union-reimburse-list-content">
<table-list api="/platform/unionReimburse/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" :show_loading_overlay="false"
@ready="doSearch" @loading-change="onLoadingChange">
<template #header="{row}">
<div v-if="row" class="union-reimburse-card-header">
<div class="union-reimburse-card-header__heading"><div class="union-reimburse-card-header__title">{{row.userName || '--'}}</div><div class="union-reimburse-card-header__sub">工号:{{row.loginName || '--'}}</div></div>
<enum-tag class="union-reimburse-card-header__status" v-if="row.instanceState" :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
<van-tag v-else :class="['union-reimburse-card-header__status', getStatusClass(row.stateId)]">{{getStateName(row.stateId)}}</van-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="经办人工号">{{row.loginName}}</table-column>
<table-column label="姓名">{{row.userName}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="所属单位">{{row.unitName}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime)}}</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject)}}</table-column>
<table-column label="备注">{{getRemarkText(row)}}</table-column>
<table-column label="申请状态">
<span :class="getStateClass(row.stateId)">{{getStateName(row.stateId)}}</span>
</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject) || '--'}}</table-column>
<table-column label="" label_suffix="">{{getRemarkText(row) || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName || '--'}}</table-column>
<table-column label="所属单位">{{row.unitName || '--'}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime) || '--'}}</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>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" @click="onEdit(row)" v-if="row.stateId == 1 || row.stateId == 5">
<i class="fa fa-edit"></i>
<van-icon name="edit"></van-icon>
<span>编辑</span>
</div>
<div class="action-btn delete" v-if="row.stateId == 2" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
<div class="action-btn delete" @click="onDelete(row)" v-if="row.stateId == 1">
<i class="fa fa-trash"></i>
<van-icon name="delete-o"></van-icon>
<span>删除</span>
</div>
</template>
<template #empty>
<div v-if="listLoading" class="union-reimburse-list-skeleton"><div v-for="item in 2" :key="item" class="union-reimburse-skeleton-card"><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--title"></div><div class="union-reimburse-skeleton-line"></div><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--short"></div></div></div>
<div v-else class="union-reimburse-empty"><img src="/assets/mobile/img/unionReimburse/union-reimburse-empty.png" alt="暂无报销记录插画"><div class="union-reimburse-empty__title">暂无报销记录</div><div class="union-reimburse-empty__hint">当前筛选条件下暂时没有报销申请</div></div>
</template>
</table-list>
</main>
<union-reimburse-info ref="unionReimburseInfoRef"></union-reimburse-info>
<union-reimburse-info ref="unionReimburseInfoRef" history-layer-name="union-reimburse-mine-info"></union-reimburse-info>
<van-dialog :value="confirmVisible" title="提示" :message="confirmMessage" show-cancel-button :confirm-button-loading="formLoading" @confirm="confirmPendingAction" @cancel="closeConfirm"></van-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -66,13 +68,18 @@ layout("/layouts/platform_h5.html"){
const vue = new Vue({
el: "#app",
store,
dicts: ["UNION_REIMBURSE_PROJECT"],
dicts: ["UNION_REIMBURSE_PROJECT", "PROCESS_INSTANCE_STATE"],
components: {
"union-reimburse-info":UNION_REIMBURSE_INFO
},
data() {
return {
viewShow: false,
listLoading: true,
formLoading: false,
confirmVisible: false,
pendingAction: "",
pendingRow: null,
historyLayerUnsubscribe: null,
yearList: [],
pageForm: {
pageNumber: 1,
@@ -84,6 +91,9 @@ layout("/layouts/platform_h5.html"){
infoShow: false
}
},
computed: {
confirmMessage() { return this.pendingAction === "revoke" ? "您确定要撤销申请吗?" : "您确定要删除吗?" }
},
methods: {
// 手机端列表的报销项目、备注、状态文案与 PC 端 mine 列表保持一致。
getReimburseProjectName(reimburseProject) {
@@ -125,58 +135,34 @@ layout("/layouts/platform_h5.html"){
}
return stateId || ""
},
getStateClass(stateId) {
if (stateId == 3) {
return "state-success"
}
if (stateId == 4) {
return "state-danger"
}
return "state-primary"
getStatusClass(stateId) {
if (stateId == 3) return "union-reimburse-card-header__status--success"
if (stateId == 4 || stateId == 5) return "union-reimburse-card-header__status--danger"
return "union-reimburse-card-header__status--pending"
},
formatCreateTime(value) {
return value && this.$moment(value).isValid() ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : ""
},
onView(row) {
this.showApprovalForm = false
this.$refs.unionReimburseInfoRef.onOpen(row)
this.$refs.unionReimburseInfoRef.onOpen(row, "报销申请详情")
},
onEdit(row) {
this.$pjaxReplace('/platform/unionReimburse/apply/h5?taskId=' + (row.startTaskId || '') + "&bizId=" + row.id)
},
onRevoke(row){
this.$dialog
.confirm({
title: "提示",
message: "您确定要撤销申请吗?"
})
.then(() => {
this.$axios.post("/platform/unionReimburse/mine/revokeTask", {id: row.id}).then((resp) => {
this.$toast.success(resp.msg)
this.doSearch()
})
})
},
onDelete(row) {
this.$dialog
.confirm({
title: "提示",
message: "您确定要删除吗?"
})
.then(() => {
this.$axios.post("/platform/unionReimburse/mine/delete", {id: row.id}).then((resp) => {
if (resp.code === 0) {
this.$toast.success(resp.msg)
this.doSearch()
}
})
})
},
onLoadingChange(state) { this.$set(this, "listLoading", state.loading) },
onNavBack() { const manager = window.h5HistoryLayerManager; if (manager && manager.stack.length) { manager.close(); return } historyBack() },
openConfirm(action, row) { this.$set(this, "pendingAction", action); this.$set(this, "pendingRow", row); const manager = window.h5HistoryLayerManager; if (manager) { manager.open("union-reimburse-mine-confirm"); return } this.$set(this, "confirmVisible", true) },
closeConfirm(afterClose) { const manager = window.h5HistoryLayerManager; if (manager) { manager.close("union-reimburse-mine-confirm", afterClose); return } this.$set(this, "confirmVisible", false); if (afterClose) afterClose() },
confirmPendingAction() { const action = this.pendingAction; const row = this.pendingRow; this.closeConfirm(() => { if (action === "revoke") { this.revoke(row) } else { this.deleteApplication(row) } }) },
onRevoke(row){ this.openConfirm("revoke", row) },
revoke(row) { this.$set(this, "formLoading", true); this.$axios.post("/platform/unionReimburse/mine/revokeTask", {id: row.id}).then((resp) => { if (resp.code === 0) { this.$toast.success(resp.msg); this.doSearch() } else { this.$toast.fail(resp.msg) } }).finally(() => { this.$set(this, "formLoading", false) }) },
onDelete(row) { this.openConfirm("delete", row) },
deleteApplication(row) { this.$set(this, "formLoading", true); this.$axios.post("/platform/unionReimburse/mine/delete", {id: row.id}).then((resp) => { if (resp.code === 0) { this.$toast.success(resp.msg); this.doSearch() } else { this.$toast.fail(resp.msg) } }).finally(() => { this.$set(this, "formLoading", false) }) },
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$set(this.pageForm, "pageNumber", 1)
this.$set(this.pageForm, "totalCount", 0)
this.$refs.tableListRef.doSearch()
})
},
@@ -190,7 +176,13 @@ layout("/layouts/platform_h5.html"){
},
created() {
this.initData()
const manager = window.h5HistoryLayerManager
if (manager) { manager.ensureRegistered("union-reimburse-mine"); this.$set(this, "historyLayerUnsubscribe", manager.subscribe((layers) => { this.$set(this, "confirmVisible", layers.includes("union-reimburse-mine-confirm")) })) }
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
const manager = window.h5HistoryLayerManager
if (manager) manager.unregister("union-reimburse-mine")
}
})
</script>
@@ -2,100 +2,64 @@
layout("/layouts/platform_h5.html"){
#-->
<style>
.state-primary {
color: #409eff;
}
.state-success {
color: #67c23a;
}
.state-danger {
color: #f56c6c;
}
<style id="style-union-reimburse-h5">
<!--#include('../common/unionReimburse.css'){}#-->
</style>
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="报销审核" placeholder fixed></van-nav-bar>
<div id="app" v-cloak class="union-reimburse-page">
<van-nav-bar @click-left="onNavBack" left-arrow left-text="返回" title="报销审核" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
:reverse-color="false"
:show-action="false"
@search="doSearch"
input-align="left"
placeholder="请输入经办人搜索"
v-model="pageForm.searchKeyword"
></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>
<div class="union-reimburse-sticky">
<van-search class="union-reimburse-search" :reverse-color="false" :show-action="false" @search="doSearch" @clear="doSearch" input-align="left" placeholder="请输入姓名或工号搜索" v-model="pageForm.searchKeyword"></van-search>
<van-dropdown-menu class="union-reimburse-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="yearList" @change="doSearch" v-model="pageForm.year"></van-dropdown-item>
<van-dropdown-item :options="reimburseProjectOptions" @change="doSearch" v-model="pageForm.reimburseProject"></van-dropdown-item>
<van-dropdown-item :options="approvalOptions" @change="onApprovalFilterChange" v-model="pageForm.approvalText"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<table-list api="/platform/unionReimburse/review/pageData" :page_form.sync="pageForm" ref="tableListRef"
@ready="doSearch">
<main class="union-reimburse-list-content">
<table-list api="/platform/unionReimburse/review/pageData" :page_form.sync="pageForm" ref="tableListRef" :show_loading_overlay="false"
@ready="doSearch" @loading-change="onLoadingChange">
<template #header="{row}"><div v-if="row" class="union-reimburse-card-header"><div class="union-reimburse-card-header__heading"><div class="union-reimburse-card-header__title">{{row.userName || '--'}}</div><div class="union-reimburse-card-header__sub">工号:{{row.loginName || '--'}}</div></div><van-tag :class="['union-reimburse-card-header__status', getStatusClass(row.stateId)]">{{getStateName(row.stateId)}}</van-tag></div></template>
<template v-slot="{index,row}">
<table-column label="经办人工号">{{row.loginName}}</table-column>
<table-column label="姓名">{{row.userName}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="所属单位">{{row.unitName}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime)}}</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject)}}</table-column>
<table-column label="备注">{{getRemarkText(row)}}</table-column>
<table-column label="申请状态">
<span :class="getStateClass(row.stateId)">{{getStateName(row.stateId)}}</span>
</table-column>
<table-column label="报销项目">{{getReimburseProjectName(row.reimburseProject) || '--'}}</table-column>
<table-column label="" label_suffix="">{{getRemarkText(row) || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName || '--'}}</table-column>
<table-column label="所属单位">{{row.unitName || '--'}}</table-column>
<table-column label="申请时间">{{formatCreateTime(row.createTime) || '--'}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.stateId == 2" @click="onAudit(row)">
<i class="fa fa-edit"></i>
<div class="action-btn review" v-if="row.stateId == 2" @click="onAudit(row)">
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
</template>
<template #empty><div v-if="listLoading" class="union-reimburse-list-skeleton"><div v-for="item in 2" :key="item" class="union-reimburse-skeleton-card"><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--title"></div><div class="union-reimburse-skeleton-line"></div><div class="union-reimburse-skeleton-line union-reimburse-skeleton-line--short"></div></div></div><div v-else class="union-reimburse-empty"><img src="/assets/mobile/img/unionReimburse/union-reimburse-empty.png" alt="暂无报销审核记录插画"><div class="union-reimburse-empty__title">暂无报销审核记录</div><div class="union-reimburse-empty__hint">当前筛选条件下暂时没有待办申请</div></div></template>
</table-list>
</main>
<union-reimburse-info ref="unionReimburseInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">
校工会审核
</div>
<div class="form-container">
<union-reimburse-info ref="unionReimburseInfoRef" history-layer-name="union-reimburse-review-info">
<template v-if="showApprovalForm">
<section class="union-reimburse-approval-card">
<h2 class="union-reimburse-approval-card__title">{{formData.taskName || '校工会审核'}}</h2>
<van-form ref="formRef">
<van-cell-group title="审批意见">
<van-field label=""
:rules="[{ required: true,message:'请填审批意见' }]"
v-model="formData.reviewOpinion"
required
type="textarea"
name="reviewOpinion"
rows="4"
autosize
class="more-text"
placeholder="请填审批意见"></van-field>
</van-cell-group>
<!-- <van-cell-group title="电子签名" class="form-section">-->
<!-- <van-field class="more-text" name="tf_userSign" label="" required-->
<!-- v-model="formData.reviewOpinion" :rules="[{ required: true,message:'请签名' }]">-->
<!-- <template #input>-->
<!-- <h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature>-->
<!-- </template>-->
<!-- </van-field>-->
<!-- </van-cell-group>-->
<div class="form-actions">
<van-button type="primary" @click="handleTaskAction(3)">同意申请</van-button>
<van-button type="danger" @click="handleTaskAction(4)">拒绝申请</van-button>
<van-button type="danger" @click="handleTaskAction(5)">退回到发起人</van-button>
</div>
<p class="union-reimburse-approval-field-label"><span class="union-reimburse-approval-required">*</span>审批意见</p>
<van-field v-model="formData.reviewOpinion" class="union-reimburse-approval-field" name="reviewOpinion" type="textarea" rows="6" label="" placeholder="请输入审批意见" :rules="[{required: true, message: '请填写审批意见'}]" maxlength="100" show-word-limit></van-field>
</van-form>
</section>
<div class="union-reimburse-approval-actions">
<van-button :loading="formLoading" type="danger" block @click="handleTaskAction(5)">退回</van-button>
<van-button :loading="formLoading" type="danger" block @click="handleTaskAction(4)">不同意</van-button>
<van-button :loading="formLoading" type="primary" block @click="handleTaskAction(3)">同意</van-button>
</div>
</div>
</template>
</union-reimburse-info>
<van-dialog :value="submitConfirmVisible" title="温馨提示" message="您确定要提交审核结果吗?" show-cancel-button :confirm-button-loading="formLoading" @confirm="submitTaskAction" @cancel="closeSubmitConfirm"></van-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -116,11 +80,26 @@ layout("/layouts/platform_h5.html"){
searchKeyword: "",
approvalText: "0",
approval: false,
year: new Date().getFullYear()
year: new Date().getFullYear(),
reimburseProject: ""
},
yearList: [],
reimburseProjectOptions: [
{text: "全部项目", value: ""},
{text: "慰问", value: "UNION_REIMBURSE_PROJECT_1"},
{text: "文体活动", value: "UNION_REIMBURSE_PROJECT_2"},
{text: "日常活动", value: "UNION_REIMBURSE_PROJECT_3"},
{text: "专项活动", value: "UNION_REIMBURSE_PROJECT_4"}
],
approvalOptions: [{text: "未审核", value: "0"}, {text: "已审核", value: "1"}],
formData: {},
showApprovalForm: false,
infoShow: false
infoShow: false,
listLoading: true,
formLoading: false,
submitConfirmVisible: false,
pendingSubmitType: null,
historyLayerUnsubscribe: null
}
},
methods: {
@@ -164,62 +143,63 @@ layout("/layouts/platform_h5.html"){
}
return stateId || ""
},
getStateClass(stateId) {
if (stateId == 3) {
return "state-success"
}
if (stateId == 4) {
return "state-danger"
}
return "state-primary"
},
getStatusClass(stateId) { if (stateId == 3) return "union-reimburse-card-header__status--success"; if (stateId == 4 || stateId == 5) return "union-reimburse-card-header__status--danger"; return "union-reimburse-card-header__status--pending" },
formatCreateTime(value) {
return value && this.$moment(value).isValid() ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : ""
},
handleTaskAction(val) {
const opinion = (this.formData.reviewOpinion || "").trim()
if (!opinion) {
this.$toast("请填写审批意见")
this.$refs.formRef.validate("reviewOpinion").catch(() => {})
return
}
this.$set(this.formData, "reviewOpinion", opinion)
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "温馨提示",
message: "您确定要提交吗?"
}).then(() => {
this.$axios.post("/platform/unionReimburse/review/reviewTask", {
data: JSON.stringify(Object.assign({}, this.formData)),
submitType: val
}).then(res => {
if (res.code === 0) {
this.$toast.success("提交成功")
this.doSearch()
this.$refs.unionReimburseInfoRef.onClose()
}
})
}).catch(() => {
})
}).catch()
this.$set(this, "pendingSubmitType", val)
const manager = window.h5HistoryLayerManager
if (manager) { manager.open("union-reimburse-review-confirm"); return }
this.$set(this, "submitConfirmVisible", true)
}).catch(() => {})
},
closeSubmitConfirm(afterClose) { const manager = window.h5HistoryLayerManager; if (manager) { manager.close("union-reimburse-review-confirm", afterClose); return } this.$set(this, "submitConfirmVisible", false); if (afterClose) afterClose() },
submitTaskAction() { const submitType = this.pendingSubmitType; this.closeSubmitConfirm(() => { this.$set(this, "formLoading", true); this.$axios.post("/platform/unionReimburse/review/reviewTask", {data: JSON.stringify(Object.assign({}, this.formData)), submitType: submitType}).then((res) => { if (res.code === 0) { this.$toast.success("提交成功"); this.doSearch(); this.$refs.unionReimburseInfoRef.onClose() } else { this.$toast.fail(res.msg) } }).finally(() => { this.$set(this, "formLoading", false) }) }) },
onView(row) {
this.showApprovalForm = false
this.$refs.unionReimburseInfoRef.onOpen(row)
this.$set(this, "showApprovalForm", false)
this.$refs.unionReimburseInfoRef.onOpen(row, "报销申请详情")
},
onAudit(row) {
this.showApprovalForm = true
this.formData = {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
id: row.id,
reviewOpinion: "通过"
}
this.$refs.unionReimburseInfoRef.onOpen(row)
taskName: "校工会审核",
reviewOpinion: ""
})
this.$refs.unionReimburseInfoRef.onOpen(row, "报销审核")
},
onLoadingChange(state) { this.$set(this, "listLoading", state.loading) },
onNavBack() { const manager = window.h5HistoryLayerManager; if (manager && manager.stack.length) { manager.close(); return } historyBack() },
onApprovalFilterChange(val) { this.$set(this.pageForm, "approval", val === "1"); this.doSearch() },
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$set(this.pageForm, "pageNumber", 1)
this.$set(this.pageForm, "totalCount", 0)
this.$refs.tableListRef.doSearch()
})
}
},
created() {
for (let i = new Date().getFullYear() - 10; i <= new Date().getFullYear(); i++) {
this.yearList.unshift({value: i, text: i + "年"})
}
const manager = window.h5HistoryLayerManager
if (manager) { manager.ensureRegistered("union-reimburse-review"); this.$set(this, "historyLayerUnsubscribe", manager.subscribe((layers) => { this.$set(this, "submitConfirmVisible", layers.includes("union-reimburse-review-confirm")) })) }
},
beforeDestroy() {
if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe()
const manager = window.h5HistoryLayerManager
if (manager) manager.unregister("union-reimburse-review")
}
})
</script>
@@ -253,11 +253,24 @@ const PROPOSAL_INFO = {
this.$nextTick(() => {
this.$set(this, "reloadPending", false)
if (!this.visible || !this.currentBizId) return
this.resetScrollPosition()
this.getInfo()
this.getDoneTasks()
})
},
/**
* 重置提案详情的纵向滚动位置
* 无参数查找组件所在的 proposal-full-popup__scroll 容器并滚动到顶部无匹配容器时安全跳过
*/
resetScrollPosition() {
const componentRoot = this.$el
const scrollContainer = componentRoot && componentRoot.closest
? componentRoot.closest(".proposal-full-popup__scroll")
: null
if (scrollContainer) scrollContainer.scrollTop = 0
},
/**
* 获取字典字段的展示文本
* options PROPOSAL_SOURCE 等字典数组value 为业务保存的字典 code返回名称字符串无匹配项时返回原值或 --
@@ -46,7 +46,7 @@ layout("/layouts/platform_h5.html"){
.proposal-hero__title {
margin: 0;
color: var(--proposal-primary);
font-size: 23px;
font-size: 25px;
line-height: 30px;
font-weight: 700;
letter-spacing: 1px;
@@ -55,7 +55,7 @@ layout("/layouts/platform_h5.html"){
.proposal-hero__subtitle {
margin-top: 2px;
color: #718096;
font-size: 11px;
font-size: 13px;
line-height: 18px;
}
@@ -93,7 +93,7 @@ layout("/layouts/platform_h5.html"){
.proposal-notice__title {
color: #1989fa;
font-size: 12px;
font-size: 14px;
line-height: 18px;
font-weight: 600;
}
@@ -101,7 +101,7 @@ layout("/layouts/platform_h5.html"){
.proposal-notice__text {
margin-top: 2px;
color: #8a97a8;
font-size: 10px;
font-size: 12px;
line-height: 17px;
}
@@ -115,7 +115,7 @@ layout("/layouts/platform_h5.html"){
border: 0;
background: transparent;
color: var(--proposal-primary);
font-size: 10px;
font-size: 12px;
}
.proposal-card {
@@ -132,7 +132,7 @@ layout("/layouts/platform_h5.html"){
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 13px;
font-size: 15px;
line-height: 20px;
font-weight: 600;
}
@@ -168,14 +168,14 @@ layout("/layouts/platform_h5.html"){
.proposal-card .van-field__label {
width: 84px;
color: #596779;
font-size: 12px;
font-size: 14px;
line-height: 24px;
}
.proposal-card .van-field__value,
.proposal-card .van-field__control {
color: #4f5d70;
font-size: 12px;
font-size: 14px;
line-height: 24px;
}
@@ -186,7 +186,7 @@ layout("/layouts/platform_h5.html"){
.proposal-card .van-field__word-limit {
margin-top: 2px;
color: #b5bdc8;
font-size: 9px;
font-size: 11px;
line-height: 14px;
}
@@ -225,8 +225,8 @@ layout("/layouts/platform_h5.html"){
.proposal-actions .van-button {
height: 40px;
margin: 0;
border-radius: 6px;
font-size: 14px;
border-radius: 14px;
font-size: 16px;
}
.proposal-actions .van-button:first-child {
@@ -239,14 +239,13 @@ layout("/layouts/platform_h5.html"){
.proposal-actions .van-button:last-child {
flex: 1.08;
border-color: var(--proposal-primary);
border-radius: 14px;
background: var(--proposal-primary);
}
.proposal-actions .van-icon {
margin-right: 5px;
font-size: 15px;
vertical-align: -2px;
#app > .van-nav-bar .van-nav-bar__title,
#app > .van-nav-bar .van-nav-bar__text,
#app > .van-nav-bar .van-nav-bar__arrow {
font-size: 18px;
}
.suggestion-unit-picker {
@@ -267,7 +266,7 @@ layout("/layouts/platform_h5.html"){
.picker-cancel, .picker-confirm {
background: none;
border: none;
font-size: 16px;
font-size: 18px;
color: #1989fa;
cursor: pointer;
}
@@ -277,11 +276,21 @@ layout("/layouts/platform_h5.html"){
}
.picker-title {
font-size: 16px;
font-size: 18px;
font-weight: 500;
color: #323233;
}
.proposal-write-page .van-picker-column__item {
font-size: 18px;
}
.proposal-write-page .van-picker__cancel,
.proposal-write-page .van-picker__confirm,
.proposal-write-page .van-search__field {
font-size: 16px;
}
.search-container {
padding: 12px 16px;
background-color: #fff;
@@ -303,7 +312,7 @@ layout("/layouts/platform_h5.html"){
.option-text {
margin-left: 12px;
flex: 1;
font-size: 14px;
font-size: 16px;
color: #323233;
}
@@ -324,6 +333,7 @@ layout("/layouts/platform_h5.html"){
width: 100%;
padding: 0 16px; /* 左右留白 */
box-sizing: border-box;
font-size: 16px;
}
.notice-dialog-content p {
@@ -332,6 +342,16 @@ layout("/layouts/platform_h5.html"){
text-indent: 2em; /* 中文首行缩进更规范 */
}
#app .van-action-sheet__header,
.van-dialog__header {
font-size: 18px;
}
#app .van-action-sheet__cancel,
.van-dialog__message {
font-size: 16px;
}
@media (min-width: 600px) {
.proposal-hero__content,
.proposal-main {
@@ -385,7 +405,7 @@ layout("/layouts/platform_h5.html"){
<van-form ref="formRef">
<!-- 基本信息:保留当前教代会、代表身份及联系方式等业务字段。 -->
<section class="proposal-card">
<h2 class="proposal-card__title">一、基本信息</h2>
<h2 class="proposal-card__title">基本信息</h2>
<van-cell-group :border="false">
<van-field
v-model="formData.createUserName"
@@ -467,7 +487,7 @@ layout("/layouts/platform_h5.html"){
<!-- 提案内容:字段和校验继续以现有接口及实体定义为准。 -->
<section class="proposal-card">
<h2 class="proposal-card__title">二、提案内容</h2>
<h2 class="proposal-card__title">提案内容</h2>
<van-cell-group :border="false">
<van-field
v-model="formData.name"
@@ -540,7 +560,7 @@ layout("/layouts/platform_h5.html"){
<!-- 附件材料仅使用卡片分组,上传控件保持项目原有样式和结果结构。 -->
<section class="proposal-card">
<h2 class="proposal-card__title">三、附件材料</h2>
<h2 class="proposal-card__title">附件材料</h2>
<van-cell-group :border="false">
<van-field label="附件" name="files" class="direction-column-field">
<h5-file-upload
@@ -613,13 +633,13 @@ layout("/layouts/platform_h5.html"){
<!-- 保存、首次提交和重新提交继续共用原业务入口。 -->
<div class="proposal-actions" v-if="isWriteTime">
<van-button plain type="info" :loading="formLoading && loadingAction === 'save'" :disabled="formLoading" @click="onSave">
<van-icon name="records"></van-icon>保存草稿
保存草稿
</van-button>
<van-button type="primary" :loading="formLoading && loadingAction === 'submit'" :disabled="formLoading" @click="onSubmit" v-if="!taskId">
<van-icon name="guide-o"></van-icon>提交提案
提交提案
</van-button>
<van-button type="primary" :loading="formLoading && loadingAction === 'submitAgain'" :disabled="formLoading" @click="onSubmitAgain" v-else>
<van-icon name="guide-o"></van-icon>重新提交
重新提交
</van-button>
</div>
</div>
@@ -1005,7 +1005,7 @@ layout("/layouts/platform_h5.html"){
width: 100%;
height: 100%;
object-fit: contain;
transform: scale(1.45);
transform: scale(1.6);
transform-origin: center;
}
@@ -1126,32 +1126,79 @@ layout("/layouts/platform_h5.html"){
}
.mobile-bottom-panel {
padding: 11px 14px calc(11px + env(safe-area-inset-bottom));
border-top: 1px solid #edf0f5;
padding: 0 0 env(safe-area-inset-bottom);
border-top: 0;
background: #fff;
box-shadow: 0 -5px 18px rgba(36, 55, 80, .08);
box-shadow: none;
}
.mobile-bottom-panel.is-empty {
padding-top: 12px;
padding-top: 0;
}
.mobile-bottom-selected {
display: grid;
grid-template-columns: 105px minmax(0, 1fr);
gap: 12px;
grid-template-columns: 42px max-content 1px minmax(0, 1fr);
gap: 9px;
align-items: center;
padding: 15px 9px;
background: #fff;
border: 1px solid #e8eef7;
border-radius: 0;
box-shadow: 0 5px 15px rgba(49, 93, 157, .12);
}
.mobile-bottom-summary {
display: block;
min-height: auto;
display: flex;
height: 50px;
margin: 0;
padding: 5px 8px;
align-items: center;
box-sizing: border-box;
color: #202b3c;
font-size: 15px;
background: #fff;
border: 1px solid #e8eef7;
border-radius: 23px;
box-shadow: 0 4px 12px rgba(51, 103, 178, .1);
white-space: nowrap;
}
.mobile-seat-status-icon {
width: 42px;
height: 42px;
object-fit: contain;
}
.mobile-seat-status-copy {
display: flex;
min-width: 0;
flex-direction: column;
align-items: flex-start;
justify-content: center;
text-align: left;
}
.mobile-seat-status-value {
color: #1677ff;
font-size: 15px;
font-weight: 600;
line-height: 20px;
}
.mobile-seat-status-tag {
margin-bottom: 1px;
color: #7d899a;
font-size: 10px;
line-height: 14px;
}
.mobile-seat-status-divider {
width: 1px;
height: 28px;
background: #dfe5ed;
}
.mobile-bottom-summary strong {
margin: 0 4px;
color: #1677ff;
@@ -1160,12 +1207,13 @@ layout("/layouts/platform_h5.html"){
}
.mobile-confirm-button {
height: 46px;
height: 38px;
border: 0;
border-radius: 8px;
background: #1677ff;
font-size: 16px;
font-weight: 500;
border-radius: 9px;
background: linear-gradient(90deg, #458df8 0%, #0869f5 100%);
box-shadow: none;
font-size: 14px;
font-weight: 600;
}
.mobile-confirm-button.van-button--disabled {
@@ -1174,12 +1222,13 @@ layout("/layouts/platform_h5.html"){
opacity: 1;
}
.seat-confirm-sheet {
border-radius: 18px 18px 0 0;
.seat-confirm-dialog {
width: calc(100% - 48px);
border-radius: 12px;
}
.seat-confirm-content {
padding: 21px 18px calc(18px + env(safe-area-inset-bottom));
.seat-confirm-dialog .seat-confirm-content {
padding: 21px 18px 18px;
}
.seat-confirm-header {
@@ -1216,17 +1265,28 @@ layout("/layouts/platform_h5.html"){
.seat-confirm-submit {
height: 46px;
margin-top: 18px;
border: 0;
border-radius: 8px;
background: #ff3344;
background: #69aefc;
font-size: 16px;
font-weight: 600;
}
@media (max-width: 375px) {
.mobile-bottom-selected {
grid-template-columns: 95px minmax(0, 1fr);
gap: 9px;
grid-template-columns: 38px max-content 1px minmax(0, 1fr);
gap: 8px;
}
.mobile-bottom-summary {
padding: 5px 6px;
font-size: 14px;
}
.mobile-seat-status-icon {
width: 38px;
height: 38px;
}
.mobile-seat {
@@ -1248,7 +1308,8 @@ layout("/layouts/platform_h5.html"){
<!-- 头部信息 -->
<div class="mobile-header" v-if="meeting">
<div class="meeting-title">{{ meeting.name }}</div>
<div class="meeting-time"><van-icon name="clock-o"></van-icon>{{ formatMeetingRange(meeting) }}</div>
<div class="meeting-time"><van-icon name="clock-o"></van-icon>开始时间:{{ formatMeetingTime(meeting.startTime) }}</div>
<div class="meeting-time"><van-icon name="calendar-o"></van-icon>结束时间:{{ formatMeetingTime(meeting.endTime) }}</div>
<div class="meeting-address"><van-icon name="location-o"></van-icon>{{ meeting.address || '会议地点待定' }}</div>
<div class="mobile-header-visual">
<img src="/assets/mobile/img/democratic/teachercongress/meeting-dais.png" alt="会议主席台">
@@ -1337,68 +1398,23 @@ layout("/layouts/platform_h5.html"){
<!-- 图例 -->
<div class="mobile-legend">
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#f8f9fa" stroke="#dee2e6"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#ffffff" stroke="#dee2e6"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#e9ecef" stroke="#dee2e6"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#e9ecef" stroke="#dee2e6"
stroke-width="1"/>
</svg>
<img class="mobile-legend-seat" src="/assets/mobile/img/democratic/teachercongress/seat-available.png" alt="">
<span>可选</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#d0d0d0" stroke="#6c757d"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
</svg>
<img class="mobile-legend-seat" src="/assets/mobile/img/democratic/teachercongress/seat-occupied.png" alt="">
<span>占用</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#1867b0" stroke="#0d47a1"
stroke-width="2"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
</svg>
<img class="mobile-legend-seat" src="/assets/mobile/img/democratic/teachercongress/seat-selected.png" alt="">
<span>选中</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#ff4757" stroke="#c62828"
stroke-width="2"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
</svg>
<span>我的</span>
<img class="mobile-legend-seat" src="/assets/mobile/img/democratic/teachercongress/seat-mine.png" alt="">
<span>已选</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#f0f0f0" stroke="#cccccc"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
</svg>
<img class="mobile-legend-seat" src="/assets/mobile/img/democratic/teachercongress/seat-disabled.png" alt="">
<span>禁用</span>
</div>
</div>
@@ -1426,13 +1442,7 @@ layout("/layouts/platform_h5.html"){
class="mobile-seat"
:class="[seat ? getSeatClass(seat) : 'seat-placeholder', {'has-aisle': seatIndex + 1 === aisleStartColumn}]"
@click="seat && selectSeat(seat)">
<svg class="mobile-seat-icon" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" class="seat-back"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" class="seat-cushion"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" class="seat-armrest"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" class="seat-armrest"/>
</svg>
<van-icon v-if="seat === selectedSeat" class="mobile-seat-check" name="success"></van-icon>
<img v-if="seat" class="mobile-seat-icon" :src="getSeatIcon(seat)" alt="">
</div>
</div>
</div>
@@ -1444,32 +1454,33 @@ layout("/layouts/platform_h5.html"){
<!-- 底部操作面板 -->
<div class="mobile-bottom-panel" :class="{'is-empty': !selectedSeat}">
<div class="mobile-bottom-selected">
<div class="mobile-bottom-summary">
<template v-if="myCurrentSeat">{{ myCurrentSeat.rowNumber }}排{{ myCurrentSeat.colNumber }}列(已选)</template>
<template v-else>未选择</template>
</div>
<img class="mobile-seat-status-icon"
src="/assets/mobile/img/democratic/teachercongress/seat-status-icon.svg" alt="座位">
<span class="mobile-seat-status-copy">
<span class="mobile-seat-status-tag">{{ myCurrentSeat ? '已选座位' : '座位状态' }}</span>
<span class="mobile-seat-status-value">
<template v-if="myCurrentSeat">{{ myCurrentSeat.rowNumber }}排{{ myCurrentSeat.colNumber }}列</template>
<template v-else>未选择</template>
</span>
</span>
<span class="mobile-seat-status-divider"></span>
<van-button class="mobile-confirm-button" type="info" block :disabled="!selectedSeat"
@click="openSeatConfirm">
<template v-if="selectedSeat">确认换座 {{ selectedSeat.rowNumber }}排{{ selectedSeat.colNumber }}列</template>
<template v-if="selectedSeat">{{ myCurrentSeat ? '确认换座' : '确认选座' }} {{ selectedSeat.rowNumber }}排{{ selectedSeat.colNumber }}列</template>
<template v-else>{{ myCurrentSeat ? '点击座位更新' : '请先选择座位' }}</template>
</van-button>
</div>
</div>
<van-action-sheet v-model="showSeatConfirm" class="seat-confirm-sheet" safe-area-inset-bottom>
<van-dialog v-model="showSeatConfirm" class="seat-confirm-dialog"
show-cancel-button confirm-button-text="确认" cancel-button-text="取消"
@confirm="confirmSelection">
<div class="seat-confirm-content">
<div class="seat-confirm-header">
<span>已选座位</span>
<button type="button" class="seat-confirm-clear" @click="clearSelectedSeat">清空</button>
<span v-if="selectedSeat">确认{{ myCurrentSeat ? '换座' : '选座' }} {{ selectedSeat.rowNumber }}排{{ selectedSeat.colNumber }}列吗?</span>
</div>
<div class="seat-confirm-value" v-if="selectedSeat">
{{ selectedSeat.rowNumber }}排{{ selectedSeat.colNumber }}座
</div>
<div class="seat-confirm-tip">请确认座位信息</div>
<van-button class="seat-confirm-submit" type="danger" block :loading="submitting"
@click="confirmSelection">确认选座</van-button>
</div>
</van-action-sheet>
</van-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -1557,11 +1568,8 @@ layout("/layouts/platform_h5.html"){
}
})
},
formatMeetingRange(item) {
if (!item) return ''
const start = item.startTime ? this.$moment(item.startTime).format('YYYY-MM-DD HH:mm') : '--'
const end = item.endTime ? this.$moment(item.endTime).format('YYYY-MM-DD HH:mm') : '--'
return start + ' 至 ' + end
formatMeetingTime(time) {
return time ? this.$moment(time).format('YYYY-MM-DD HH:mm') : '--'
},
// 查询选座情况
async loadSeats(loading = createListLoading()) {
@@ -1670,6 +1678,15 @@ layout("/layouts/platform_h5.html"){
return 'available';
},
getSeatIcon(seat) {
const iconPath = '/assets/mobile/img/democratic/teachercongress/'
if (!seat.enable) return iconPath + 'seat-disabled.png'
if (seat.isMine) return iconPath + 'seat-mine.png'
if (seat === this.selectedSeat) return iconPath + 'seat-selected.png'
if (seat.isOccupied) return iconPath + 'seat-occupied.png'
return iconPath + 'seat-available.png'
},
getSeatTitle(seat) {
if (seat.isMine) return '我的座位';
if (seat === this.selectedSeat) return '当前选择';
@@ -12,6 +12,12 @@ layout("/layouts/platform_h5.html"){
color: #1989fa;
}
#app > .van-nav-bar .van-nav-bar__title,
#app > .van-nav-bar .van-nav-bar__text,
#app > .van-nav-bar .van-nav-bar__arrow {
font-size: 18px;
}
.search-dialog {
height: 80%;
}
@@ -38,7 +44,7 @@ layout("/layouts/platform_h5.html"){
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 13px;
font-size: 15px;
line-height: 20px;
font-weight: 600;
background: transparent;
@@ -63,7 +69,7 @@ layout("/layouts/platform_h5.html"){
.condolence-card__desc {
padding: 0 14px 8px 21px;
color: orangered;
font-size: 12px;
font-size: 14px;
line-height: 18px;
font-weight: normal;
}
@@ -83,14 +89,14 @@ layout("/layouts/platform_h5.html"){
.condolence-apply-form .form-section .van-field__label {
width: 84px;
color: #596779;
font-size: 12px;
font-size: 14px;
line-height: 24px;
}
.condolence-apply-form .form-section .van-field__value,
.condolence-apply-form .form-section .van-field__control {
color: #4f5d70;
font-size: 12px;
font-size: 14px;
line-height: 24px;
}
@@ -120,8 +126,8 @@ layout("/layouts/platform_h5.html"){
.condolence-apply-actions .van-button {
height: 40px;
margin: 0;
border-radius: 6px;
font-size: 14px;
border-radius: 14px !important;
font-size: 16px;
}
.condolence-apply-actions .van-button:first-child {
@@ -134,9 +140,28 @@ layout("/layouts/platform_h5.html"){
.condolence-apply-actions .van-button:last-child {
flex: 1.08;
border-color: #1989fa;
border-radius: 14px;
background: #1989fa;
}
#app .van-action-sheet__header,
#app .van-picker__title,
.van-dialog__header {
font-size: 18px;
}
#app .van-action-sheet__name,
#app .van-action-sheet__cancel,
#app .van-empty__description,
#app .van-search__field,
#app .van-picker__cancel,
#app .van-picker__confirm,
.van-dialog__message {
font-size: 16px;
}
#app .van-picker-column__item {
font-size: 18px;
}
</style>
<div id="app">
@@ -23,6 +23,13 @@ layout("/layouts/platform_h5.html"){
.condolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.condolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.condolence-list-content { padding: 0 12px; }
.condolence-list-skeleton { padding-top: 2px; }
.condolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-list-skeleton__card .van-skeleton { padding: 0; }
.condolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.condolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.condolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.condolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.condolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.condolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
@@ -72,7 +79,14 @@ layout("/layouts/platform_h5.html"){
</van-sticky>
<main class="condolence-list-content">
<table-list api="/platform/condolence/branchUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" @ready="onReady">
<div class="condolence-list-skeleton" v-if="skeletonLoading">
<div class="condolence-list-skeleton__card" v-for="index in 3" :key="'branch-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/condolence/branchUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="condolence-card-header">
<div class="condolence-card-heading">
@@ -180,6 +194,7 @@ layout("/layouts/platform_h5.html"){
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
@@ -191,6 +206,10 @@ layout("/layouts/platform_h5.html"){
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
@@ -284,7 +303,7 @@ layout("/layouts/platform_h5.html"){
})
},
queryCondolenceType() {
this.$axios.post('/platform/condolence/type/queryCondolenceType')
this.$axios.post('/platform/condolence/type/queryCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
@@ -127,6 +127,44 @@ layout("/layouts/platform_h5.html"){
padding: 0 12px;
}
.condolence-list-skeleton {
padding-top: 2px;
}
.condolence-list-skeleton__card {
margin-bottom: 12px;
padding: 18px 14px 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.condolence-list-skeleton__card .van-skeleton {
padding: 0;
}
.condolence-list-skeleton__card .van-skeleton__title {
width: 42%;
height: 18px;
margin-bottom: 18px;
}
.condolence-list-skeleton__card .van-skeleton__row {
height: 14px;
margin-top: 12px;
}
.condolence-list-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 72% !important;
}
.condolence-list-skeleton__card .van-skeleton__row:last-child {
width: 34% !important;
margin-top: 18px;
margin-left: auto;
}
.condolence-list-page .table-list-container {
margin-top: 0;
padding-bottom: 2px;
@@ -278,7 +316,14 @@ layout("/layouts/platform_h5.html"){
</van-sticky>
<main class="condolence-list-content">
<table-list api="/platform/condolence/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" @ready="onReady">
<div class="condolence-list-skeleton" v-if="skeletonLoading">
<div class="condolence-list-skeleton__card" v-for="index in 3" :key="'mine-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/condolence/mine/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="condolence-card-header">
<div class="condolence-card-heading">
@@ -351,21 +396,28 @@ layout("/layouts/platform_h5.html"){
type: null
},
typeOptions: [],
skeletonLoading: true,
}
},
methods: {
async onReady() {
const typeList = await this.queryCondolenceType()
this.typeOptions = [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id })))
if (this.typeOptions.length > 0) {
this.pageForm.type = this.typeOptions[0].value
this.doSearch()
}
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
onReady() {
this.queryCondolenceType()
.then((typeList) => {
this.$set(this, "typeOptions", [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id }))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
})
},
onRevoke(row) {
this.$dialog.confirm({
@@ -403,9 +455,9 @@ layout("/layouts/platform_h5.html"){
})
.catch(() => {})
},
async queryCondolenceType() {
const res = await this.$axios.post('/platform/condolence/type/queryCondolenceType')
return res.data
queryCondolenceType() {
return this.$axios.post('/platform/condolence/type/queryCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => res.data || [])
},
doSearch() {
this.$nextTick(() => {
@@ -23,6 +23,13 @@ layout("/layouts/platform_h5.html"){
.condolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.condolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.condolence-list-content { padding: 0 12px; }
.condolence-list-skeleton { padding-top: 2px; }
.condolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-list-skeleton__card .van-skeleton { padding: 0; }
.condolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.condolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.condolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.condolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.condolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.condolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
@@ -72,7 +79,14 @@ layout("/layouts/platform_h5.html"){
</van-sticky>
<main class="condolence-list-content">
<table-list api="/platform/condolence/schoolPrincipalApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" @ready="onReady">
<div class="condolence-list-skeleton" v-if="skeletonLoading">
<div class="condolence-list-skeleton__card" v-for="index in 3" :key="'school-principal-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/condolence/schoolPrincipalApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="condolence-card-header">
<div class="condolence-card-heading">
@@ -180,6 +194,7 @@ layout("/layouts/platform_h5.html"){
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
@@ -191,6 +206,10 @@ layout("/layouts/platform_h5.html"){
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
@@ -284,7 +303,7 @@ layout("/layouts/platform_h5.html"){
})
},
queryCondolenceType() {
this.$axios.post('/platform/condolence/type/queryCondolenceType')
this.$axios.post('/platform/condolence/type/queryCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
@@ -23,6 +23,13 @@ layout("/layouts/platform_h5.html"){
.condolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.condolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.condolence-list-content { padding: 0 12px; }
.condolence-list-skeleton { padding-top: 2px; }
.condolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-list-skeleton__card .van-skeleton { padding: 0; }
.condolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.condolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.condolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.condolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.condolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.condolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.condolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
@@ -72,7 +79,14 @@ layout("/layouts/platform_h5.html"){
</van-sticky>
<main class="condolence-list-content">
<table-list api="/platform/condolence/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" @ready="onReady">
<div class="condolence-list-skeleton" v-if="skeletonLoading">
<div class="condolence-list-skeleton__card" v-for="index in 3" :key="'school-union-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/condolence/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="condolence-card-header">
<div class="condolence-card-heading">
@@ -180,6 +194,7 @@ layout("/layouts/platform_h5.html"){
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
@@ -191,6 +206,10 @@ layout("/layouts/platform_h5.html"){
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
@@ -284,7 +303,7 @@ layout("/layouts/platform_h5.html"){
})
},
queryCondolenceType() {
this.$axios.post('/platform/condolence/type/queryCondolenceType')
this.$axios.post('/platform/condolence/type/queryCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
@@ -1,17 +1,305 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app {
--color-primary: #1989fa;
}
#app > .van-nav-bar .van-icon,
#app > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
#app > .van-nav-bar .van-nav-bar__title,
#app > .van-nav-bar .van-nav-bar__text,
#app > .van-nav-bar .van-nav-bar__arrow {
font-size: 18px;
}
.difficult-help-apply-form.form-container {
min-height: 100%;
padding: 12px 15px calc(84px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fc;
}
.difficult-help-apply-form .form-section {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, 0.08);
}
.difficult-help-apply-form .difficult-help-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
line-height: 20px;
font-weight: 600;
}
.difficult-help-apply-form .difficult-help-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
.difficult-help-apply-form .form-section .van-cell-group {
margin: 0;
background: transparent;
}
.difficult-help-apply-form .form-section .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.difficult-help-apply-form .form-section .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.difficult-help-apply-form .form-section .van-field__label {
width: 84px;
color: #596779;
font-size: 14px;
line-height: 24px;
}
.difficult-help-apply-form .form-section .van-field__value,
.difficult-help-apply-form .form-section .van-field__control {
color: #4f5d70;
font-size: 14px;
line-height: 24px;
}
.difficult-help-apply-form .form-section .van-field__control::placeholder {
color: #b1bac6;
}
.difficult-help-apply-form .form-section .van-cell--required::before {
left: 6px;
color: #f06464;
}
.difficult-help-apply-actions {
position: fixed;
z-index: 100;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 9px;
padding: 10px 18px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 -4px 14px rgba(50, 78, 112, 0.06);
}
.difficult-help-apply-actions .van-button {
height: 40px;
margin: 0;
border-radius: 14px !important;
font-size: 16px;
}
.difficult-help-apply-actions .van-button:first-child {
flex: 1;
color: #1989fa;
border-color: #1989fa;
background: #fff;
}
.difficult-help-apply-actions .van-button:last-child {
flex: 1.08;
border-color: #1989fa;
background: #1989fa;
}
#app .van-action-sheet__header,
#app .van-picker__title,
.van-dialog__header {
font-size: 18px;
}
#app .van-action-sheet__name,
#app .van-action-sheet__cancel,
#app .van-empty__description,
#app .van-search__field,
#app .van-picker__cancel,
#app .van-picker__confirm,
.van-dialog__message {
font-size: 16px;
}
#app .van-picker-column__item {
font-size: 18px;
}
.difficult-help-option-popup {
display: flex;
height: 50%;
padding-bottom: env(safe-area-inset-bottom);
flex-direction: column;
box-sizing: border-box;
background: #f5f7fa;
}
.difficult-help-option-popup__header {
display: flex;
min-height: 52px;
padding: 0 16px;
align-items: center;
justify-content: space-between;
flex: none;
border-bottom: 1px solid #edf1f6;
background: #fff;
}
.difficult-help-option-popup__title {
color: #263548;
font-size: 17px;
line-height: 24px;
font-weight: 600;
}
.difficult-help-option-popup__action {
min-width: 48px;
padding: 8px 0;
border: 0;
color: #1989fa;
font-size: 15px;
line-height: 22px;
text-align: left;
background: transparent;
}
.difficult-help-option-popup__action:last-child {
text-align: right;
}
.difficult-help-option-popup__action:disabled {
color: #b7c0cc;
}
.difficult-help-option-popup__list {
min-height: 0;
padding: 12px;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.difficult-help-option-popup__search {
padding: 10px 12px 0;
flex: none;
background: #f5f7fa;
}
.difficult-help-option-popup__search .van-search__content {
border: 1px solid #e3e9f1;
background: #fff;
}
.difficult-help-option-popup__empty {
padding: 44px 18px;
color: #98a4b3;
font-size: 14px;
line-height: 22px;
text-align: center;
}
.difficult-help-option-popup__option {
display: flex;
width: 100%;
min-height: 58px;
margin-bottom: 10px;
padding: 13px 14px;
align-items: center;
justify-content: space-between;
gap: 12px;
box-sizing: border-box;
border: 1px solid #e4eaf2;
border-radius: 10px;
color: #46566c;
text-align: left;
background: #fff;
}
.difficult-help-option-popup__option:last-child {
margin-bottom: 0;
}
.difficult-help-option-popup__option.is-selected {
border-color: #1989fa;
color: #176fbd;
background: #eef7ff;
box-shadow: 0 3px 10px rgba(25, 137, 250, 0.1);
}
.difficult-help-option-popup__option-text {
min-width: 0;
flex: 1;
font-size: 15px;
line-height: 23px;
white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
}
.difficult-help-option-popup__recipient {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
}
.difficult-help-option-popup__recipient-name {
color: #304157;
font-size: 16px;
line-height: 23px;
font-weight: 600;
}
.difficult-help-option-popup__recipient-unit {
margin-top: 5px;
color: #8290a3;
font-size: 13px;
line-height: 20px;
white-space: normal;
word-break: break-word;
}
.difficult-help-option-popup__option.is-selected .difficult-help-option-popup__recipient-name,
.difficult-help-option-popup__option.is-selected .difficult-help-option-popup__recipient-unit {
color: #176fbd;
}
</style>
<div id="app">
<div v-if="!isShow">
<van-empty image="error" :description="'抱歉,当前时间不能申请,可申请时间为' + time + '。'"></van-empty>
</div>
<div v-else>
<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="handlePageBack" placeholder fixed></van-nav-bar>
<div class="form-container">
<van-form ref="formRef">
<van-cell-group title="申请人信息" class="form-section">
<van-form ref="formRef" class="form-container difficult-help-apply-form">
<section class="form-section">
<h2 class="difficult-help-card__title">申请人信息</h2>
<van-cell-group :border="false">
<van-field label="申请次数" v-model="formData.applyCount" readonly
required name="applyCount"></van-field>
<van-field label="填写人姓名" :rules="[{ required: true }]" v-model="formData.proxyUserName" readonly
@@ -29,28 +317,25 @@ layout("/layouts/platform_h5.html"){
</van-radio-group>
</template>
</van-field>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group title="受助人信息" class="form-section">
<section class="form-section">
<h2 class="difficult-help-card__title">受助人信息</h2>
<van-cell-group :border="false">
<template v-if="formData.mode == 2">
<van-field label="受助人" :rules="[{ required: true }]" v-model="formData.userName"
required name="userName" placeholder="请输入受补助人姓名后点击查询">
<template #button>
<van-button :disabled="formData.id" @click="queryRecipients(formData.userName)"
size="small" type="primary">查询
</van-button>
</template>
<van-field
label="受助人"
:rules="[{ required: true }]"
v-model="formData.userName"
:is-link="!formData.id"
readonly
required
name="userName"
placeholder="请选择受助人"
@click="openRecipientPopup">
</van-field>
<van-action-sheet
:actions="subsidizedList"
:close-on-click-overlay="false"
@cancel="userCancel"
@select="onUserSelect"
cancel-text="重新输入姓名或工号查询"
v-model="userSheetShow">
</van-action-sheet>
</template>
<template v-else>
<van-field label="受助人" :rules="[{ required: true }]" v-model="formData.userName" readonly
@@ -66,16 +351,8 @@ layout("/layouts/platform_h5.html"){
readonly
name="sex"
:rules="[{ required: true }]"
@click="showSexPicker = true"
@click="openOptionPopup('sex')"
></van-field>
<van-popup v-model="showSexPicker" position="bottom">
<van-picker
show-toolbar
:columns="['男', '女']"
@confirm="onSexConfirm"
@cancel="showSexPicker=false"
></van-picker>
</van-popup>
<van-field
v-model="formData.unionName"
@@ -171,47 +448,34 @@ layout("/layouts/platform_h5.html"){
:rules="[{ required: true }]"
></van-field>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group title="困难类型及材料" class="form-section">
<section class="form-section">
<h2 class="difficult-help-card__title">困难类型及材料</h2>
<van-cell-group :border="false">
<van-field
v-model="formData.subsidyName"
label="补助类型"
placeholder="请选择补助类型"
is-link
@click="showSubsidyPicker = true"
readonly
@click="openOptionPopup('subsidyType')"
required
name="subsidyName"
:rules="[{ required: true, message: '请选择补助类型' }]"
></van-field>
<van-popup v-model="showSubsidyPicker" position="bottom">
<van-picker
show-toolbar
:columns="subsidyTypeColumns"
@confirm="onSubsidyTypeConfirm"
@cancel="showSubsidyPicker=false">
</van-picker>
</van-popup>
<van-field
v-model="formData.subsidyStandardsName"
label="困难类型"
placeholder="请选择困难类型"
is-link
@click="showSubsidyStandardPicker = true"
readonly
@click="openOptionPopup('subsidyStandard')"
required
name="subsidyStandardsName"
:rules="[{ required: true, message: '请选择困难类型' }]"
></van-field>
<van-popup v-model="showSubsidyStandardPicker" position="bottom">
<van-picker
show-toolbar
:columns="subsidyStandardColumns"
@confirm="onSubsidyStandardConfirm"
@cancel="showSubsidyStandardPicker=false">
</van-picker>
</van-popup>
<van-field
v-model="formData.bankCardNum"
label="收款账户"
@@ -242,9 +506,12 @@ layout("/layouts/platform_h5.html"){
name="reason"
:rules="[{ required: true }]"
></van-field>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group title="家庭主要成员信息" class="form-section up_down">
<section class="form-section up_down">
<h2 class="difficult-help-card__title">家庭主要成员信息</h2>
<van-cell-group :border="false">
<div class="van-cell">
<div style="display: flex;justify-content: space-between">
<div style="display: flex;">
@@ -290,9 +557,12 @@ layout("/layouts/platform_h5.html"){
</div>
</template>
</div>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group title="附件信息" class="form-section">
<section class="form-section">
<h2 class="difficult-help-card__title">附件信息</h2>
<van-cell-group :border="false">
<van-field class="more-text" name="files" label="">
<template #input>
<h5-file-upload
@@ -306,9 +576,12 @@ layout("/layouts/platform_h5.html"){
></h5-file-upload>
</template>
</van-field>
</van-cell-group>
</van-cell-group>
</section>
<van-cell-group title="签字" class="form-section">
<section class="form-section">
<h2 class="difficult-help-card__title">签字确认</h2>
<van-cell-group :border="false">
<van-field class="more-text" name="sign" label="">
<template #input>
<h5-signature v-model="formData.sign" slot="input"></h5-signature>
@@ -317,15 +590,67 @@ layout("/layouts/platform_h5.html"){
<div style="color: red; padding: 10px;">
本人承诺:本人及家庭成员未有购买价格超过15万元的机动车,也未有两套及以上商品房,特此承诺!
</div>
</van-cell-group>
</van-cell-group>
</section>
<div class="form-actions">
<van-button native-type="button" plain type="info" @click="onSave">保存</van-button>
<van-button type="primary" @click="onSubmit" v-if="!taskId">提交</van-button>
<van-button type="primary" @click="onFinishTask" v-else>提交</van-button>
<div class="form-actions difficult-help-apply-actions">
<van-button native-type="button" plain round type="info" @click="onSave">保存申请</van-button>
<van-button round type="info" @click="onSubmit" v-if="!taskId">提交申请</van-button>
<van-button round type="info" @click="onFinishTask" v-else>提交申请</van-button>
</div>
</van-form>
</div>
<van-popup
v-model="showOptionPicker"
position="bottom"
round
:close-on-click-overlay="false"
class="difficult-help-option-popup">
<div class="difficult-help-option-popup__header">
<button type="button" class="difficult-help-option-popup__action"
@click="cancelOptionPopup">取消</button>
<div class="difficult-help-option-popup__title">{{optionPopupTitle}}</div>
<button type="button" class="difficult-help-option-popup__action"
:disabled="pendingOptionValue === null || pendingOptionValue === undefined || pendingOptionValue === ''"
@click="confirmOptionPopup">确认</button>
</div>
<van-search
v-if="optionPopupType === 'recipient'"
v-model.trim="recipientSearchKey"
class="difficult-help-option-popup__search"
shape="round"
clearable
placeholder="请输入姓名或工号搜索"
@search="queryRecipients"
@clear="clearRecipientSearchResults">
</van-search>
<div class="difficult-help-option-popup__list">
<div
v-if="optionPopupType === 'recipient' && !recipientHasSearched && optionPopupOptions.length === 0"
class="difficult-help-option-popup__empty">
输入姓名或工号后,点击键盘搜索键查询
</div>
<div
v-else-if="optionPopupType === 'recipient' && recipientHasSearched && optionPopupOptions.length === 0"
class="difficult-help-option-popup__empty">
未查询到符合条件的受助人
</div>
<button
v-for="option in optionPopupOptions"
:key="option.value"
type="button"
class="difficult-help-option-popup__option"
:class="{'is-selected': pendingOptionValue === option.value}"
:aria-pressed="pendingOptionValue === option.value"
@click="selectOptionPopupItem(option)">
<span v-if="optionPopupType === 'recipient'" class="difficult-help-option-popup__recipient">
<span class="difficult-help-option-popup__recipient-name">{{option.text}}</span>
<span class="difficult-help-option-popup__recipient-unit">{{option.description || '暂无单位信息'}}</span>
</span>
<span v-else class="difficult-help-option-popup__option-text">{{option.text}}</span>
</button>
</div>
</van-popup>
</div>
</div>
@@ -333,11 +658,11 @@ layout("/layouts/platform_h5.html"){
.mt5 {
margin-top: 5px;
}
.up_down > .van-cell {
.up_down > .van-cell-group > .van-cell {
flex-flow: column;
}
.up_down > .van-cell > .van-field__label {
.up_down > .van-cell-group > .van-cell > .van-field__label {
width: 100%;
}
</style>
@@ -350,10 +675,15 @@ layout("/layouts/platform_h5.html"){
return {
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
showSexPicker: false,
showDatePicker: false,
showSubsidyPicker: false,
showSubsidyStandardPicker: false,
showOptionPicker: false,
optionPopupType: "",
optionPopupTitle: "",
optionPopupOptions: [],
pendingOptionValue: "",
recipientSearchKey: "",
recipientHasSearched: false,
historyLayerPageKey: "difficult-help-apply",
formData: {
id: GetQueryString("bizId"),
mode: "1",
@@ -365,7 +695,6 @@ layout("/layouts/platform_h5.html"){
subsidyValue: "",
subsidyStandardsValue: "",
userSheetShow: false,
subsidizedList: [],
maxDate: new Date(),
@@ -380,19 +709,153 @@ layout("/layouts/platform_h5.html"){
},
methods: {
historyBack,
// 替他人申请时由只读字段主动打开受助人选择,已有业务数据时保持原禁止更换规则。
openRecipientPopup() {
if (this.formData.mode !== "2" || this.formData.id) return
this.$set(this, "recipientSearchKey", "")
this.$set(this, "recipientHasSearched", false)
this.$set(this, "subsidizedList", [])
this.openOptionPopup("recipient")
},
/**
* 根据选择类型配置并打开统一弹框。
* optionType 支持 recipient、sex、subsidyType、subsidyStandard;方法无返回值。
*/
openOptionPopup(optionType) {
let title = ""
let options = []
let currentValue = ""
if (optionType === "recipient") {
title = "选择受助人"
options = this.subsidizedList.map((item) => {
return {value: item.id, text: item.username, description: item.unitName, source: item}
})
const matchedRecipient = options.find((item) => {
return item.value === this.formData.userId
})
currentValue = matchedRecipient ? matchedRecipient.value : ""
} else if (optionType === "sex") {
title = "选择性别"
options = [{value: "男", text: "男"}, {value: "女", text: "女"}]
currentValue = this.formData.sex || ""
} else if (optionType === "subsidyType") {
title = "选择补助类型"
options = this.subsidyTypeColumns.slice()
const matchedSubsidy = options.find((item) => {
return item.text === this.formData.subsidyName
})
currentValue = matchedSubsidy
? matchedSubsidy.value
: (this.formData.subsidy || this.subsidyValue || "")
} else if (optionType === "subsidyStandard") {
title = "选择困难类型"
options = this.subsidyStandardColumns.slice()
const matchedStandard = options.find((item) => {
return item.text === this.formData.subsidyStandardsName
})
currentValue = matchedStandard
? matchedStandard.value
: (this.formData.subsidyStandards || this.subsidyStandardsValue || "")
}
this.$set(this, "optionPopupType", optionType)
this.$set(this, "optionPopupTitle", title)
this.$set(this, "optionPopupOptions", options)
this.$set(this, "pendingOptionValue", currentValue)
this.openPopup("showOptionPicker")
},
selectOptionPopupItem(option) {
this.$set(this, "pendingOptionValue", option.value)
},
cancelOptionPopup() {
if (this.optionPopupType === "recipient") {
this.$set(this, "recipientSearchKey", "")
this.$set(this, "recipientHasSearched", false)
this.$set(this, "subsidizedList", [])
}
this.closePopup("showOptionPicker")
},
clearRecipientSearchResults() {
this.$set(this, "recipientHasSearched", false)
this.$set(this, "subsidizedList", [])
this.$set(this, "optionPopupOptions", [])
this.$set(this, "pendingOptionValue", "")
},
/**
* 确认统一弹框选项并按类型回填表单。
* 选项 value 为业务值、text 为展示文本,recipient 额外携带 source 用户对象;方法无返回值。
*/
confirmOptionPopup() {
const selectedOption = this.optionPopupOptions.find((item) => {
return item.value === this.pendingOptionValue
})
if (!selectedOption) {
this.$toast("请选择" + this.optionPopupTitle.replace("选择", ""))
return
}
if (this.optionPopupType === "recipient") {
this.applySelectedUser(selectedOption.source)
this.$set(this, "recipientSearchKey", "")
this.$set(this, "recipientHasSearched", false)
this.$set(this, "subsidizedList", [])
} else if (this.optionPopupType === "sex") {
this.$set(this.formData, "sex", selectedOption.value)
} else if (this.optionPopupType === "subsidyType") {
this.$set(this.formData, "subsidyName", selectedOption.text)
this.$set(this, "subsidyValue", selectedOption.value)
} else if (this.optionPopupType === "subsidyStandard") {
this.$set(this.formData, "subsidyStandardsName", selectedOption.text)
this.$set(this, "subsidyStandardsValue", selectedOption.value)
}
this.closePopup("showOptionPicker")
},
/**
* 打开底部弹框并增加一层浏览器历史。
* popupKey 传弹框对应的 data 字段名;方法无返回值。
*/
openPopup(popupKey) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open(popupKey)) return
this.syncPopupState(popupKey, true)
},
/**
* 主动关闭底部弹框并回退对应的浏览器历史。
* popupKey 传弹框对应的 data 字段名;返回键回调只同步界面状态,避免重复回退历史。
*/
closePopup(popupKey) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close(popupKey)) return
this.syncPopupState(popupKey, false)
},
syncPopupState(popupKey, visible) {
const popupKeys = ["showOptionPicker"]
if (popupKeys.includes(popupKey)) {
this.$set(this, popupKey, visible)
}
},
// 根据统一弹层历史栈同步界面,返回操作只关闭栈顶弹框,不重新加载当前页面。
syncHistoryLayerStack(stack) {
const popupKeys = ["showOptionPicker"]
popupKeys.forEach((popupKey) => {
this.syncPopupState(popupKey, stack.includes(popupKey))
})
},
// NavBar 返回与系统返回保持一致:有弹框先关闭弹框,无弹框再返回业务页面。
handlePageBack() {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.stack.length) {
window.h5HistoryLayerManager.close()
return
}
this.historyBack()
},
// 业务跳转前清空当前页面的弹框历史,避免返回时重新出现已关闭弹框。
clearPopupHistoryAndNavigate(url) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAllAndNavigate(url)) return
this.$pjaxReplace(url)
},
showBirthday() {
if (this.formData.birthday) {
this.$set(this.formData, "currentDate", new Date(this.formData.birthday))
}
this.showDatePicker = true
},
userCancel() {
if (!this.formData.userId) {
this.$set(this.formData, "userName", null)
}
this.subsidizedList = []
},
async initDictOptions() {
const subsidyTypeData = await this.$businessTool.getDictOptions("DIFFICULT_SUBSIDY_TYPE");
this.subsidyTypeColumns = subsidyTypeData.map(item => {
@@ -432,7 +895,7 @@ layout("/layouts/platform_h5.html"){
this.$axios.post("/platform/difficultHelp/apply/save", { data: JSON.stringify(submitData) }).then(res => {
if (res.code === 0) {
this.$toast("保存成功")
this.$pjaxReplace("/platform/difficultHelp/mine/h5")
this.clearPopupHistoryAndNavigate("/platform/difficultHelp/mine/h5")
}
})
})
@@ -465,7 +928,7 @@ layout("/layouts/platform_h5.html"){
}).then(res => {
if (res.code === 0) {
this.$toast("提交成功")
this.$pjaxReplace("/platform/difficultHelp/mine/h5")
this.clearPopupHistoryAndNavigate("/platform/difficultHelp/mine/h5")
}
})
})
@@ -500,39 +963,56 @@ layout("/layouts/platform_h5.html"){
}).then(res => {
if (res.code === 0) {
this.$toast("提交成功")
this.$pjaxReplace("/platform/difficultHelp/mine/h5")
this.clearPopupHistoryAndNavigate("/platform/difficultHelp/mine/h5")
}
})
})
})
},
// 查询受助人
async queryRecipients(key) {
/**
* 按姓名或工号查询受助人。
* recipientSearchKey 传搜索关键词;接口返回用户数组,列表仅展示姓名和单位。
*/
queryRecipients() {
if (this.formData.mode !== "2") {
return
}
const res = await this.$axios.post("/platform/difficultHelp/apply/queryRecipients", { key })
if (res.code === 0) {
res.data.forEach((v) => {
v.name = v.username
return v
})
this.subsidizedList = res.data
this.userSheetShow = true
const key = (this.recipientSearchKey || "").trim()
if (!key) {
this.$toast("请输入姓名或工号")
return
}
this.$axios.post("/platform/difficultHelp/apply/queryRecipients", {key: key}).then((res) => {
if (res.code === 0) {
const recipients = res.data || []
const options = recipients.map((item) => {
return {value: item.id, text: item.username, description: item.unitName, source: item}
})
const currentRecipient = options.find((item) => {
return String(item.value) === String(this.formData.userId || "")
})
this.$set(this, "recipientHasSearched", true)
this.$set(this, "subsidizedList", recipients)
this.$set(this, "optionPopupOptions", options)
this.$set(this, "pendingOptionValue", currentRecipient ? currentRecipient.value : "")
}
})
},
onUserSelect(val) {
const user = this.subsidizedList.find((item) => item.id === val.id)
// 受助人确认后回填用户、单位和工会信息,参数 user 为查询接口返回的完整用户对象。
applySelectedUser(user) {
if (user) {
const unitId = user.unitId !== undefined ? user.unitId : user.unitid
const unionId = user.unionId !== undefined ? user.unionId : user.unionid
this.$set(this.formData, "userId", user.id)
this.$set(this.formData, "userName", user.username)
this.$set(this.formData, "loginName", user.loginname)
this.$set(this.formData, "sex", user.sex)
this.$set(this.formData, "unitId", user.unitid)
this.$set(this.formData, "birthday", user.birthday)
this.$set(this.formData, "unitId", unitId)
this.$set(this.formData, "unitName", user.unitName)
this.$set(this.formData, "unionId", user.unionid)
this.$set(this.formData, "unionId", unionId)
this.$set(this.formData, "unionName", user.unionName)
this.$set(this.formData, "position", user.position || "")
this.$set(this.formData, "officialCapacity", user.officialCapacity || "")
@@ -554,27 +1034,11 @@ layout("/layouts/platform_h5.html"){
this.$set(this.formData, "idCard", null)
this.$set(this.formData, "homeAddress", null)
}
this.userSheetShow = false
},
onSexConfirm(value) {
this.formData.sex = value
this.showSexPicker = false
},
onDateConfirm(value) {
this.formData.birthday = moment(value).format("YYYY-MM-DD")
this.showDatePicker = false
},
onSubsidyTypeConfirm(option) {
this.$set(this.formData, "subsidyName", option.text);
this.subsidyValue = option.value;
this.showSubsidyPicker = false;
},
onSubsidyStandardConfirm(option) {
this.$set(this.formData, "subsidyStandardsName", option.text);
this.subsidyStandardsValue = option.value;
this.showSubsidyStandardPicker = false;
},
findOne(id) {
this.$axios.post("/platform/difficultHelp/mine/findOne", { id }).then(res => {
@@ -599,7 +1063,6 @@ layout("/layouts/platform_h5.html"){
}
}
this.queryRecipients(res.data.loginName)
}
})
},
@@ -631,7 +1094,6 @@ layout("/layouts/platform_h5.html"){
this.$set(this.formData, 'familyList', [])
this.$set(this.formData, 'files', [])
this.$set(this.formData, 'applyCount', savedApplyCount )
this.queryRecipients(user.loginname)
} else {
this.formData = {}
this.$set(this.formData, 'proxyUserId', user.id)
@@ -774,6 +1236,18 @@ layout("/layouts/platform_h5.html"){
this.modeChange("1")
}
}
},
mounted() {
// 使用布局统一管理器注册弹层状态,处理方式与提案预审核详情弹层保持一致。
if (!window.h5HistoryLayerManager) return
window.h5HistoryLayerManager.register(this.historyLayerPageKey, (stack) => {
this.syncHistoryLayerStack(stack)
})
},
beforeDestroy() {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.pageKey === this.historyLayerPageKey) {
window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
}
}
})
</script>
@@ -1,38 +1,81 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style id="style-difficult-help-approval-list-h5">
<!--#include("../common/approval-list.css"){}#-->
</style>
<style id="style-difficult-help-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<van-sticky>
<div class="difficult-help-approval-page">
<van-nav-bar title="帮扶分工会审核" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<div class="difficult-help-approval-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="difficult-help-approval-search"
placeholder="请输入工号或者姓名进行查询"
show-action
:reverse-color="false"
input-align="left"
@search="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
shape="round"
clearable
@search="doSearch"
@clear="doSearch">
</van-search>
<van-dropdown-menu
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
class="difficult-help-approval-filter"
:class="{'is-open': filterOpened}"
active-color="#1989fa"
:close-on-click-outside="false"
:close-on-click-overlay="false"
>
<van-dropdown-item :options="unionList" @change="unionChange" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="unitList" @change="doSearch" v-model="pageForm.unitId"></van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unionList"
@change="unionChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unionId">
</van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unitList"
@change="doSearch"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unitId">
</van-dropdown-item>
<van-dropdown-item
:options="approvalOptions"
@change="onApprovalFilterChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.approvalText">
</van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.audit = val==='1';this.doSearch();}">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</div>
</van-sticky>
<table-list api="/platform/difficultHelp/branchUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<main class="difficult-help-approval-content">
<div class="difficult-help-list-skeleton" v-if="skeletonLoading">
<div class="difficult-help-list-skeleton__card" v-for="index in 3" :key="'branch-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/difficultHelp/branchUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="difficult-help-approval-card-header">
<div class="difficult-help-approval-card-heading">
<div class="difficult-help-approval-card-title">{{row.userName || '--'}}</div>
<div class="difficult-help-approval-card-code">工号:{{row.loginName || '--'}}</div>
</div>
<enum-tag class="difficult-help-approval-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="受助人">{{row.userName + '(' + row.loginName + ')'}}</table-column>
<table-column label="申请人">{{row.proxyUserName || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="工作单位">{{row.unitName}}</table-column>
<table-column label="困难类型">{{row.subsidyStandards}}</table-column>
@@ -41,43 +84,66 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="difficult-help-approval-empty">
<img src="/assets/mobile/img/difficultHelp/difficult-help-empty.png" alt="暂无帮扶审核记录插画">
<div class="difficult-help-approval-empty__title">暂无帮扶审核记录</div>
<div class="difficult-help-approval-empty__hint">当前筛选条件下暂时没有帮扶申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef">
<info ref="infoRef" page-key="difficult-help-branch-union-detail" full-screen>
<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="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<section class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">
审批意见<span class="difficult-help-audit-card__required">*</span>
</h2>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
class="difficult-help-audit-textarea"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
</van-form>
</section>
<div class="difficult-help-popup-actions">
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">拒绝</van-button>
<van-button class="difficult-help-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm">
</van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("../common/info.js"){}#-->
@@ -88,8 +154,9 @@ layout("/layouts/platform_h5.html"){
data() {
return {
pageForm: {
pageNo: 1,
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
unionId: '',
unitId: '',
@@ -98,9 +165,21 @@ layout("/layouts/platform_h5.html"){
},
unionList: [],
unitList: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
filterOpened: false,
skeletonLoading: true,
formData: {},
showApprovalForm: false
showApprovalForm: false,
formLoading: false,
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
components: {
@@ -109,6 +188,24 @@ layout("/layouts/platform_h5.html"){
methods: {
historyBack,
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 audit 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "audit", val === "1")
this.doSearch()
},
// 同步筛选面板展开状态,用于调整下拉框底部圆角。
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
onReady() {
this.doSearch()
},
@@ -123,99 +220,139 @@ layout("/layouts/platform_h5.html"){
// 查看详情
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "帮扶详情")
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.infoRef.onOpen(row)
this.formData = {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName
}
taskName: row.curTaskName,
tf_opinion: ""
})
this.$refs.infoRef.onOpen(row, 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.infoRef.onClose()
// val 为流程提交类型:1 同意、2 拒绝、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 提交流程任务参数,接口成功后清理详情弹框历史并刷新列表。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
})
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
})
} catch (error) {
}
},
// 撤回
onRevoke(row) {
debugger
this.$dialog.confirm({
title: '提示',
message: '您确定要撤回吗?',
}).then(() => {
this.$axios.post('/flow/common/revokeTask', {taskId: row.taskId}).then(res => {
if (res.code === 0) {
this.$toast.success('撤回成功');
this.doSearch();
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
}).catch(() => {
});
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后由统一确认框执行撤回。
onRevoke(row) {
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || "撤回成功")
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
unionChange() {
this.$set(this.pageForm, "unitId", null)
this.flushUnits()
this.doSearch()
},
async initUnion() {
initUnion() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? null : this.$store.state.user.union.id
this.unionList = await this.$businessTool.listUnion(unionId)
this.unionList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unionOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unionList", list || [])
this.unionList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
if (hasAdmin) this.unionList.unshift({text: "全部工会", value: null})
if (this.unionList.length > 0) this.$set(this.pageForm, "unionId", this.unionList[0].value)
})
if (hasAdmin) {
this.unionList.unshift({ text: "全部工会", value: null })
}
if (this.unionList && this.unionList.length > 0) {
this.$set(this.pageForm, "unionId", this.unionList[0].value)
}
},
async flushUnits() {
flushUnits() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? this.pageForm.unionId : this.$store.state.user.union.id
this.unitList = await this.$businessTool.listUnit(unionId)
this.unitList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unitOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unitList", list || [])
this.unitList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
this.unitList.unshift({text: "全部单位", value: null})
if (this.unitList.length > 0) this.$set(this.pageForm, "unitId", this.unitList[0].value)
})
this.unitList.unshift({ text: "全部单位", value: null })
if (this.unitList && this.unitList.length > 0) {
this.$set(this.pageForm, "unitId", this.unitList[0].value)
}
},
},
async created() {
await this.initUnion()
await this.flushUnits()
created() {
this.initUnion().then(() => this.flushUnits())
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("difficult-help-action-confirm"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>
@@ -0,0 +1,307 @@
#app {
--color-primary: #1989fa;
}
.difficult-help-approval-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-approval-page .van-nav-bar,
.difficult-help-approval-page .van-sticky--fixed {
background: #fff;
}
.difficult-help-approval-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.difficult-help-approval-page .van-nav-bar .van-icon,
.difficult-help-approval-page .van-nav-bar__text {
color: #1989fa;
}
.difficult-help-approval-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-approval-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.difficult-help-approval-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.difficult-help-approval-search .van-field__control {
color: #263548;
font-size: 14px;
}
.difficult-help-approval-search .van-field__control::placeholder {
color: #a7b0bf;
}
.difficult-help-approval-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.difficult-help-approval-filter.is-open {
border-radius: 10px 10px 0 0;
}
.difficult-help-approval-filter .van-dropdown-menu__bar,
.difficult-help-approval-filter .van-dropdown-menu__item,
.difficult-help-approval-filter .van-dropdown-menu__title {
height: 44px;
}
.difficult-help-approval-filter .van-dropdown-menu__bar {
box-shadow: none;
}
.difficult-help-approval-filter .van-dropdown-menu__item {
min-width: 0;
align-items: center;
justify-content: center;
}
.difficult-help-approval-filter .van-dropdown-menu__title {
display: inline-flex;
max-width: calc(100% - 12px);
align-items: center;
color: #66758a;
font-size: 13px;
line-height: 20px;
}
.difficult-help-approval-filter .van-dropdown-menu__title--active,
.difficult-help-approval-filter .van-dropdown-item__option--active,
.difficult-help-approval-filter .van-dropdown-item__option--active .van-cell__title,
.difficult-help-approval-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.difficult-help-approval-filter .van-dropdown-item__content {
width: calc(100% - 24px);
max-height: 50%;
margin: 0 12px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.difficult-help-approval-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.difficult-help-approval-filter .van-dropdown-item__option .van-cell__title {
display: flex;
min-width: 0;
min-height: 20px;
align-items: center;
flex: 1 1 auto;
line-height: 20px;
}
.difficult-help-approval-filter .van-dropdown-item__option .van-cell__value {
display: flex;
width: 20px;
min-width: 0;
overflow: visible;
align-items: center;
justify-content: flex-end;
flex: 0 0 20px;
}
.difficult-help-approval-content {
padding: 0 12px;
}
.difficult-help-list-skeleton {
padding-bottom: 2px;
}
.difficult-help-list-skeleton__card {
margin-bottom: 12px;
padding: 14px;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-list-skeleton__card .van-skeleton {
padding: 0;
}
.difficult-help-list-skeleton__card .van-skeleton__title {
width: 34%;
height: 17px;
margin-bottom: 13px;
}
.difficult-help-list-skeleton__card .van-skeleton__row {
height: 12px;
margin-top: 11px;
}
.difficult-help-list-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 88%;
}
.difficult-help-list-skeleton__card .van-skeleton__row:last-child {
width: 48%;
margin-top: 18px;
}
.difficult-help-approval-page .table-list-container {
margin-top: 0;
padding-bottom: 2px;
}
.difficult-help-approval-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-approval-card-header {
display: flex;
margin-bottom: 8px;
align-items: flex-start;
justify-content: space-between;
}
.difficult-help-approval-card-heading {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.difficult-help-approval-card-title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.difficult-help-approval-card-code {
margin-top: 1px;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-card-status {
display: inline-flex;
flex: none;
}
.difficult-help-approval-card-status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn.delete {
color: #f04444;
background: #fff0f0;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn .van-icon {
margin-right: 5px;
font-size: 13px;
}
.difficult-help-approval-page .empty-state {
padding: 0;
}
.difficult-help-approval-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.difficult-help-approval-empty img {
display: block;
width: 86%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.difficult-help-approval-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.difficult-help-approval-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
@@ -0,0 +1,353 @@
.difficult-help-full-popup {
--color-primary: #1989fa;
width: 100%;
height: 100%;
overflow: hidden;
background: #f3f7fc;
}
.difficult-help-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.difficult-help-full-popup__page > .van-nav-bar {
z-index: 20;
background: #fff;
}
.difficult-help-full-popup__page > .van-nav-bar .van-icon,
.difficult-help-full-popup__page > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.difficult-help-full-popup__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.difficult-help-info-main {
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
}
.difficult-help-info-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.difficult-help-info-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.difficult-help-info-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
.difficult-help-audit-card__required {
margin-left: 4px;
color: #ee0a24;
font-size: 14px;
}
.difficult-help-audit-form {
padding-bottom: 4px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select,
.difficult-help-info-card .van-field.difficult-help-audit-money {
min-height: 48px;
padding: 11px 14px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__label,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__label {
width: 88px;
color: #596779;
font-size: 12px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__control,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__control {
color: #253247;
font-size: 13px;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__control {
min-height: 132px;
color: #253247;
font-size: 13px;
line-height: 22px;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__control::placeholder,
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__control::placeholder,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__control::placeholder {
color: #9aa7b8;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__word-limit {
color: #718096;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea::after {
display: none;
}
.difficult-help-popup-actions {
display: flex;
padding: 10px 0 calc(12px + env(safe-area-inset-bottom));
justify-content: space-between;
column-gap: 10px;
background: transparent;
}
.difficult-help-popup-actions .van-button {
min-width: 0;
border-radius: 14px;
}
.difficult-help-popup-actions .difficult-help-action-primary {
border-color: #1989fa;
background: #1989fa;
}
.difficult-help-popup-actions .difficult-help-action-danger {
border-color: #ff4d4f;
background: #ff4d4f;
}
.difficult-help-info-card .van-cell-group {
margin: 0;
background: transparent;
}
.difficult-help-info-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.difficult-help-info-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.difficult-help-info-card .van-cell__title {
width: 112px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 112px;
}
.difficult-help-info-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.difficult-help-info-long-cell {
display: block;
}
.difficult-help-info-long-cell .van-cell__title {
width: auto;
max-width: 100%;
}
.difficult-help-info-long-cell .van-cell__value {
margin-top: 7px;
}
.difficult-help-info-pre-line {
white-space: pre-line;
overflow-wrap: anywhere;
}
.difficult-help-family-list {
padding: 0 14px 14px;
}
.difficult-help-family-item {
overflow: hidden;
border: 1px solid #e5edf7;
border-radius: 10px;
background: #fff;
box-shadow: 0 2px 8px rgba(51, 87, 126, .06);
}
.difficult-help-family-item + .difficult-help-family-item {
margin-top: 10px;
}
.difficult-help-family-header {
display: flex;
min-height: 44px;
padding: 9px 11px;
align-items: center;
box-sizing: border-box;
background: linear-gradient(90deg, #f0f6ff 0%, #f8fbff 100%);
}
.difficult-help-family-index {
display: inline-flex;
width: 24px;
height: 24px;
margin-right: 8px;
align-items: center;
justify-content: center;
flex: 0 0 24px;
border-radius: 50%;
color: #fff;
background: #1989fa;
font-size: 12px;
font-weight: 600;
}
.difficult-help-family-name {
min-width: 0;
overflow: hidden;
color: #2f3d51;
font-size: 14px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.difficult-help-family-relation {
max-width: 92px;
margin-left: 8px;
padding: 2px 8px;
overflow: hidden;
border-radius: 10px;
color: #1989fa;
background: #e7f2ff;
font-size: 11px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
flex: none;
}
.difficult-help-family-summary {
display: grid;
margin: 0 11px;
padding: 11px 0;
grid-template-columns: repeat(2, minmax(0, 1fr));
border-bottom: 1px solid #eef2f7;
}
.difficult-help-family-summary__item {
min-width: 0;
padding: 0 8px;
}
.difficult-help-family-summary__item:first-child {
padding-left: 0;
border-right: 1px solid #eef2f7;
}
.difficult-help-family-summary__item span,
.difficult-help-family-summary__item strong,
.difficult-help-family-detail__row span,
.difficult-help-family-detail__row strong {
display: block;
overflow-wrap: anywhere;
}
.difficult-help-family-summary__item span,
.difficult-help-family-detail__row span {
color: #909bac;
font-size: 11px;
line-height: 17px;
}
.difficult-help-family-summary__item strong {
margin-top: 3px;
color: #435167;
font-size: 13px;
font-weight: 500;
line-height: 20px;
}
.difficult-help-family-detail {
padding: 4px 11px 7px;
}
.difficult-help-family-detail__row {
display: grid;
padding: 7px 0;
grid-template-columns: 68px minmax(0, 1fr);
column-gap: 8px;
}
.difficult-help-family-detail__row strong {
color: #536176;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
.difficult-help-info-card .van-empty {
padding: 12px 0 20px;
}
.difficult-help-info-promise {
color: #4f5d70;
font-size: 12px;
line-height: 21px;
}
.difficult-help-info-commitment {
display: flex;
margin-top: 8px;
align-items: flex-end;
color: #8995a6;
font-size: 11px;
line-height: 18px;
flex-direction: column;
}
.difficult-help-signature-image {
display: block;
max-width: 100%;
}
@@ -1,166 +1,326 @@
const INFO = {
template: /*language=HTML*/ `
<van-action-sheet v-model="visible" title="查看详情">
<div class="detail-container">
<van-cell-group title="基本信息">
<van-cell title="填写人姓名">{{ viewData.proxyUserName }}</van-cell>
<van-cell title="填写人工号">{{ viewData.proxyLoginName }}</van-cell>
<van-cell title="性别">{{ viewData.sex }}</van-cell>
<component
:is="detailComponent"
v-model="visible"
:title="detailTitle"
:position="detailPosition"
:class="detailClass"
:close-on-click-overlay="false"
:get-container="detailContainer"
>
<div :class="{'difficult-help-full-popup__page': fullScreen, 'detail-container': !fullScreen}">
<van-nav-bar
v-if="fullScreen"
:title="popupTitle"
left-arrow
left-text="返回"
fixed
@click-left="onClose()"
></van-nav-bar>
<div ref="detailScrollRef" :class="{'difficult-help-full-popup__scroll': fullScreen}">
<main :class="{'difficult-help-info-main': fullScreen}">
<section :class="{'difficult-help-info-card': fullScreen}">
<h2 v-if="fullScreen" class="difficult-help-info-card__title">基本信息</h2>
<van-cell-group :title="fullScreen ? '' : '基本信息'" :border="!fullScreen">
<van-cell title="填写人姓名">{{ viewData.proxyUserName || '--' }}</van-cell>
<van-cell title="填写人工号">{{ viewData.proxyLoginName || '--' }}</van-cell>
<van-cell title="性别">{{ viewData.sex || '--' }}</van-cell>
<van-cell title="受助人">
{{ viewData.userName || '--' }}{{ viewData.loginName ? '' + viewData.loginName + '' : '' }}
</van-cell>
<van-cell title="所在单位">{{ viewData.unitName || '--' }}</van-cell>
<van-cell title="所在工会">{{ viewData.unionName || '--' }}</van-cell>
<van-cell title="职务">{{ viewData.position || '--' }}</van-cell>
<van-cell title="职称">{{ viewData.officialCapacity || '--' }}</van-cell>
<van-cell title="联系电话">{{ viewData.mobile || '--' }}</van-cell>
<van-cell title="家庭年总收入(万元)">{{ viewData.homeIncome || '--' }}</van-cell>
<van-cell title="家庭人数">{{ viewData.homeNumber || '--' }}</van-cell>
<van-cell :class="{'difficult-help-info-long-cell': fullScreen}" title="家庭住址">
{{ viewData.homeAddress || '--' }}
</van-cell>
<van-cell title="补助类型">
<dict-tag :options="dict.type.DIFFICULT_SUBSIDY_TYPE" :value="viewData.subsidy"></dict-tag>
</van-cell>
<van-cell title="困难类型">
<dict-tag :options="dict.type.DIFFICULT_TYPE" :value="viewData.subsidyStandards"></dict-tag>
</van-cell>
<van-cell title="申请次数">{{ viewData.applyCount || 1 }}</van-cell>
<van-cell :class="{'difficult-help-info-long-cell': fullScreen}" title="开户行">
{{ viewData.bankOfDeposit || '--' }}
</van-cell>
<van-cell title="银行卡号">{{ viewData.bankCardNum || '--' }}</van-cell>
<van-cell :class="{'difficult-help-info-long-cell': fullScreen, 'text-cell': !fullScreen}" title="生活致困原因">
<div class="difficult-help-info-pre-line">{{ viewData.reason || '暂无' }}</div>
</van-cell>
</van-cell-group>
</section>
<van-cell title="受助人">{{ viewData.userName + '(' + viewData.loginName + ')' }}</van-cell>
<van-cell title="所在单位">{{ viewData.unitName }}</van-cell>
<van-cell title="所在工会">{{ viewData.unionName }}</van-cell>
<van-cell title="职务">{{ viewData.position }}</van-cell>
<van-cell title="职称">{{ viewData.officialCapacity }}</van-cell>
<van-cell title="联系电话">{{ viewData.mobile }}</van-cell>
<van-cell title="家庭年总收入(万元)">{{ viewData.homeIncome }}</van-cell>
<van-cell title="家庭人数">{{ viewData.homeNumber }}</van-cell>
<van-cell title="家庭住址">{{ viewData.homeAddress }}</van-cell>
<van-cell title="补助类型">
<dict-tag :options="dict.type.DIFFICULT_SUBSIDY_TYPE" :value="viewData.subsidy"></dict-tag>
</van-cell>
<van-cell title="困难类型">
<dict-tag :options="dict.type.DIFFICULT_TYPE" :value="viewData.subsidyStandards"></dict-tag>
</van-cell>
<van-cell title="申请次数">{{ viewData.applyCount || 1 }}</van-cell>
<van-cell title="开户行">{{ viewData.bankOfDeposit }}</van-cell>
<van-cell title="银行卡号">{{ viewData.bankCardNum }}</van-cell>
<van-cell title="生活致困原因" class="text-cell">
<template #label>
<div style="word-wrap: break-word; white-space: pre-line;">
{{viewData.reason}}
<section v-if="fullScreen" class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">家庭成员</h2>
<div v-if="viewData.familyList && viewData.familyList.length" class="difficult-help-family-list">
<article
v-for="(item, index) in viewData.familyList"
:key="index"
class="difficult-help-family-item"
>
<header class="difficult-help-family-header">
<span class="difficult-help-family-index">{{ index + 1 }}</span>
<strong class="difficult-help-family-name">{{ item.name || '未填写姓名' }}</strong>
<span class="difficult-help-family-relation">{{ item.relation || '关系未填写' }}</span>
</header>
<div class="difficult-help-family-summary">
<div class="difficult-help-family-summary__item">
<span>年龄</span>
<strong>
{{ item.age !== null && item.age !== undefined && item.age !== '' ? item.age + ' 岁' : '--' }}
</strong>
</div>
<div class="difficult-help-family-summary__item">
<span>年收入</span>
<strong>
{{ item.yearIncome !== null && item.yearIncome !== undefined && item.yearIncome !== '' ? item.yearIncome + ' 元' : '--' }}
</strong>
</div>
</div>
<div class="difficult-help-family-detail">
<div class="difficult-help-family-detail__row">
<span>工作单位</span>
<strong>{{ item.homeUnit || '--' }}</strong>
</div>
<div class="difficult-help-family-detail__row">
<span>备注</span>
<strong>{{ item.remarks || '--' }}</strong>
</div>
</div>
</article>
</div>
</template>
</van-cell>
<van-empty v-else description="暂无家庭成员信息" image-size="72"></van-empty>
</section>
<van-cell title="家庭成员" class="column-cell">
<table class="family-table">
<thead>
<tr>
<th class="table-header">序号</th>
<th class="table-header">与本人关系</th>
<th class="table-header">姓名</th>
<th class="table-header">年龄</th>
<th class="table-header">工作单位</th>
<th class="table-header">年收入()</th>
<th class="table-header">备注</th>
</tr>
</thead>
<tbody>
<tr class="table-row" v-for="(item, index) in viewData.familyList" :key="index">
<td class="table-cell">{{ index + 1 }}</td>
<td class="table-cell">{{ item.relation }}</td>
<td class="table-cell">{{ item.name }}</td>
<td class="table-cell">{{ item.age }}</td>
<td class="table-cell">{{ item.homeUnit }}</td>
<td class="table-cell">{{ item.yearIncome }}</td>
<td class="table-cell">{{ item.remarks }}</td>
</tr>
</tbody>
</table>
</van-cell>
<van-cell-group v-else title="家庭成员">
<van-cell class="column-cell">
<table class="family-table">
<thead>
<tr>
<th class="table-header">序号</th>
<th class="table-header">与本人关系</th>
<th class="table-header">姓名</th>
<th class="table-header">年龄</th>
<th class="table-header">工作单位</th>
<th class="table-header">年收入()</th>
<th class="table-header">备注</th>
</tr>
</thead>
<tbody>
<tr class="table-row" v-for="(item, index) in viewData.familyList" :key="index">
<td class="table-cell">{{ index + 1 }}</td>
<td class="table-cell">{{ item.relation }}</td>
<td class="table-cell">{{ item.name }}</td>
<td class="table-cell">{{ item.age }}</td>
<td class="table-cell">{{ item.homeUnit }}</td>
<td class="table-cell">{{ item.yearIncome }}</td>
<td class="table-cell">{{ item.remarks }}</td>
</tr>
</tbody>
</table>
</van-cell>
</van-cell-group>
<van-cell title="本人承诺">
<div style="text-align: left; padding: 5px 0; font-size: 14px;">
本人及家庭成员未有购买价格超过15万元的机动车也未有两套及以上商品房特此承诺
</div>
<div style="text-align: right; font-size: 12px; color: #999; margin-top: 5px;">
<span>承诺人{{ viewData.userName }}</span>
<br/>
<span>{{ $moment(viewData.applyTime).format("YYYY-MM-DD") }}</span>
</div>
</van-cell>
<section :class="{'difficult-help-info-card': fullScreen}">
<h2 v-if="fullScreen" class="difficult-help-info-card__title">承诺与材料</h2>
<van-cell-group :title="fullScreen ? '' : '承诺与材料'" :border="!fullScreen">
<van-cell :class="{'difficult-help-info-long-cell': fullScreen}" title="本人承诺">
<div class="difficult-help-info-promise">
本人及家庭成员未有购买价格超过15万元的机动车也未有两套及以上商品房特此承诺
</div>
<div class="difficult-help-info-commitment">
<span>承诺人{{ viewData.userName || '--' }}</span>
<span>{{ viewData.applyTime ? $moment(viewData.applyTime).format('YYYY-MM-DD') : '--' }}</span>
</div>
</van-cell>
<van-cell :class="{'difficult-help-info-long-cell': fullScreen}" title="签字">
<van-image
v-if="viewData.sign"
:src="viewData.sign"
class="difficult-help-signature-image"
></van-image>
<span v-else>暂无签字</span>
</van-cell>
<van-cell :class="{'difficult-help-info-long-cell': fullScreen}" title="附件">
<file-preview v-if="fullScreen" :files="viewData.files" complete_result></file-preview>
<template v-else-if="viewData.files && viewData.files.length">
<div v-for="(file, index) in viewData.files" :key="index">
<a :href="file.url" target="_blank">{{ file.name }}</a>
</div>
</template>
<span v-else>暂无</span>
</van-cell>
</van-cell-group>
</section>
<van-cell title="签字" v-if="viewData.sign">
<img :src="viewData.sign" style="max-width: 100px; max-height: 50px;" />
</van-cell>
<van-cell title="附件" v-if="viewData.files && viewData.files.length > 0">
<div v-for="(file, index) in viewData.files" :key="index" style="margin-bottom: 5px;">
<a :href="file.url" target="_blank">{{ file.name }}</a>
</div>
</van-cell>
<van-cell title="附件" v-else>
<span>暂无</span>
</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="审批级别" v-if="task.displayName == '校工会审核'">
{{ viewData.level }}
</van-cell>
<van-cell title="审批金额" v-if="task.displayName == '校工会审核'">
{{ '¥' + viewData.money }}
</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</van-cell>
<van-cell title="办理意见" v-if="!task.ext.isFirstTaskNode && task.taskFormData.opinion" class="direction-column-cell">
<div style="white-space: pre-line;">{{ task.taskFormData.opinion }}</div>
</van-cell>
</van-cell-group>
</template>
<section
v-for="(task, index) in doneTasks"
:key="task.id || index"
:class="{'difficult-help-info-card': fullScreen}"
>
<h2 v-if="fullScreen" class="difficult-help-info-card__title">
{{ task.displayName || '办理记录' }}
</h2>
<van-cell-group
v-if="task.ext && task.ext.isFirstTaskNode"
:title="fullScreen ? '' : task.displayName"
:border="!fullScreen"
>
<van-cell title="申请用户">
{{ task.ext.initiatorName || '--' }}{{ task.ext.initiatorAccount ? '' + 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 v-else :title="fullScreen ? '' : task.displayName" :border="!fullScreen">
<van-cell title="办理用户">
{{ task.taskFormData && task.taskFormData.userName || '--' }}{{ task.taskFormData && task.taskFormData.loginName ? '' + task.taskFormData.loginName + '' : '' }}
</van-cell>
<van-cell title="办理时间">{{ task.finishTime || '--' }}</van-cell>
<van-cell v-if="task.displayName === '校工会审核'" title="审批级别">
{{ viewData.level || '--' }}
</van-cell>
<van-cell v-if="task.displayName === '校工会审核'" title="审批金额">
{{ viewData.money ? '¥' + viewData.money : '--' }}
</van-cell>
<van-cell title="办理结果">
<dict-tag
:options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext && task.ext.submitType"
></dict-tag>
</van-cell>
<van-cell
v-if="task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)"
:class="{'difficult-help-info-long-cell': fullScreen, 'direction-column-cell': !fullScreen}"
title="办理意见"
>
<div class="difficult-help-info-pre-line">
{{ task.taskFormData.opinion || task.taskFormData.tf_opinion }}
</div>
</van-cell>
</van-cell-group>
</section>
<slot></slot>
</main>
</div>
</div>
<slot></slot>
</van-action-sheet>
</component>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE", "DIFFICULT_SUBSIDY_TYPE", "DIFFICULT_TYPE"], data() {
dicts: ["PROCESS_TASK_SUBMIT_TYPE", "DIFFICULT_SUBSIDY_TYPE", "DIFFICULT_TYPE"],
props: {
fullScreen: {
type: Boolean,
default: false
},
pageKey: {
type: String,
default: ""
}
},
data() {
return {
visible: false,
popupTitle: "帮扶详情",
row: {},
viewData: {},
doneTasks: [],
historyLayerUnsubscribe: null
}
},
computed: {
detailComponent() {
return this.fullScreen ? "van-popup" : "van-action-sheet"
},
detailTitle() {
return this.fullScreen ? undefined : "查看详情"
},
detailPosition() {
return this.fullScreen ? "right" : undefined
},
detailClass() {
return this.fullScreen ? "difficult-help-full-popup" : ""
},
detailContainer() {
return this.fullScreen ? "#app" : undefined
}
},
methods: {
onOpen(row) {
this.row = row
this.visible = true
this.info()
this.getDoneTasks()
// 浏览器历史变化时,只同步当前困难帮扶详情弹框的显示状态。
syncHistoryLayerStack(stack) {
this.$set(this, "visible", stack.includes("difficult-help-detail"))
},
// 关闭
onClose(){
this.visible = false
// 全屏详情复用同一滚动容器,每次切换记录后将内部滚动位置恢复到基本信息顶部。
resetScrollPosition() {
if (!this.fullScreen) return
this.$nextTick(() => {
const scrollContainer = this.$refs.detailScrollRef
if (scrollContainer) scrollContainer.scrollTop = 0
})
},
/**
* 打开困难帮扶详情
* row 传列表当前行title 传顶部导航标题方法无返回值详情数据和流程记录由组件内部加载
*/
onOpen(row, title = "帮扶详情") {
this.$set(this, "row", row || {})
this.$set(this, "popupTitle", title)
this.$set(this, "viewData", {})
this.$set(this, "doneTasks", [])
this.info()
this.getDoneTasks()
this.resetScrollPosition()
if (this.fullScreen && window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-detail")) return
this.$set(this, "visible", true)
},
// 顶部返回按钮与业务主动关闭统一回退详情弹框对应的浏览器历史。
onClose(afterClose = null) {
if (this.fullScreen && window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-detail", afterClose)) return
this.$set(this, "visible", false)
if (typeof afterClose === "function") afterClose()
},
info() {
this.$axios.post("/platform/difficultHelp/mine/findOne", { id: this.row.id }).then((res) => {
this.$axios.post("/platform/difficultHelp/mine/findOne", {id: this.row.id}).then((res) => {
if (res.code === 0) {
this.viewData = res.data
this.$set(this, "viewData", res.data || {})
}
})
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
this.$set(this, "doneTasks", res.data || [])
}
})
},
}
},
created() {
if (!this.fullScreen || !this.pageKey || !window.h5HistoryLayerManager) return
window.h5HistoryLayerManager.ensureRegistered(this.pageKey)
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayerStack(stack)
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
if (this.fullScreen && window.h5HistoryLayerManager && window.h5HistoryLayerManager.pageKey === this.pageKey) {
window.h5HistoryLayerManager.unregister(this.pageKey)
}
},
style: /*language=CSS*/ `
.column-cell {
@@ -169,21 +329,21 @@ const INFO = {
.family-table {
width: 100%;
max-width: 100%;
margin: 20px 0;
border-collapse: collapse;
table-layout: auto;
font-family: Arial, sans-serif;
text-align: center;
font-family: 'Arial', sans-serif;
margin: 20px 0;
}
.table-header {
background-color: gray;
color: white;
background-color: gray;
font-weight: bold;
text-transform: uppercase;
}
.table-row {
background-color: #f9f9f9;
transition: background-color 0.3s;
transition: background-color .3s;
}
.table-row:hover {
background-color: #f1f1f1;
@@ -191,8 +351,8 @@ const INFO = {
.table-cell {
padding: 10px 5px;
border: 1px solid #ddd;
text-align: center;
font-size: 12px;
text-align: center;
}
.text-cell {
word-break: break-all;
@@ -1,33 +1,382 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app {
--color-primary: #1989fa;
}
.difficult-help-list-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-list-page .van-nav-bar,
.difficult-help-list-page .van-sticky--fixed {
background: #fff;
}
.difficult-help-list-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.difficult-help-list-page .van-nav-bar .van-icon,
.difficult-help-list-page .van-nav-bar__text {
color: #1989fa;
}
.difficult-help-list-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-list-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.difficult-help-list-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.difficult-help-list-search .van-field__control {
color: #263548;
font-size: 14px;
}
.difficult-help-list-search .van-field__control::placeholder {
color: #a7b0bf;
}
.difficult-help-list-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.difficult-help-list-filter.is-open {
border-radius: 10px 10px 0 0;
}
.difficult-help-list-filter .van-dropdown-menu__bar {
height: 44px;
box-shadow: none;
}
.difficult-help-list-filter .van-dropdown-menu__item,
.difficult-help-list-filter .van-dropdown-menu__title {
height: 44px;
}
.difficult-help-list-filter .van-dropdown-menu__item {
align-items: center;
justify-content: center;
}
.difficult-help-list-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
line-height: 20px;
color: #66758a;
font-size: 14px;
}
.difficult-help-list-filter .van-dropdown-menu__title--active,
.difficult-help-list-filter .van-dropdown-item__option--active,
.difficult-help-list-filter .van-dropdown-item__option--active .van-cell__title,
.difficult-help-list-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.difficult-help-list-filter .van-dropdown-item__content {
width: calc(100% - 24px);
max-height: 50%;
margin: 0 12px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.difficult-help-list-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.difficult-help-list-filter .van-dropdown-item__option .van-cell__title {
display: flex;
min-width: 0;
min-height: 20px;
align-items: center;
flex: 1 1 auto;
line-height: 20px;
}
.difficult-help-list-filter .van-dropdown-item__option .van-cell__value {
display: flex;
width: 20px;
min-width: 0;
overflow: visible;
align-items: center;
justify-content: flex-end;
flex: 0 0 20px;
}
.difficult-help-list-filter .van-dropdown-item__option .van-dropdown-item__icon {
margin-left: 4px;
}
.difficult-help-list-content {
padding: 0 12px;
}
.difficult-help-list-skeleton {
padding-bottom: 2px;
}
.difficult-help-list-skeleton__card {
margin-bottom: 12px;
padding: 14px;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-list-skeleton__card .van-skeleton {
padding: 0;
}
.difficult-help-list-skeleton__card .van-skeleton__title {
width: 34%;
height: 17px;
margin-bottom: 13px;
}
.difficult-help-list-skeleton__card .van-skeleton__row {
height: 12px;
margin-top: 11px;
}
.difficult-help-list-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 88%;
}
.difficult-help-list-skeleton__card .van-skeleton__row:last-child {
width: 48%;
margin-top: 18px;
}
.difficult-help-list-page .table-list-container {
margin-top: 0;
padding-bottom: 2px;
}
.difficult-help-list-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-card-header {
display: flex;
margin-bottom: 8px;
align-items: flex-start;
justify-content: space-between;
}
.difficult-help-card-heading {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.difficult-help-card-title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.difficult-help-card-code {
margin-top: 1px;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.difficult-help-card-status {
display: inline-flex;
flex: none;
}
.difficult-help-card-status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.difficult-help-list-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.difficult-help-list-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.difficult-help-list-page .table-list-container .table-list-item .action-btn.delete {
color: #f04444;
background: #fff0f0;
}
.difficult-help-list-page .table-list-container .table-list-item .action-btn i,
.difficult-help-list-page .table-list-container .table-list-item .action-btn .van-icon {
margin-right: 5px;
font-size: 13px;
}
.difficult-help-list-page .empty-state {
padding: 0;
}
.difficult-help-list-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.difficult-help-list-empty img {
display: block;
width: 86%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.difficult-help-list-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.difficult-help-list-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
</style>
<style id="style-difficult-help-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<van-sticky>
<div class="difficult-help-list-page">
<van-nav-bar title="帮扶申请记录" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<div class="difficult-help-list-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="difficult-help-list-search"
placeholder="请输入工号或者姓名进行查询"
show-action
:reverse-color="false"
input-align="left"
@search="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
shape="round"
clearable
@search="doSearch"
@clear="doSearch">
</van-search>
<van-dropdown-menu
class="difficult-help-list-filter"
:class="{'is-open': filterOpened}"
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
active-color="#1989fa"
:close-on-click-outside="false"
:close-on-click-overlay="false"
>
<van-dropdown-item :options="unionList" @change="unionChange" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="unitList" @change="doSearch" v-model="pageForm.unitId"></van-dropdown-item>
<van-dropdown-item
:options="unionList"
@change="unionChange"
@open="filterOpened = true"
@close="filterOpened = false"
v-model="pageForm.unionId">
</van-dropdown-item>
<van-dropdown-item
:options="unitList"
@change="doSearch"
@open="filterOpened = true"
@close="filterOpened = false"
v-model="pageForm.unitId">
</van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<table-list api="/platform/difficultHelp/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="doSearch">
<main class="difficult-help-list-content">
<div class="difficult-help-list-skeleton" v-if="skeletonLoading">
<div class="difficult-help-list-skeleton__card" v-for="index in 3" :key="'mine-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/difficultHelp/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="doSearch"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="difficult-help-card-header">
<div class="difficult-help-card-heading">
<div class="difficult-help-card-title">{{row.userName || '--'}}</div>
<div class="difficult-help-card-code">工号:{{row.loginName || '--'}}</div>
</div>
<enum-tag class="difficult-help-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="受助人">{{row.userName + '(' + row.loginName + ')'}}</table-column>
<table-column label="申请人">{{row.proxyUserName || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="工作单位">{{row.unitName}}</table-column>
<table-column label="困难类型">{{row.subsidyStandards}}</table-column>
@@ -36,27 +385,36 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskKey === 'startTask' || !row.instanceId"
@click="onEdit(row)">
<i class="fa fa-edit"></i>
<van-icon name="edit"></van-icon>
<span>编辑</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
<div class="action-btn delete" v-if="row.taskKey === 'startTask' || !row.instanceId"
@click="onDelete(row.id)">
<i class="fa fa-trash"></i>
<van-icon name="delete-o"></van-icon>
<span>删除</span>
</div>
</template>
<template #empty>
<div class="difficult-help-list-empty">
<img src="/assets/mobile/img/difficultHelp/difficult-help-empty.png" alt="暂无帮扶申请记录插画">
<div class="difficult-help-list-empty__title">暂无帮扶申请记录</div>
<div class="difficult-help-list-empty__hint">当前筛选条件下暂时没有帮扶申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef"></info>
<info ref="infoRef" page-key="difficult-help-mine-detail" full-screen></info>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("../common/info.js"){}#-->
@@ -75,6 +433,8 @@ layout("/layouts/platform_h5.html"){
},
unionList: [],
unitList: [],
filterOpened: false,
skeletonLoading: true,
}
},
components: {
@@ -83,6 +443,11 @@ layout("/layouts/platform_h5.html"){
methods: {
historyBack,
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
onView(row) {
this.$refs.infoRef.onOpen(row)
},
@@ -124,49 +489,51 @@ layout("/layouts/platform_h5.html"){
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
// 等待下拉框的 v-model 与 table-list 内部分页参数同步后再查询,避免旧参数回写覆盖新选中值。
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
unionChange() {
this.$set(this.pageForm, "unitId", null)
this.flushUnits()
this.doSearch()
},
async initUnion() {
// unionId 为当前用户可查询的工会范围;返回 Promise,结果写入 Vant 工会选项。
initUnion() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? null : this.$store.state.user.union.id
this.unionList = await this.$businessTool.listUnion(unionId)
this.unionList.forEach((v) => {
v.text = v.name
v.value = v.id
})
if (hasAdmin) {
this.unionList.unshift({ text: "全部工会", value: null })
}
if (this.unionList && this.unionList.length > 0) {
this.$set(this.pageForm, "unionId", this.unionList[0].value)
}
return this.$axios.post("/open/common/unionOptions", {unionId}, {h5SkeletonLoading: true})
.then((res) => {
this.$set(this, "unionList", res && res.code === 0 ? (res.data || []) : [])
this.unionList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
if (hasAdmin) this.unionList.unshift({text: "全部工会", value: null})
if (this.unionList.length > 0) this.$set(this.pageForm, "unionId", this.unionList[0].value)
})
},
async flushUnits() {
// unionId 为已选工会;返回 Promise,结果写入 Vant 单位选项。
flushUnits() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? this.pageForm.unionId : this.$store.state.user.union.id
this.unitList = await this.$businessTool.listUnit(unionId)
this.unitList.forEach((v) => {
v.text = v.name
v.value = v.id
})
if (hasAdmin && !this.pageForm.unionId) {
this.unitList.unshift({ text: "全部单位", value: null })
}
if (this.unitList && this.unitList.length > 0) {
this.$set(this.pageForm, "unitId", this.unitList[0].value)
}
return this.$axios.post("/open/common/unitOptions", {unionId}, {h5SkeletonLoading: true})
.then((res) => {
this.$set(this, "unitList", res && res.code === 0 ? (res.data || []) : [])
this.unitList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
if (hasAdmin && !this.pageForm.unionId) this.unitList.unshift({text: "全部单位", value: null})
if (this.unitList.length > 0) this.$set(this.pageForm, "unitId", this.unitList[0].value)
})
},
},
async created() {
await this.initUnion()
await this.flushUnits()
created() {
this.initUnion().then(() => this.flushUnits())
}
})
</script>
@@ -1,38 +1,81 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style id="style-difficult-help-approval-list-h5">
<!--#include("../common/approval-list.css"){}#-->
</style>
<style id="style-difficult-help-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<van-sticky>
<div class="difficult-help-approval-page">
<van-nav-bar title="帮扶校工会审核" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<div class="difficult-help-approval-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="difficult-help-approval-search"
placeholder="请输入工号或者姓名进行查询"
show-action
:reverse-color="false"
input-align="left"
@search="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
shape="round"
clearable
@search="doSearch"
@clear="doSearch">
</van-search>
<van-dropdown-menu
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
class="difficult-help-approval-filter"
:class="{'is-open': filterOpened}"
active-color="#1989fa"
:close-on-click-outside="false"
:close-on-click-overlay="false"
>
<van-dropdown-item :options="unionList" @change="unionChange" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="unitList" @change="doSearch" v-model="pageForm.unitId"></van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unionList"
@change="unionChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unionId">
</van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unitList"
@change="doSearch"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unitId">
</van-dropdown-item>
<van-dropdown-item
:options="approvalOptions"
@change="onApprovalFilterChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.approvalText">
</van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.audit = val==='1';this.doSearch();}">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</div>
</van-sticky>
<table-list api="/platform/difficultHelp/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<main class="difficult-help-approval-content">
<div class="difficult-help-list-skeleton" v-if="skeletonLoading">
<div class="difficult-help-list-skeleton__card" v-for="index in 3" :key="'school-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/difficultHelp/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="difficult-help-approval-card-header">
<div class="difficult-help-approval-card-heading">
<div class="difficult-help-approval-card-title">{{row.userName || '--'}}</div>
<div class="difficult-help-approval-card-code">工号:{{row.loginName || '--'}}</div>
</div>
<enum-tag class="difficult-help-approval-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="受助人">{{row.userName + '(' + row.loginName + ')'}}</table-column>
<table-column label="申请人">{{row.proxyUserName || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="工作单位">{{row.unitName}}</table-column>
<table-column label="困难类型">{{row.subsidyStandards}}</table-column>
@@ -41,72 +84,101 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="difficult-help-approval-empty">
<img src="/assets/mobile/img/difficultHelp/difficult-help-empty.png" alt="暂无帮扶审核记录插画">
<div class="difficult-help-approval-empty__title">暂无帮扶审核记录</div>
<div class="difficult-help-approval-empty__hint">当前筛选条件下暂时没有帮扶申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef">
<info ref="infoRef" page-key="difficult-help-school-union-detail" full-screen>
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<van-field
v-model="formData.level"
name="level"
label="级别"
placeholder="请选择级别"
is-link
@click="showLevelPicker = true"
:rules="[{ required: true, message: '请选择级别' }]"
required
></van-field>
<van-popup v-model="showLevelPicker" position="bottom">
<van-picker
show-toolbar
:columns="levelColumns"
@confirm="onLevelConfirm"
@cancel="showLevelPicker=false">
</van-picker>
</van-popup>
<van-field
v-model="formData.money"
name="money"
label="审核金额"
placeholder="请输入审核金额"
type="number"
:rules="[{ required: true, message: '请输入审核金额' }]"
required
></van-field>
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
<section class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">审核信息</h2>
<div class="difficult-help-audit-form">
<van-field
v-model="formData.level"
class="difficult-help-audit-select"
name="level"
label="级别"
placeholder="请选择级别"
readonly
is-link
required
@click="openLevelPicker"
:rules="[{ required: true, message: '请选择级别' }]"
></van-field>
<van-field
v-model="formData.money"
class="difficult-help-audit-money"
name="money"
label="审核金额"
placeholder="请输入审核金额"
type="number"
required
:rules="[{ required: true, message: '请输入审核金额' }]"
></van-field>
</div>
</section>
<section class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">
审批意见<span class="difficult-help-audit-card__required">*</span>
</h2>
<van-field
v-model="formData.tf_opinion"
class="difficult-help-audit-textarea"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
</section>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<div class="difficult-help-popup-actions">
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">拒绝</van-button>
<van-button class="difficult-help-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-popup :value="showLevelPicker" position="bottom" :close-on-click-overlay="false">
<van-picker
show-toolbar
:columns="levelColumns"
@confirm="onLevelConfirm"
@cancel="closeLevelPicker">
</van-picker>
</van-popup>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm">
</van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("../common/info.js"){}#-->
@@ -117,8 +189,9 @@ layout("/layouts/platform_h5.html"){
data() {
return {
pageForm: {
pageNo: 1,
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
unionId: '',
unitId: '',
@@ -127,12 +200,23 @@ layout("/layouts/platform_h5.html"){
},
unionList: [],
unitList: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
filterOpened: false,
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
showLevelPicker: false,
levelColumns: []
levelColumns: [],
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
components: {
@@ -141,6 +225,24 @@ layout("/layouts/platform_h5.html"){
methods: {
historyBack,
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 audit 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "audit", val === "1")
this.doSearch()
},
// 同步筛选面板展开状态,用于调整下拉框底部圆角。
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
onReady() {
this.doSearch()
},
@@ -154,123 +256,179 @@ layout("/layouts/platform_h5.html"){
},
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "帮扶详情")
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.infoRef.onOpen(row)
this.formData = {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName,
money: 0,
id: row.id,
level: "",
tf_opinion: ""
}
})
this.$refs.infoRef.onOpen(row, row.curTaskName || "帮扶审核")
},
async initLevelDict() {
const levelData = await this.$businessTool.getDictOptions("DIFFICULT_LEVEL");
this.levelColumns = levelData.map(item => {
return { text: item.name, value: item.code };
});
// 加载困难级别字典并转换为 Vant Picker 所需的 text/value 结构。
initLevelDict() {
return this.$axios.post("/open/common/dictOptions", {code: "DIFFICULT_LEVEL"}, {h5SkeletonLoading: true}).then((res) => {
const levelData = res && res.code === 0 ? res.data : []
this.$set(this, "levelColumns", (levelData || []).map((item) => ({
text: item.name,
value: item.code
})))
})
},
// 级别选择器作为详情上层弹框,打开时同步增加浏览器历史。
openLevelPicker() {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-level-picker")) return
this.$set(this, "showLevelPicker", true)
},
closeLevelPicker(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-level-picker", afterClose)) return
this.$set(this, "showLevelPicker", false)
if (typeof afterClose === "function") afterClose()
},
onLevelConfirm(option) {
this.formData.level = option.value;
this.showLevelPicker = false;
this.closeLevelPicker(() => {
this.$set(this.formData, "level", option.value)
})
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate();
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
this.$axios.post("/platform/difficultHelp/schoolUnionApproval/save", {
// val 为流程提交类型:1 同意、2 拒绝、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 先保存级别和金额,再提交 processTaskId、审批意见及流程提交类型。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/platform/difficultHelp/schoolUnionApproval/save", {
data: JSON.stringify({...this.formData})
})
.then((saveRes) => {
if (!saveRes || saveRes.code !== 0) return null
return this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
}).then((saveRes) => {
if (saveRes.code === 0) {
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.infoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
}
})
}
})
}).catch(() => {
})
} catch (error) {
}
},
// 撤回
onRevoke(row) {
debugger
this.$dialog.confirm({
title: '提示',
message: '您确定要撤回吗?',
}).then(() => {
this.$axios.post('/flow/common/revokeTask', {taskId: row.taskId}).then(res => {
if (res.code === 0) {
this.$toast.success('撤回成功');
this.doSearch();
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
}).catch(() => {
});
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后由统一确认框执行撤回。
onRevoke(row) {
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || "撤回成功")
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
unionChange() {
this.$set(this.pageForm, "unitId", null)
this.flushUnits()
this.doSearch()
},
async initUnion() {
initUnion() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? null : this.$store.state.user.union.id
this.unionList = await this.$businessTool.listUnion(unionId)
this.unionList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unionOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unionList", list || [])
this.unionList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
if (hasAdmin) this.unionList.unshift({text: "全部工会", value: null})
if (this.unionList.length > 0) this.$set(this.pageForm, "unionId", this.unionList[0].value)
})
if (hasAdmin) {
this.unionList.unshift({ text: "全部工会", value: null })
}
if (this.unionList && this.unionList.length > 0) {
this.$set(this.pageForm, "unionId", this.unionList[0].value)
}
},
async flushUnits() {
flushUnits() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? this.pageForm.unionId : this.$store.state.user.union.id
this.unitList = await this.$businessTool.listUnit(unionId)
this.unitList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unitOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unitList", list || [])
this.unitList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
this.unitList.unshift({text: "全部单位", value: null})
if (this.unitList.length > 0) this.$set(this.pageForm, "unitId", this.unitList[0].value)
})
this.unitList.unshift({ text: "全部单位", value: null })
if (this.unitList && this.unitList.length > 0) {
this.$set(this.pageForm, "unitId", this.unitList[0].value)
}
},
},
async created() {
await this.initUnion()
await this.flushUnits()
await this.initLevelDict()
created() {
this.initUnion().then(() => this.flushUnits())
this.initLevelDict()
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("difficult-help-action-confirm"))
this.$set(this, "showLevelPicker", stack.includes("difficult-help-level-picker"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>
@@ -1,38 +1,81 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style id="style-difficult-help-approval-list-h5">
<!--#include("../common/approval-list.css"){}#-->
</style>
<style id="style-difficult-help-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<van-sticky>
<div class="difficult-help-approval-page">
<van-nav-bar title="帮扶二级党组织审核" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<div class="difficult-help-approval-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="difficult-help-approval-search"
placeholder="请输入工号或者姓名进行查询"
show-action
:reverse-color="false"
input-align="left"
@search="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
shape="round"
clearable
@search="doSearch"
@clear="doSearch">
</van-search>
<van-dropdown-menu
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
class="difficult-help-approval-filter"
:class="{'is-open': filterOpened}"
active-color="#1989fa"
:close-on-click-outside="false"
:close-on-click-overlay="false"
>
<van-dropdown-item :options="unionList" @change="unionChange" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="unitList" @change="doSearch" v-model="pageForm.unitId"></van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unionList"
@change="unionChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unionId">
</van-dropdown-item>
<van-dropdown-item
v-if="$auth.hasRoleOr('SYSADMIN,SCHOOL_UNION_ADMIN,BRANCH_UNION_CHAIRMAN')"
:options="unitList"
@change="doSearch"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.unitId">
</van-dropdown-item>
<van-dropdown-item
:options="approvalOptions"
@change="onApprovalFilterChange"
@open="onFilterOpen"
@close="onFilterClose"
v-model="pageForm.approvalText">
</van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.audit = val==='1';this.doSearch();}">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</div>
</van-sticky>
<table-list api="/platform/difficultHelp/unionLeaderApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<main class="difficult-help-approval-content">
<div class="difficult-help-list-skeleton" v-if="skeletonLoading">
<div class="difficult-help-list-skeleton__card" v-for="index in 3" :key="'leader-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/difficultHelp/unionLeaderApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="difficult-help-approval-card-header">
<div class="difficult-help-approval-card-heading">
<div class="difficult-help-approval-card-title">{{row.userName || '--'}}</div>
<div class="difficult-help-approval-card-code">工号:{{row.loginName || '--'}}</div>
</div>
<enum-tag class="difficult-help-approval-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="受助人">{{row.userName + '(' + row.loginName + ')'}}</table-column>
<table-column label="申请人">{{row.proxyUserName || '--'}}</table-column>
<table-column label="所属工会">{{row.unionName}}</table-column>
<table-column label="工作单位">{{row.unitName}}</table-column>
<table-column label="困难类型">{{row.subsidyStandards}}</table-column>
@@ -41,54 +84,101 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="difficult-help-approval-empty">
<img src="/assets/mobile/img/difficultHelp/difficult-help-empty.png" alt="暂无帮扶审核记录插画">
<div class="difficult-help-approval-empty__title">暂无帮扶审核记录</div>
<div class="difficult-help-approval-empty__hint">当前筛选条件下暂时没有帮扶申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef">
<info ref="infoRef" page-key="difficult-help-union-leader-detail" full-screen>
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<el-form-item label="级别" prop="level" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<search-item>
<dict-select v-model="formData.level" code="DIFFICULT_LEVEL" style="width: 100%" placeholder="请选择级别"></dict-select>
</search-item>
</el-form-item>
<el-form-item label="审核金额" prop="money" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<el-input-number placeholder="请输入金额" v-model="formData.money" style="width: 100%" :precision="2"
:min="0" ></el-input-number>
</el-form-item>
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
<section class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">审核信息</h2>
<div class="difficult-help-audit-form">
<van-field
v-model="formData.level"
class="difficult-help-audit-select"
name="level"
label="级别"
placeholder="请选择级别"
readonly
is-link
required
@click="openLevelPicker"
:rules="[{ required: true, message: '请选择级别' }]"
></van-field>
<van-field
v-model="formData.money"
class="difficult-help-audit-money"
name="money"
label="审核金额"
placeholder="请输入审核金额"
type="number"
required
:rules="[{ required: true, message: '请输入审核金额' }]"
></van-field>
</div>
</section>
<section class="difficult-help-info-card">
<h2 class="difficult-help-info-card__title">
审批意见<span class="difficult-help-audit-card__required">*</span>
</h2>
<van-field
v-model="formData.tf_opinion"
class="difficult-help-audit-textarea"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
</section>
</van-form>
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<div class="difficult-help-popup-actions">
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="difficult-help-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">拒绝</van-button>
<van-button class="difficult-help-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-popup :value="showLevelPicker" position="bottom" :close-on-click-overlay="false">
<van-picker
show-toolbar
:columns="levelColumns"
@confirm="onLevelConfirm"
@cancel="closeLevelPicker">
</van-picker>
</van-popup>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm">
</van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("../common/info.js"){}#-->
@@ -99,8 +189,9 @@ layout("/layouts/platform_h5.html"){
data() {
return {
pageForm: {
pageNo: 1,
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
unionId: '',
unitId: '',
@@ -109,9 +200,23 @@ layout("/layouts/platform_h5.html"){
},
unionList: [],
unitList: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
filterOpened: false,
skeletonLoading: true,
formData: {},
showApprovalForm: false
showApprovalForm: false,
formLoading: false,
showLevelPicker: false,
levelColumns: [],
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
components: {
@@ -120,6 +225,24 @@ layout("/layouts/platform_h5.html"){
methods: {
historyBack,
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 audit 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "audit", val === "1")
this.doSearch()
},
// 同步筛选面板展开状态,用于调整下拉框底部圆角。
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
onReady() {
this.doSearch()
},
@@ -134,99 +257,172 @@ layout("/layouts/platform_h5.html"){
// 查看详情
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "帮扶详情")
},
onApproval(row) {
this.showApprovalForm = true
this.$refs.infoRef.onOpen(row)
this.formData = {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName
}
taskName: row.curTaskName,
level: "",
money: 0,
tf_opinion: ""
})
this.$refs.infoRef.onOpen(row, 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.infoRef.onClose()
// 加载困难级别字典并转换为 Vant Picker 所需的 text/value 结构。
initLevelDict() {
return this.$axios.post("/open/common/dictOptions", {code: "DIFFICULT_LEVEL"}, {h5SkeletonLoading: true}).then((res) => {
const levelData = res && res.code === 0 ? res.data : []
this.$set(this, "levelColumns", (levelData || []).map((item) => ({
text: item.name,
value: item.code
})))
})
},
// 级别选择器作为详情上层弹框,打开时同步增加浏览器历史。
openLevelPicker() {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-level-picker")) return
this.$set(this, "showLevelPicker", true)
},
closeLevelPicker(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-level-picker", afterClose)) return
this.$set(this, "showLevelPicker", false)
if (typeof afterClose === "function") afterClose()
},
onLevelConfirm(option) {
this.closeLevelPicker(() => {
this.$set(this.formData, "level", option.value)
})
},
// val 为流程提交类型:1 同意、2 拒绝、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 提交级别、金额、审批意见和流程提交类型,接口返回统一响应对象。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
})
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(() => {
})
} catch (error) {
}
},
// 撤回
onRevoke(row) {
debugger
this.$dialog.confirm({
title: '提示',
message: '您确定要撤回吗?',
}).then(() => {
this.$axios.post('/flow/common/revokeTask', {taskId: row.taskId}).then(res => {
if (res.code === 0) {
this.$toast.success('撤回成功');
this.doSearch();
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
}).catch(() => {
});
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("difficult-help-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后由统一确认框执行撤回。
onRevoke(row) {
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("difficult-help-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || "撤回成功")
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
unionChange() {
this.$set(this.pageForm, "unitId", null)
this.flushUnits()
this.doSearch()
},
async initUnion() {
initUnion() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? null : this.$store.state.user.union.id
this.unionList = await this.$businessTool.listUnion(unionId)
this.unionList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unionOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unionList", list || [])
this.unionList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
if (hasAdmin) this.unionList.unshift({text: "全部工会", value: null})
if (this.unionList.length > 0) this.$set(this.pageForm, "unionId", this.unionList[0].value)
})
if (hasAdmin) {
this.unionList.unshift({ text: "全部工会", value: null })
}
if (this.unionList && this.unionList.length > 0) {
this.$set(this.pageForm, "unionId", this.unionList[0].value)
}
},
async flushUnits() {
flushUnits() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
const unionId = hasAdmin ? this.pageForm.unionId : this.$store.state.user.union.id
this.unitList = await this.$businessTool.listUnit(unionId)
this.unitList.forEach((v) => {
v.text = v.name
v.value = v.id
return this.$axios.post("/open/common/unitOptions", {unionId}, {h5SkeletonLoading: true}).then((res) => {
const list = res && res.code === 0 ? res.data : []
this.$set(this, "unitList", list || [])
this.unitList.forEach((v) => {
this.$set(v, "text", v.name)
this.$set(v, "value", v.id)
})
this.unitList.unshift({text: "全部单位", value: null})
if (this.unitList.length > 0) this.$set(this.pageForm, "unitId", this.unitList[0].value)
})
this.unitList.unshift({ text: "全部单位", value: null })
if (this.unitList && this.unitList.length > 0) {
this.$set(this.pageForm, "unitId", this.unitList[0].value)
}
},
},
async created() {
await this.initUnion()
await this.flushUnits()
created() {
this.initUnion().then(() => this.flushUnits())
this.initLevelDict()
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("difficult-help-action-confirm"))
this.$set(this, "showLevelPicker", stack.includes("difficult-help-level-picker"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>