..
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package com.budwk.app.base.model;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Excel导入错误信息
|
||||
*/
|
||||
public class ExcelImportError {
|
||||
|
||||
/**
|
||||
* 行号
|
||||
*/
|
||||
private int row;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@Getter
|
||||
private String errMsg;
|
||||
|
||||
public void setErrInfo(String errMsg, int row) {
|
||||
this.errMsg = errMsg;
|
||||
this.row = row;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.budwk.app.base.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel导入结果
|
||||
* @param <T>
|
||||
*/
|
||||
@Data
|
||||
public class ExcelImportRes<T extends ExcelImportError> {
|
||||
|
||||
private int totalRecords = 0;
|
||||
|
||||
private int successCount = 0;
|
||||
|
||||
private int failedCount = 0;
|
||||
|
||||
private List<T> errorDetails = new ArrayList<>();
|
||||
|
||||
// 失败数+1
|
||||
public void addFailedCount() {
|
||||
failedCount++;
|
||||
System.out.println(failedCount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -176,9 +176,9 @@ public class SysHomeController {
|
||||
Date startDate = activity.getStartDate();
|
||||
Date endDate = activity.getEndDate();
|
||||
// 判断当前时间是否在活动范围内,不在就过滤掉
|
||||
if (!DateUtil.isIn(today, startDate, endDate)) {
|
||||
continue;
|
||||
}
|
||||
// if (!DateUtil.isIn(today, startDate, endDate)) {
|
||||
// continue;
|
||||
// }
|
||||
Integer allowUserGroupId = activity.getAllowUserGroupId();
|
||||
String allowUserSql = activity.getAllowUserSql();
|
||||
activity.setAllowUserSql(null);
|
||||
|
||||
+4
-2
@@ -11,10 +11,12 @@ import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
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;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
@@ -32,11 +34,9 @@ import org.nutz.mvc.annotation.Param;
|
||||
@Ok("json:full")
|
||||
public class ActivityBasicScopeUserDataController {
|
||||
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:platform/zhgh/activity/basic/userScopeData/index.html")
|
||||
@SaCheckPermission("activity.basic.user")
|
||||
@@ -126,6 +126,7 @@ public class ActivityBasicScopeUserDataController {
|
||||
@At
|
||||
@SLog(tag = "活动人员查询", msg = "删除活动人员")
|
||||
@SaCheckPermission("activity.basic.user")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result doDelete(@Param(value = "id") String id,
|
||||
@Param(value = "groupId") Integer groupId,
|
||||
@Param(value = "searchKeyword") String searchKeyword,
|
||||
@@ -158,6 +159,7 @@ public class ActivityBasicScopeUserDataController {
|
||||
cnd.and(new Static("userId in (" + sql + ")"));
|
||||
}
|
||||
baseService.dao().clear(ActivityUserScope.class, cnd);
|
||||
baseService.dao().clear(ActivityUserScope.class, Cnd.where(ActivityUserScope::getUserId, "not in", Sqls.create("select id from sys_user")));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package com.budwk.app.zhgh.welfare.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.model.ExcelImportRes;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.EasyExcelUtil;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.welfare.mode.WelfareUserImportExcel;
|
||||
import com.budwk.app.zhgh.welfare.mode.WelfareUserTemp;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareList;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareUserSelection;
|
||||
@@ -23,16 +25,15 @@ import com.budwk.app.zhgh.welfare.service.WelfareListService;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareProjectService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.AdaptBy;
|
||||
@@ -44,22 +45,16 @@ import org.nutz.mvc.upload.UploadAdaptor;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @ClassName WelfareListController
|
||||
* @Description TODO
|
||||
* @Author zhf
|
||||
* @Date 2024/8/13 18:28
|
||||
*/
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/welfare/list/mange")
|
||||
@Api(tags = "福利名单")
|
||||
@Slf4j
|
||||
public class WelfareListController {
|
||||
|
||||
@Inject
|
||||
@@ -120,7 +115,6 @@ public class WelfareListController {
|
||||
welfareListService.exportXlsx(pageForm, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("welfare.list.mange")
|
||||
@ApiOperation("编辑备注")
|
||||
@@ -132,18 +126,16 @@ public class WelfareListController {
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("welfare.list.mange")
|
||||
public void downloadImport(HttpServletResponse response) {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();) {
|
||||
EasyExcel.write(byteArrayOutputStream, WelfareUserTemp.class)
|
||||
.sheet("福利名单导入模版")
|
||||
.doWrite(ArrayList::new);
|
||||
CommonDownloadUtil.download("福利名单导入模版.xlsx", byteArrayOutputStream.toByteArray(), response);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ApiOperation("下载模版")
|
||||
public void downloadTemplate(HttpServletResponse response) {
|
||||
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||
entities.add(new ExcelExportEntity("工号", "loginname", 20));
|
||||
entities.add(new ExcelExportEntity("姓名", "username", 20));
|
||||
ExportParams exportParams = new ExportParams();
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, Collections.emptyList());
|
||||
CommonDownloadUtil.download("福利名单模版.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("welfare.list.mange")
|
||||
@@ -193,4 +185,75 @@ public class WelfareListController {
|
||||
}
|
||||
return Result.success("导入成功");
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("welfare.list.mange")
|
||||
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
@SLog(tag = "福利名单管理", msg = "导入福利名单")
|
||||
public Result importByExcel(@Param("file") TempFile tempFile, @Param("projectId") String projectId) {
|
||||
// 读取数据
|
||||
List<WelfareUserImportExcel> excelList = ExcelImportUtil.importExcel(tempFile.getFile(), WelfareUserImportExcel.class, new ImportParams());
|
||||
|
||||
// 查询用户
|
||||
List<String> loginNames = excelList.stream().map(WelfareUserImportExcel::getLoginName).filter(StrUtil::isNotBlank).collect(Collectors.toList());
|
||||
List<View_user> sysUsers = welfareListService.dao().query(View_user.class, Cnd.where(View_user::getLoginname, "in", loginNames));
|
||||
|
||||
// 查询当前的福利会员
|
||||
List<WelfareList> welfareLists = welfareListService.query(Cnd.where("projectId", "=", projectId));
|
||||
|
||||
// 创建结果集
|
||||
ExcelImportRes<WelfareUserImportExcel> excelImportRes = new ExcelImportRes<>();
|
||||
excelImportRes.setTotalRecords(excelList.size());
|
||||
|
||||
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
WelfareUserImportExcel excel = excelList.get(i);
|
||||
if (StrUtil.isBlank(excel.getLoginName())) {
|
||||
excel.setErrInfo("工号为空", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 不能在excel里重复
|
||||
if (excelList.stream().filter(s -> s.getLoginName().equals(excel.getLoginName())).count() > 1) {
|
||||
excel.setErrInfo("重复数据", i + 1);
|
||||
}
|
||||
|
||||
View_user user = sysUsers.stream().filter(s -> s.getLoginname().equals(excel.getLoginName())).findFirst().orElse(null);
|
||||
if (user == null) {
|
||||
excel.setErrInfo("无此用户", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (welfareLists.stream().anyMatch(s -> s.getUserId().equals(user.getId()))) {
|
||||
excel.setErrInfo("该用户已加入福利名单", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
WelfareList wl = new WelfareList();
|
||||
wl.setProjectId(projectId);
|
||||
wl.setUserId(user.getId());
|
||||
wl.setWelfareUnitId(user.getUnitId());
|
||||
wl.setWelfareUnitName(user.getUnitName());
|
||||
wl.setWelfareUnionId(user.getUnionId());
|
||||
wl.setWelfareUnionName(user.getUnionName());
|
||||
wl.setPersonType(user.getPersonType());
|
||||
wl.setPreparedBy(user.getPreparedBy());
|
||||
wl.setUserState(user.getUserState());
|
||||
try {
|
||||
welfareListService.insert(wl);
|
||||
} catch (Exception e) {
|
||||
log.error("添加福利名单失败:{}", e.getMessage());
|
||||
excel.setErrInfo("添加失败", i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加错误记录
|
||||
excelImportRes.setErrorDetails(excelList.stream().filter(s -> StrUtil.isNotBlank(s.getErrMsg())).collect(Collectors.toList()));
|
||||
excelImportRes.setFailedCount(excelImportRes.getErrorDetails().size());
|
||||
|
||||
return Result.success(excelImportRes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.budwk.app.zhgh.welfare.mode;
|
||||
|
||||
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||
import com.budwk.app.base.model.ExcelImportError;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class WelfareUserImportExcel extends ExcelImportError {
|
||||
|
||||
@Excel(name = "工号")
|
||||
private String loginName;
|
||||
|
||||
@Excel(name = "姓名")
|
||||
private String userName;
|
||||
|
||||
}
|
||||
@@ -127,10 +127,10 @@
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool :label="currentGroupName">
|
||||
<el-button @click="doExportUser" icon="el-icon-printer" size="medium" type="primary" :disabled="!pageForm.groupId">
|
||||
<el-button @click="doExportUser" icon="el-icon-printer" size="small" type="primary" :disabled="!pageForm.groupId">
|
||||
导出活动分组人员xlsx
|
||||
</el-button>
|
||||
<el-button @click="doDelete(null)" type="danger" size="medium" :disabled="tableData.length === 0">
|
||||
<el-button @click="doDelete(null)" type="danger" size="small" :disabled="tableData.length === 0">
|
||||
删除{{ currentGroupName }}
|
||||
</el-button>
|
||||
</table-tool>
|
||||
@@ -238,7 +238,7 @@ module.exports = {
|
||||
components: {},
|
||||
methods: {
|
||||
viewGroupName() {
|
||||
if (this.pageForm.groupId) {
|
||||
if (this.pageForm?.groupId) {
|
||||
const group = this.activityGroupList.find((v) => v.groupId === this.pageForm.groupId)
|
||||
this.currentGroupName = group.groupName + "人员"
|
||||
return
|
||||
@@ -282,14 +282,16 @@ module.exports = {
|
||||
async getActivityGroup(id) {
|
||||
const resp = await $.get("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
||||
this.activityGroupList = resp.data
|
||||
if (id) {
|
||||
if (this.activityGroupList.some((v) => v.groupId === id)) {
|
||||
this.$set(this.pageForm, "groupId", id)
|
||||
if (resp.data && resp.data.length > 0) {
|
||||
if (id) {
|
||||
if (this.activityGroupList.some((v) => v.groupId === id)) {
|
||||
this.$set(this.pageForm, "groupId", id)
|
||||
} else {
|
||||
this.$set(this.pageForm, "groupId", resp.data[0].groupId)
|
||||
}
|
||||
} else {
|
||||
this.$set(this.pageForm, "groupId", resp.data[0].groupId)
|
||||
}
|
||||
} else {
|
||||
this.$set(this.pageForm, "groupId", resp.data[0].groupId)
|
||||
}
|
||||
await this.doSearch()
|
||||
},
|
||||
|
||||
@@ -0,0 +1,994 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "excelImport",
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
description: "后端接收文件的URL"
|
||||
},
|
||||
extra_params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
description: "上传时附加的参数"
|
||||
},
|
||||
template_url: {
|
||||
type: String,
|
||||
default: "",
|
||||
description: "下载导入模板的URL地址"
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "dialog",
|
||||
validator: (value) => ["dialog", "direct"].includes(value),
|
||||
description: "显示模式:dialog-弹窗模式(默认),direct-直接显示"
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "Excel 数据导入",
|
||||
description: "弹窗标题(dialog模式下有效)"
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "800px",
|
||||
description: "弹窗宽度(dialog模式下有效)"
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: "是否显示弹窗(dialog模式下有效)"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedFile: null,
|
||||
importing: false,
|
||||
importComplete: false,
|
||||
progress: 0,
|
||||
progressText: "",
|
||||
stats: {
|
||||
totalRecords: 0,
|
||||
successCount: 0,
|
||||
failedCount: 0
|
||||
},
|
||||
errorDetails: [],
|
||||
excelData: [],
|
||||
dialogVisible: this.visible,
|
||||
downloading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressColor() {
|
||||
if (this.progress < 30) return "#F56C6C"
|
||||
if (this.progress < 70) return "#E6A23C"
|
||||
return "#67C23A"
|
||||
},
|
||||
successRate() {
|
||||
if (this.stats.totalRecords === 0) return 0
|
||||
return Math.round((this.stats.successCount / this.stats.totalRecords) * 100)
|
||||
},
|
||||
showTemplateBtn() {
|
||||
return !!this.template_url
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
this.dialogVisible = val
|
||||
},
|
||||
dialogVisible(val) {
|
||||
if (val !== this.visible) {
|
||||
this.$emit("update:visible", val)
|
||||
}
|
||||
if (!val) {
|
||||
this.resetUpload()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFileChange(file) {
|
||||
this.selectedFile = file.raw
|
||||
this.resetStats()
|
||||
},
|
||||
|
||||
beforeUpload(file) {
|
||||
const isExcel =
|
||||
file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
|
||||
file.type === "application/vnd.ms-excel" ||
|
||||
file.name.toLowerCase().endsWith(".xlsx") ||
|
||||
file.name.toLowerCase().endsWith(".xls")
|
||||
|
||||
if (!isExcel) {
|
||||
this.$message.error("只能上传 Excel 文件!")
|
||||
return false
|
||||
}
|
||||
|
||||
const isLt10M = file.size / 1024 / 1024 < 10
|
||||
if (!isLt10M) {
|
||||
this.$message.error("文件大小不能超过 10MB!")
|
||||
return false
|
||||
}
|
||||
|
||||
return false // 阻止自动上传
|
||||
},
|
||||
|
||||
async startImport() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning("请先选择文件!")
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.url) {
|
||||
this.$message.error("未配置上传URL!")
|
||||
return
|
||||
}
|
||||
|
||||
this.importing = true
|
||||
this.progress = 0
|
||||
this.progressText = "准备上传文件..."
|
||||
|
||||
try {
|
||||
// 创建FormData对象
|
||||
const formData = new FormData()
|
||||
formData.append("file", this.selectedFile)
|
||||
|
||||
// 添加额外参数
|
||||
if (this.extra_params) {
|
||||
Object.keys(this.extra_params).forEach((key) => {
|
||||
formData.append(key, this.extra_params[key])
|
||||
})
|
||||
}
|
||||
|
||||
// 设置上传进度
|
||||
await this.updateProgress(20, "正在上传文件...")
|
||||
|
||||
// 发送请求到后端
|
||||
const { code, data } = await this.$axios.post(this.url, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const percentCompleted = Math.round((progressEvent.loaded * 40) / progressEvent.total)
|
||||
this.updateProgress(20 + percentCompleted, "上传中...")
|
||||
}
|
||||
})
|
||||
|
||||
// 处理后端返回的结果
|
||||
await this.updateProgress(70, "处理后端导入结果...")
|
||||
|
||||
if (code === 0 && data) {
|
||||
// 处理成功
|
||||
const result = data || {}
|
||||
|
||||
this.stats.totalRecords = result.totalRecords || 0
|
||||
this.stats.successCount = result.successCount || 0
|
||||
this.stats.failedCount = result.failedCount || 0
|
||||
this.errorDetails = result.errorDetails || []
|
||||
|
||||
await this.updateProgress(100, "导入完成!")
|
||||
|
||||
setTimeout(() => {
|
||||
this.importing = false
|
||||
this.importComplete = true
|
||||
this.$message.success("Excel 导入完成!")
|
||||
this.$emit("import-success", result)
|
||||
}, 500)
|
||||
} else {
|
||||
// 处理失败
|
||||
throw new Error(msg || "导入失败")
|
||||
}
|
||||
} catch (error) {
|
||||
this.importing = false
|
||||
this.$message.error("导入失败:" + (error.message || "未知错误"))
|
||||
this.$emit("import-error", error)
|
||||
}
|
||||
},
|
||||
|
||||
async downloadTemplate() {
|
||||
if (!this.template_url) {
|
||||
this.$message.warning("未配置模板下载地址!")
|
||||
return
|
||||
}
|
||||
|
||||
this.downloading = true
|
||||
|
||||
try {
|
||||
// 下载模板文件
|
||||
const response = await this.$axios({
|
||||
url: this.template_url,
|
||||
method: "GET",
|
||||
responseType: "blob"
|
||||
})
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([response.data])
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
|
||||
// 获取文件名,从Content-Disposition中提取或使用默认名称
|
||||
let filename = "导入模板.xlsx"
|
||||
const disposition = response.headers["content-disposition"]
|
||||
if (disposition && disposition.indexOf("attachment") !== -1) {
|
||||
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
|
||||
const matches = filenameRegex.exec(disposition)
|
||||
if (matches != null && matches[1]) {
|
||||
filename = matches[1].replace(/['"]/g, "")
|
||||
// 解决中文乱码问题
|
||||
try {
|
||||
filename = decodeURIComponent(filename)
|
||||
} catch (e) {
|
||||
// 解码失败时使用原始文件名
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 触发下载
|
||||
const link = document.createElement("a")
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.style.display = "none"
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
this.$message.success("模板下载成功!")
|
||||
} catch (error) {
|
||||
this.$message.error("模板下载失败:" + (error.message || "未知错误"))
|
||||
this.$emit("template-error", error)
|
||||
} finally {
|
||||
this.downloading = false
|
||||
}
|
||||
},
|
||||
|
||||
readExcelFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = new Uint8Array(e.target.result)
|
||||
const workbook = XLSX.read(data, { type: "array" })
|
||||
const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
|
||||
const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 })
|
||||
resolve(jsonData)
|
||||
} catch (error) {
|
||||
reject(new Error("文件解析失败"))
|
||||
}
|
||||
}
|
||||
reader.onerror = () => reject(new Error("文件读取失败"))
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
},
|
||||
|
||||
async processData(data) {
|
||||
this.excelData = data
|
||||
this.stats.totalRecords = data.length - 1 // 减去表头
|
||||
this.errorDetails = []
|
||||
|
||||
let successCount = 0
|
||||
let failedCount = 0
|
||||
|
||||
// 模拟数据处理过程
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
await this.updateProgress(40 + Math.round((i / (data.length - 1)) * 50), `处理第 ${i} 条记录...`)
|
||||
|
||||
// 模拟数据验证和处理
|
||||
const row = data[i]
|
||||
const isValid = this.validateRow(row, i + 1)
|
||||
|
||||
if (isValid) {
|
||||
successCount++
|
||||
} else {
|
||||
failedCount++
|
||||
}
|
||||
|
||||
// 添加延迟以显示进度效果
|
||||
if (i % 10 === 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
}
|
||||
}
|
||||
|
||||
this.stats.successCount = successCount
|
||||
this.stats.failedCount = failedCount
|
||||
},
|
||||
|
||||
validateRow(row, rowIndex) {
|
||||
// 模拟数据验证逻辑
|
||||
if (!row || row.length === 0) {
|
||||
this.errorDetails.push({
|
||||
row: rowIndex,
|
||||
message: "空行数据"
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 随机生成一些错误(用于演示)
|
||||
if (Math.random() < 0.1) {
|
||||
// 10% 的错误率
|
||||
const errors = ["必填字段为空", "数据格式不正确", "数据长度超出限制", "重复数据", "外键约束违反"]
|
||||
this.errorDetails.push({
|
||||
row: rowIndex,
|
||||
message: errors[Math.floor(Math.random() * errors.length)]
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
updateProgress(percentage, text) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
this.progress = percentage
|
||||
this.progressText = text
|
||||
resolve()
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
|
||||
getSuccessRateColor(rate) {
|
||||
if (rate >= 90) return "#67C23A"
|
||||
if (rate >= 70) return "#E6A23C"
|
||||
return "#F56C6C"
|
||||
},
|
||||
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0) return "0 Bytes"
|
||||
const k = 1024
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"]
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
||||
},
|
||||
|
||||
downloadReport() {
|
||||
if (this.errorDetails.length === 0) return
|
||||
|
||||
let content = "错误报告\n\n"
|
||||
content += `总记录数: ${this.stats.totalRecords}\n`
|
||||
content += `成功数: ${this.stats.successCount}\n`
|
||||
content += `失败数: ${this.stats.failedCount}\n`
|
||||
content += `成功率: ${this.successRate}%\n\n`
|
||||
content += "错误详情:\n"
|
||||
|
||||
this.errorDetails.forEach((error) => {
|
||||
content += `第${error.row}行: ${error.message}\n`
|
||||
})
|
||||
|
||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = "导入错误报告.txt"
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
this.$message.success("错误报告下载完成!")
|
||||
},
|
||||
|
||||
resetUpload() {
|
||||
this.selectedFile = null
|
||||
this.importing = false
|
||||
this.importComplete = false
|
||||
this.progress = 0
|
||||
this.progressText = ""
|
||||
this.resetStats()
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
this.$emit("reset")
|
||||
},
|
||||
|
||||
resetStats() {
|
||||
this.stats = {
|
||||
totalRecords: 0,
|
||||
successCount: 0,
|
||||
failedCount: 0
|
||||
}
|
||||
this.errorDetails = []
|
||||
this.excelData = []
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
if (this.mode === "dialog") {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
},
|
||||
|
||||
showDialog() {
|
||||
if (this.mode === "dialog") {
|
||||
this.dialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Dialog模式 -->
|
||||
<el-dialog
|
||||
v-if="mode === 'dialog'"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="title"
|
||||
:width="width"
|
||||
@close="resetUpload"
|
||||
:append-to-body="true"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<div class="excel-import-container dialog-mode">
|
||||
<div class="content">
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="upload-area" v-if="!importing && !importComplete">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:before-upload="beforeUpload"
|
||||
accept=".xlsx,.xls"
|
||||
drag
|
||||
:show-file-list="false"
|
||||
>
|
||||
<div class="upload-icon">📁</div>
|
||||
<div class="upload-text">将 Excel 文件拖拽到此处,或点击选择文件</div>
|
||||
<div class="upload-hint">支持 .xlsx、.xls 格式,文件大小不超过 10MB</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<!-- 下载模板按钮 -->
|
||||
<div v-if="showTemplateBtn && !importing && !importComplete" class="template-download">
|
||||
<el-button type="text" @click="downloadTemplate" :loading="downloading" icon="el-icon-download">下载导入模板</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 已选择文件信息 -->
|
||||
<div v-if="selectedFile && !importing && !importComplete" class="file-info">
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件名:</strong></span>
|
||||
<span>{{ selectedFile.name }}</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件大小:</strong></span>
|
||||
<span>{{ formatFileSize(selectedFile.size) }}</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件类型:</strong></span>
|
||||
<span>{{ selectedFile.type || "未知" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导入按钮 -->
|
||||
<div v-if="selectedFile && !importing && !importComplete" class="actions">
|
||||
<el-button type="primary" size="large" @click="startImport" :loading="importing">
|
||||
<i class="el-icon-upload2"></i>
|
||||
开始导入
|
||||
</el-button>
|
||||
<el-button size="large" @click="resetUpload">
|
||||
<i class="el-icon-refresh"></i>
|
||||
重新选择
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 导入进度 -->
|
||||
<div v-if="importing" class="progress-section">
|
||||
<h3 style="text-align: center; color: #409eff; margin-bottom: 20px">
|
||||
<i class="el-icon-loading"></i>
|
||||
正在处理文件...
|
||||
</h3>
|
||||
<el-progress :percentage="progress" :color="progressColor" :stroke-width="8" text-inside></el-progress>
|
||||
<p style="text-align: center; margin-top: 15px; color: #606266">
|
||||
{{ progressText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 导入结果 -->
|
||||
<div v-if="importComplete" class="result-section">
|
||||
<div class="result-header">
|
||||
<div class="result-icon">
|
||||
<i class="el-icon-circle-check"></i>
|
||||
</div>
|
||||
<h3>导入完成</h3>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon total-icon">
|
||||
<i class="el-icon-document"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number total">{{ stats.totalRecords }}</div>
|
||||
<div class="stat-label">总记录数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon success-icon">
|
||||
<i class="el-icon-check"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number success">{{ stats.successCount }}</div>
|
||||
<div class="stat-label">导入成功</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon failed-icon">
|
||||
<i class="el-icon-close"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number failed">{{ stats.failedCount }}</div>
|
||||
<div class="stat-label">导入失败</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功率展示 -->
|
||||
<div class="success-rate-section">
|
||||
<div class="success-rate-header">
|
||||
<span class="success-rate-label">导入成功率</span>
|
||||
<span class="success-rate-value" :style="{ color: getSuccessRateColor(successRate) }">{{ successRate }}%</span>
|
||||
</div>
|
||||
<el-progress :percentage="successRate" :color="getSuccessRateColor(successRate)" :stroke-width="6"></el-progress>
|
||||
</div>
|
||||
|
||||
<!-- 错误详情 -->
|
||||
<div v-if="errorDetails.length > 0" class="error-details-section">
|
||||
<el-collapse>
|
||||
<el-collapse-item name="errors">
|
||||
<template slot="title">
|
||||
<div class="error-title">
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
<span>查看错误详情 ({{ errorDetails.length }}条)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="error-list">
|
||||
<div v-for="(error, index) in errorDetails" :key="index" class="error-item">
|
||||
<div class="error-row">第 {{ error.row }} 行</div>
|
||||
<div class="error-message">{{ error.errMsg }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="downloadReport" v-if="stats.failedCount > 0">
|
||||
<i class="el-icon-download"></i>
|
||||
下载错误报告
|
||||
</el-button>
|
||||
<el-button @click="resetUpload">
|
||||
<i class="el-icon-refresh"></i>
|
||||
重新导入
|
||||
</el-button>
|
||||
<el-button @click="closeDialog">
|
||||
<i class="el-icon-close"></i>
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 直接显示模式 -->
|
||||
<div v-else class="excel-import-container">
|
||||
<div class="header">
|
||||
<h1>📊 Excel 文件导入</h1>
|
||||
<p>支持 .xlsx、.xls 格式文件,自动解析并统计导入结果</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="upload-area" v-if="!importing && !importComplete">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:before-upload="beforeUpload"
|
||||
accept=".xlsx,.xls"
|
||||
drag
|
||||
:show-file-list="false"
|
||||
>
|
||||
<div class="upload-icon">📁</div>
|
||||
<div class="upload-text">将 Excel 文件拖拽到此处,或点击选择文件</div>
|
||||
<div class="upload-hint">支持 .xlsx、.xls 格式,文件大小不超过 10MB</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<!-- 下载模板按钮 -->
|
||||
<div v-if="showTemplateBtn && !importing && !importComplete" class="template-download">
|
||||
<el-button type="text" @click="downloadTemplate" :loading="downloading" icon="el-icon-download">下载导入模板</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 已选择文件信息 -->
|
||||
<div v-if="selectedFile && !importing && !importComplete" class="file-info">
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件名:</strong></span>
|
||||
<span>{{ selectedFile.name }}</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件大小:</strong></span>
|
||||
<span>{{ formatFileSize(selectedFile.size) }}</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<span><strong>文件类型:</strong></span>
|
||||
<span>{{ selectedFile.type || "未知" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导入按钮 -->
|
||||
<div v-if="selectedFile && !importing && !importComplete" class="actions">
|
||||
<el-button type="primary" size="large" @click="startImport" :loading="importing">
|
||||
<i class="el-icon-upload2"></i>
|
||||
开始导入
|
||||
</el-button>
|
||||
<el-button size="large" @click="resetUpload">
|
||||
<i class="el-icon-refresh"></i>
|
||||
重新选择
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 导入进度 -->
|
||||
<div v-if="importing" class="progress-section">
|
||||
<h3 style="text-align: center; color: #409eff; margin-bottom: 20px">
|
||||
<i class="el-icon-loading"></i>
|
||||
正在处理文件...
|
||||
</h3>
|
||||
<el-progress :percentage="progress" :color="progressColor" :stroke-width="8" text-inside></el-progress>
|
||||
<p style="text-align: center; margin-top: 15px; color: #606266">
|
||||
{{ progressText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 导入结果 -->
|
||||
<div v-if="importComplete" class="result-section">
|
||||
<div class="result-header">
|
||||
<div class="result-icon">
|
||||
<i class="el-icon-circle-check"></i>
|
||||
</div>
|
||||
<h3>导入完成</h3>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon total-icon">
|
||||
<i class="el-icon-document"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number total">{{ stats.totalRecords }}</div>
|
||||
<div class="stat-label">总记录数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon success-icon">
|
||||
<i class="el-icon-check"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number success">{{ stats.successCount }}</div>
|
||||
<div class="stat-label">导入成功</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon failed-icon">
|
||||
<i class="el-icon-close"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number failed">{{ stats.failedCount }}</div>
|
||||
<div class="stat-label">导入失败</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误详情 -->
|
||||
<div v-if="errorDetails.length > 0" class="error-details-section">
|
||||
<el-collapse>
|
||||
<el-collapse-item name="errors">
|
||||
<template slot="title">
|
||||
<div class="error-title">
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
<span>查看错误详情 ({{ errorDetails.length }}条)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="error-list">
|
||||
<div v-for="(error, index) in errorDetails" :key="index" class="error-item">
|
||||
<div class="error-row">第 {{ error.row }} 行</div>
|
||||
<div class="error-message">{{ error.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="downloadReport" v-if="stats.failedCount > 0">
|
||||
<i class="el-icon-download"></i>
|
||||
下载错误报告
|
||||
</el-button>
|
||||
<el-button @click="resetUpload">
|
||||
<i class="el-icon-refresh"></i>
|
||||
重新导入
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.excel-import-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.excel-import-container.dialog-mode {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 10px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.template-download {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 48px;
|
||||
color: #c0c4cc;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #f6f8fa 0%, #ffffff 100%);
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
transition: transform 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.total-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.failed-icon {
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.total {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #606266;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
margin-top: 30px;
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.result-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 48px;
|
||||
color: #67c23a;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.result-header h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.success-rate-section {
|
||||
margin-top: 30px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.success-rate-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.success-rate-label {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.success-rate-value {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.error-details-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-title i {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.error-list {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
background: #fff5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.error-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #fde2e2;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.error-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.error-row {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
background: rgba(245, 108, 108, 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
background: #e8f4fd;
|
||||
border: 1px solid #b3d8ff;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.file-info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
@@ -430,6 +430,7 @@
|
||||
Vue.component("custom-form-field", httpVueLoader("/components/plugins/customFormField/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("dynamic-Table-form-eval", httpVueLoader("/components/plugins/sysDynamicTableFormEval/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("condition-group", httpVueLoader("/components/plugins/conditionGroup/index.vue?v=" + new Date().getTime()))
|
||||
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -3,28 +3,47 @@ const activity = {
|
||||
<div class="home-activity">
|
||||
<el-card shadow="never">
|
||||
<div class="card-title" slot="header">最新活动</div>
|
||||
<div v-if="activityOptions && activityOptions.length > 0" v-for="item in activityOptions" :key="item.id">
|
||||
<div style="width: 100%">
|
||||
<div style="background-color: #fff; position: relative; margin-bottom: 10px">
|
||||
<div style="display: flex; justify-content: center; align-items: center">
|
||||
<el-image :src="item.cover" alt="" fit="contain" style="margin: 3px 14px 0 0; width: 100px; height: 70px; border-radius: 8px">
|
||||
<div v-if="activityOptions && activityOptions.length > 0" v-for="item in activityOptions"
|
||||
:key="item.id">
|
||||
<div style="width: 100%">
|
||||
<div style="background-color: #fff; position: relative; margin-bottom: 10px">
|
||||
<div style="display: flex; justify-content: center; align-items: center;border-bottom: 1px solid var(--border-color-lighter);padding-bottom: 8px">
|
||||
<div
|
||||
style="position: relative;flex-shrink: 1;width: 150px; height: 90px;margin-right: 10px; ">
|
||||
<el-image :src="item.cover" alt="" fit="cover"
|
||||
style="width: 100%; height: 100%; border-radius: 8px;">
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</el-image>
|
||||
<div style="flex: 1">
|
||||
<div style="line-height: 22px; color: #111">
|
||||
<i class="el-icon-star-on" style="color: red; font-size: 17px"></i>{{item.name}}
|
||||
</div>
|
||||
<div style="font-size: 12px; line-height: 20px; color: #999">开始时间:{{$moment(item.startDate).format('MM-DD HH:mm')}}</div>
|
||||
<div style="font-size: 12px; line-height: 20px; color: #999">结束时间:{{$moment(item.endDate).format('MM-DD HH:mm')}}</div>
|
||||
<el-tag class="act-item-tag" type="danger" size="mini"
|
||||
v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
已结束
|
||||
</el-tag>
|
||||
|
||||
<el-tag class="act-item-tag" type="success" size="mini"
|
||||
v-else-if="$moment().unix() < $moment(item.endDate).unix()">
|
||||
进行中
|
||||
</el-tag>
|
||||
</div>
|
||||
<div style="flex: 1;height: 90px;display: flex;flex-direction: column;justify-content: space-between;">
|
||||
<div style="color: #111">
|
||||
<i class="el-icon-star-on" style="color: red; font-size: 17px"></i>{{item.name}}
|
||||
</div>
|
||||
<div @click="enterActivity(item)">
|
||||
<el-tag style="cursor: pointer">查看<i class="el-icon-s-promotion"></i></el-tag>
|
||||
<div style="font-size: 12px; color: #999">
|
||||
开始时间:{{$moment(item.startDate).format('MM-DD HH:mm')}}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #999">
|
||||
结束时间:{{$moment(item.endDate).format('MM-DD HH:mm')}}
|
||||
</div>
|
||||
</div>
|
||||
<div @click="enterActivity(item)">
|
||||
<el-button size="mini" type="primary">查看<i class="el-icon-s-promotion"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="activityOptions.length === 0" description="暂无活动"></el-empty>
|
||||
</el-card>
|
||||
@@ -63,5 +82,12 @@ const activity = {
|
||||
.home-activity .el-empty {
|
||||
padding: 40px 0 !important;
|
||||
}
|
||||
|
||||
.home-activity .act-item-tag{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ const SYS_MENU_QUICK_ENTRY_COMPONENT = {
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="图标" prop="icon">
|
||||
<template scope="{row}">
|
||||
<svg-icon :name="row.icon"></svg-icon>
|
||||
<svg-icon :name="row.icon" v-if="platform==='PC'"></svg-icon>
|
||||
<img v-else :src="row.icon" alt="" style="width: 30px;height: 30px">
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@@ -31,10 +31,10 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
style="width: 100%;"
|
||||
v-model="formData.projectTypeCode">
|
||||
<el-option
|
||||
:key="item.id"
|
||||
:label="item.name+' ('+item.code+')'"
|
||||
:value="item.code"
|
||||
v-for="item in projectTypeList">
|
||||
:key="item.id"
|
||||
:label="item.name+' ('+item.code+')'"
|
||||
:value="item.code"
|
||||
v-for="item in projectTypeList">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -68,10 +68,10 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
reserve-keyword
|
||||
style="width: 100%" v-model="formData.userId">
|
||||
<el-option
|
||||
:key="item.userId"
|
||||
:label="item.userName+'-'+item.loginName+'-'+item.unitName"
|
||||
:value="item.userId"
|
||||
v-for="item in userList">
|
||||
:key="item.userId"
|
||||
:label="item.userName+'-'+item.loginName+'-'+item.unitName"
|
||||
:value="item.userId"
|
||||
v-for="item in userList">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -84,48 +84,48 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12"-->
|
||||
<!-- v-if="formData.isEnrollSystem === true">-->
|
||||
<!-- <el-form-item label="活动计划时间" prop="plannedDate">-->
|
||||
<!-- <el-date-picker-->
|
||||
<!-- @change="plannedDateChange"-->
|
||||
<!-- end-placeholder="结束日期"-->
|
||||
<!-- range-separator="-"-->
|
||||
<!-- start-placeholder="开始日期"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- type="daterange"-->
|
||||
<!-- v-model="formData.plannedDate" value-format="yyyy-MM-dd">-->
|
||||
<!-- </el-date-picker>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- <el-col :span="12"-->
|
||||
<!-- v-if="formData.isEnrollSystem === true">-->
|
||||
<!-- <el-form-item label="活动计划时间" prop="plannedDate">-->
|
||||
<!-- <el-date-picker-->
|
||||
<!-- @change="plannedDateChange"-->
|
||||
<!-- end-placeholder="结束日期"-->
|
||||
<!-- range-separator="-"-->
|
||||
<!-- start-placeholder="开始日期"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- type="daterange"-->
|
||||
<!-- v-model="formData.plannedDate" value-format="yyyy-MM-dd">-->
|
||||
<!-- </el-date-picker>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实际活动时间" prop="time">
|
||||
<el-date-picker
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
style="width: 100%"
|
||||
type="daterange"
|
||||
v-model="formData.time"
|
||||
value-format="yyyy-MM-dd">
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
style="width: 100%"
|
||||
type="daterange"
|
||||
v-model="formData.time"
|
||||
value-format="yyyy-MM-dd">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:rules="[{required:[1,2].includes(formData.signUpMethod),message:'请选择报名日期',trigger:['change','blur']}]"
|
||||
label="报名日期"
|
||||
prop="applyTime2">
|
||||
:rules="[{required:[1,2].includes(formData.signUpMethod),message:'请选择报名日期',trigger:['change','blur']}]"
|
||||
label="报名日期"
|
||||
prop="applyTime2">
|
||||
<el-date-picker
|
||||
end-placeholder="结束时间"
|
||||
range-separator="-"
|
||||
start-placeholder="开始时间"
|
||||
style="width: 100%"
|
||||
type="datetimerange"
|
||||
v-model="formData.applyTime2"
|
||||
value-format="yyyy-MM-dd HH:mm">
|
||||
end-placeholder="结束时间"
|
||||
range-separator="-"
|
||||
start-placeholder="开始时间"
|
||||
style="width: 100%"
|
||||
type="datetimerange"
|
||||
v-model="formData.applyTime2"
|
||||
value-format="yyyy-MM-dd HH:mm">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -133,9 +133,9 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
<el-col :span="12" v-if="!formData.isEnrollSystem">
|
||||
<el-form-item label="活动人数" prop="peopleNum">
|
||||
<el-input
|
||||
maxlength="10"
|
||||
placeholder="请填写活动人数"
|
||||
v-model="formData.peopleNum"></el-input>
|
||||
maxlength="10"
|
||||
placeholder="请填写活动人数"
|
||||
v-model="formData.peopleNum"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -156,8 +156,8 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click="$refs.drawerUserScope.userScopeDialog = true"
|
||||
type="primary">设置
|
||||
@click="$refs.drawerUserScope.userScopeDialog = true"
|
||||
type="primary">设置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +181,9 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
<el-form-item label="报名人数限制" prop="userNumberLimit">
|
||||
<el-radio-group v-model="formData.userNumberLimit" size="small">
|
||||
<el-radio border :label="1">总人数限制</el-radio>
|
||||
<el-radio border :label="2" v-if="activity_type===40001">分工会人数限制</el-radio>
|
||||
<el-radio border :label="2" v-if="activity_type===40001">
|
||||
分工会人数限制
|
||||
</el-radio>
|
||||
<el-radio border :label="null">不限制</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
@@ -315,10 +317,10 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
<el-col :span="24" v-if="formData.isEnrollSystem === false">
|
||||
<el-form-item label="活动考核内容" prop="activityExamineContent">
|
||||
<el-input
|
||||
:rows="7"
|
||||
placeholder="请输入活动考核内容"
|
||||
type="textarea"
|
||||
v-model="formData.activityExamineContent">
|
||||
:rows="7"
|
||||
placeholder="请输入活动考核内容"
|
||||
type="textarea"
|
||||
v-model="formData.activityExamineContent">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -428,16 +430,16 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
<el-table :data="userData" border height="500" size="small"
|
||||
stripe v-loading="userTabLoading" row-key="userId">
|
||||
<el-table-column
|
||||
label="序号" type="index"
|
||||
width="100">
|
||||
label="序号" type="index"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:sortable="column.sortable"
|
||||
v-for="column in userColumns">
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:sortable="column.sortable"
|
||||
v-for="column in userColumns">
|
||||
</el-table-column>
|
||||
<el-table-column label="操作"
|
||||
prop="userOnline"
|
||||
@@ -455,26 +457,25 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</table-tool>
|
||||
<el-form-item label="" prop="activitySummary">
|
||||
<el-input
|
||||
:disabled="!formData.time || Date.now() < Date.parse(formData.time[1])"
|
||||
:rows="5"
|
||||
placeholder="请输入活动内容"
|
||||
type="textarea"
|
||||
v-model="formData.activitySummary">
|
||||
:disabled="!formData.time || Date.now() < Date.parse(formData.time[1])"
|
||||
:rows="5"
|
||||
placeholder="请输入活动内容"
|
||||
type="textarea"
|
||||
v-model="formData.activitySummary">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-alert
|
||||
class="mb20"
|
||||
show-icon
|
||||
style="margin-left: 130px"
|
||||
title="活动结束后可输入活动总结"
|
||||
type="info"
|
||||
v-if="!formData.time || Date.now() < Date.parse(formData.time[1])">
|
||||
class="mb20"
|
||||
show-icon
|
||||
style="margin-left: 130px"
|
||||
title="活动结束后可输入活动总结"
|
||||
type="info"
|
||||
v-if="!formData.time || Date.now() < Date.parse(formData.time[1])">
|
||||
</el-alert>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div style="padding: 20px;text-align: center;">
|
||||
<el-button @click="operation" style="width: 300px" type="primary" :disabled="formLoading">
|
||||
确 定
|
||||
@@ -484,12 +485,12 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</el-card>
|
||||
|
||||
<drawer-user-scope
|
||||
@group_change="getActivityGroup"
|
||||
ref="drawerUserScope"
|
||||
:group_id.sync="formData.groupId"
|
||||
@group_change="getActivityGroup"
|
||||
ref="drawerUserScope"
|
||||
:group_id.sync="formData.groupId"
|
||||
></drawer-user-scope>
|
||||
|
||||
<el-dialog :close-on-click-modal="false" :visible.sync="userDialogVisible" title="添加人员" width="40%">
|
||||
<el-dialog :close-on-click-modal="false" :visible.sync="userDialogVisible" append-to-body title="添加人员" width="40%">
|
||||
|
||||
<el-form :model="formData" label-width="100px">
|
||||
<el-form-item label="参加人员" prop="userId">
|
||||
@@ -501,11 +502,11 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
style="width: 100%"
|
||||
v-model="formData.tissuePersonList">
|
||||
<el-option
|
||||
:disabled="userData.map(v=>v.userId).includes(item.userId)"
|
||||
:key="item.userId"
|
||||
:label="item.userName+'-'+item.loginName+'-'+item.unitName"
|
||||
:value="item.userId"
|
||||
v-for="item in userList">
|
||||
:disabled="userData.map(v=>v.userId).includes(item.userId)"
|
||||
:key="item.userId"
|
||||
:label="item.userName+'-'+item.loginName+'-'+item.unitName"
|
||||
:value="item.userId"
|
||||
v-for="item in userList">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -99,11 +99,14 @@ layout("/layouts/platform.html"){
|
||||
</search-item>
|
||||
|
||||
<search-item label="聘用方式:">
|
||||
<dict-select clearable code="USER_PREPARED_BY_TYPE"
|
||||
multiple
|
||||
collapse-tags
|
||||
placeholder="请选择聘用方式"
|
||||
v-model="pageForm.preparedBys"></dict-select>
|
||||
<dict-select
|
||||
clearable
|
||||
code="USER_PREPARED_BY_TYPE"
|
||||
multiple
|
||||
collapse-tags
|
||||
placeholder="请选择聘用方式"
|
||||
v-model="pageForm.preparedBys"
|
||||
></dict-select>
|
||||
</search-item>
|
||||
|
||||
<search-item label="在职状态:">
|
||||
@@ -201,9 +204,22 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<add_welfare_list_by_select ref="addWelfareListUser" @search="doSearch"></add_welfare_list_by_select>
|
||||
|
||||
<!--添加福利人员-->
|
||||
<add-user ref="addUserRef" @refresh="doSearch"></add-user>
|
||||
|
||||
<!--人员备注-->
|
||||
<update-remark ref="updateRemarkRef" @refresh="doSearch"></update-remark>
|
||||
|
||||
<!--导入福利名单-->
|
||||
<excel-import
|
||||
ref="excelImportRef"
|
||||
url="/platform/welfare/list/mange/importByExcel"
|
||||
template_url="/platform/welfare/list/mange/downloadTemplate"
|
||||
:visible.sync="showImportDialog"
|
||||
title="导入用户数据"
|
||||
width="700px"
|
||||
:extra_params="{projectId:pageForm.projectId}"
|
||||
></excel-import>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -260,7 +276,9 @@ layout("/layouts/platform.html"){
|
||||
importLoading: false,
|
||||
importDialog: false,
|
||||
|
||||
welfareListUserDrawer: false
|
||||
welfareListUserDrawer: false,
|
||||
|
||||
showImportDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -275,12 +293,14 @@ layout("/layouts/platform.html"){
|
||||
this.doSearch()
|
||||
this.importDialog = false
|
||||
},
|
||||
|
||||
// 打开导入名单
|
||||
openImport() {
|
||||
this.importData = {
|
||||
isfg: 1,
|
||||
fileList: []
|
||||
}
|
||||
this.importDialog = true
|
||||
this.showImportDialog = true
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("您确定要删除吗?", "提示", {
|
||||
|
||||
@@ -16,12 +16,13 @@ const home = {
|
||||
|
||||
<!--快捷入口-->
|
||||
<div class="quick-grid">
|
||||
<div class="section-title">快速入口</div>
|
||||
<van-grid :column-num="4" icon-size="46" square clickable :border="false">
|
||||
<van-grid-item v-for="item in quickEntries"
|
||||
@click="menuClick(item)"
|
||||
:key="item.id"
|
||||
:text="item.name">
|
||||
<img src="https://staticfile.org/images/banners/Home/7@2x.png"
|
||||
<img :src="item.icon"
|
||||
slot="icon"
|
||||
style="width: 40px; height: 40px"/>
|
||||
</van-grid-item>
|
||||
@@ -30,8 +31,17 @@ const home = {
|
||||
|
||||
<!--活动列表-->
|
||||
<div class="act-list">
|
||||
<div class="section-title">最新活动</div>
|
||||
<template v-if="activityOptions && activityOptions.length > 0">
|
||||
<div v-for="item in activityOptions" :key="item.id" class="act-item" @click="enterAct(item)">
|
||||
<van-tag class="act-item-wrapper-tag" v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
已结束
|
||||
</van-tag>
|
||||
|
||||
<van-tag class="act-item-wrapper-tag" type="success" v-else-if="$moment().unix() < $moment(item.endDate).unix()">
|
||||
进行中
|
||||
</van-tag>
|
||||
|
||||
<div class="act-item-cover">
|
||||
<van-image v-if="item.cover" width="120" height="84" :src="item.cover"></van-image>
|
||||
</div>
|
||||
@@ -93,6 +103,8 @@ const home = {
|
||||
.quick-grid {
|
||||
background-color: #fff;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.quick-grid .van-grid-item .van-grid-item__text {
|
||||
@@ -101,6 +113,7 @@ const home = {
|
||||
text-overflow: ellipsis;
|
||||
width: 85%;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.act-list {
|
||||
@@ -115,17 +128,22 @@ const home = {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.act-item-cover {
|
||||
margin: 0 14px 0 0;
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.act-item-cover .van-image{
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
/*width: 120px;*/
|
||||
/*height: 84px;*/
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.act-item-wrapper{
|
||||
@@ -135,6 +153,13 @@ const home = {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.act-item-wrapper-tag{
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.act-item-wrapper .content-title{
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
@@ -151,5 +176,10 @@ const home = {
|
||||
color: grey;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.section-title{
|
||||
padding: 10px 0 5px 10px;
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user