Merge branch 'main' of https://dd3skj.picp.vip/JyuHsin/zhgh_cug_v4
# Conflicts: # src/main/resources/static/components/module/activity/UserScope.vue
This commit is contained in:
@@ -18,6 +18,7 @@ import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
@@ -110,14 +111,39 @@ public class FlowDesignController {
|
||||
@ApiOperation("获取流程设计分页列表")
|
||||
@SaCheckPermission("flow.design")
|
||||
public Result pageData(PageForm pageForm, String displayName, String name, String category, Boolean deployed) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
pd.*
|
||||
FROM
|
||||
wf_process_design pd
|
||||
LEFT JOIN wf_process_category pc ON pc.id = pd.category
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
|
||||
cnd.and(Cnd.likeEX(ProcessDesign::getDisplayName, displayName));
|
||||
cnd.and(Cnd.likeEX(ProcessDesign::getName, name));
|
||||
cnd.andEX(ProcessDesign::getCategory, "=", category);
|
||||
cnd.andEX(ProcessDesign::getIsDeployed, "=", deployed);
|
||||
cnd.and(Cnd.likeEX("pd.displayName", displayName));
|
||||
cnd.and(Cnd.likeEX("pd.name", name));
|
||||
cnd.andEX("pd.category", "=", category);
|
||||
cnd.andEX("pd.isDeployed", "=", deployed);
|
||||
if ("sortNo".equals(pageForm.getPageOrderName())) {
|
||||
if ("descending".equals(pageForm.getPageOrderBy())) {
|
||||
cnd.desc("pd.sortNo");
|
||||
} else {
|
||||
cnd.asc("pd.sortNo");
|
||||
}
|
||||
} else if ("category".equals(pageForm.getPageOrderName())) {
|
||||
if ("descending".equals(pageForm.getPageOrderBy())) {
|
||||
cnd.desc("pc.name");
|
||||
} else {
|
||||
cnd.asc("pc.name");
|
||||
}
|
||||
} else {
|
||||
cnd.asc("pd.sortNo");
|
||||
}
|
||||
cnd.asc("pd.id");
|
||||
|
||||
Pagination pagination = processDesignService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = processDesignService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@@ -206,6 +232,17 @@ public class FlowDesignController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("修改流程设计排序编码")
|
||||
@SaCheckPermission("flow.design")
|
||||
public Result updateSortNo(@Param("id") Long id, @Param("sortNo") Integer sortNo) {
|
||||
if (id == null || sortNo == null || sortNo < 0) {
|
||||
return Result.error("排序编码必须是非负整数");
|
||||
}
|
||||
processDesignService.update(Chain.make("sortNo", sortNo), Cnd.where(ProcessDesign::getId, "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取流程设计任务参与者处理类")
|
||||
|
||||
@@ -68,6 +68,11 @@ public class ProcessDesign extends BaseModel {
|
||||
@Column
|
||||
private Integer isDeployed;
|
||||
|
||||
@Comment("排序编码")
|
||||
@Column
|
||||
@Default("0")
|
||||
private Integer sortNo;
|
||||
|
||||
@Comment("备注")
|
||||
@Column
|
||||
private String remark;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.budwk.app.flow.handler;
|
||||
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.flow.engine.AssignmentHandler;
|
||||
import com.budwk.app.flow.engine.core.Execution;
|
||||
import com.budwk.app.flow.engine.core.ServiceContext;
|
||||
import com.budwk.app.flow.engine.model.TaskModel;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FlowSchoolUnionActivityAdminHandler implements AssignmentHandler {
|
||||
|
||||
@Override
|
||||
public List<String> assign(TaskModel model, Execution execution) {
|
||||
Dao dao = ServiceContext.find(Dao.class);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
userRole.userId
|
||||
FROM
|
||||
`sys_user_role` userRole
|
||||
LEFT JOIN sys_role role ON role.id = userRole.roleId
|
||||
WHERE
|
||||
role.`code` = @roleCode
|
||||
GROUP BY
|
||||
userRole.userId
|
||||
""").setParam("roleCode", RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao.execute(sql);
|
||||
return sql.getList(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "\u83b7\u53d6\u6821\u5de5\u4f1a\u6d3b\u52a8\u7ba1\u7406\u5458";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return AssignmentHandler.super.getOrder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.enums.SysDataImportPlugin;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.DateUtil;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.random.R;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.AdaptBy;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.mvc.upload.TempFile;
|
||||
import org.nutz.mvc.upload.UploadAdaptor;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/sys/data/tool")
|
||||
@Api("系统数据高级工具")
|
||||
public class SysDataToolController {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/sys/data/tool/index.html")
|
||||
@SaCheckPermission("sys.data.advanced.tool")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询数据库中的表")
|
||||
@SaCheckPermission("sys.data.advanced.tool")
|
||||
public Result tableNames() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
table_name,
|
||||
table_rows,
|
||||
table_comment
|
||||
FROM
|
||||
information_schema.TABLES
|
||||
WHERE
|
||||
table_schema = (SELECT DATABASE())
|
||||
ORDER BY
|
||||
table_name
|
||||
""");
|
||||
return Result.success(sysUserService.listMap(sql));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询数据表字段")
|
||||
@SaCheckPermission("sys.data.advanced.tool")
|
||||
public Result columns(String tableName) {
|
||||
if (StrUtil.isBlank(tableName)) {
|
||||
return Result.error("请选择数据表");
|
||||
}
|
||||
return Result.success(getColumns(tableName));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("执行高级导入")
|
||||
@SLog(tag = "数据管理-高级工具", msg = "执行高级导入")
|
||||
@SaCheckPermission("sys.data.advanced.tool")
|
||||
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
public Result importData(@Param("file") TempFile file,
|
||||
String tableName,
|
||||
String relation,
|
||||
Integer method,
|
||||
String field,
|
||||
String plugins) throws IOException {
|
||||
if (file == null) {
|
||||
return Result.error("请上传 Excel 文件");
|
||||
}
|
||||
if (StrUtil.isBlank(tableName)) {
|
||||
return Result.error("请选择数据表");
|
||||
}
|
||||
if (method == null) {
|
||||
return Result.error("请选择导入方式");
|
||||
}
|
||||
if ((method == 1 || method == 3) && StrUtil.isBlank(field)) {
|
||||
return Result.error("请选择关键字段");
|
||||
}
|
||||
if (StrUtil.isBlank(relation)) {
|
||||
return Result.error("请配置字段对应关系");
|
||||
}
|
||||
|
||||
List<Record> columns = getColumns(tableName);
|
||||
if (columns.isEmpty()) {
|
||||
return Result.error("未获取到数据表字段");
|
||||
}
|
||||
|
||||
NutMap[] relations = parseRelations(relation);
|
||||
List<NutMap> dataList = getDataForExcel(file, relations);
|
||||
List<SysDataImportPlugin> pluginList = parsePlugins(plugins);
|
||||
pluginList.sort(Comparator.comparingInt(SysDataImportPlugin::getLocation));
|
||||
Record pkRecord = getPrimaryKey(columns).orElse(null);
|
||||
Set<String> tableColumns = new HashSet<>();
|
||||
columns.forEach(column -> tableColumns.add(column.getString("column_name")));
|
||||
|
||||
int total = 0;
|
||||
int success = 0;
|
||||
List<ImportError> errors = new ArrayList<>();
|
||||
|
||||
for (NutMap row : dataList) {
|
||||
total++;
|
||||
try {
|
||||
if (StrUtil.isNotBlank(field)) {
|
||||
row.setv(field, row.getString(field));
|
||||
}
|
||||
|
||||
Chain chain = buildChain(row, tableColumns);
|
||||
if (chain == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object pk;
|
||||
if (method == 1) {
|
||||
Record currentRecord = sysUserService.dao().fetch(tableName, Cnd.where(field, "=", row.get(field)));
|
||||
if (currentRecord == null) {
|
||||
pk = prepareBefore(tableName, pkRecord, field, pluginList, true, chain, row, tableColumns);
|
||||
sysUserService.dao().insert(tableName, chain);
|
||||
} else {
|
||||
pk = prepareBefore(tableName, pkRecord, field, pluginList, false, chain, row, tableColumns);
|
||||
sysUserService.dao().update(tableName, chain, Cnd.where(field, "=", row.get(field)));
|
||||
}
|
||||
} else if (method == 2) {
|
||||
pk = prepareBefore(tableName, pkRecord, field, pluginList, true, chain, row, tableColumns);
|
||||
sysUserService.dao().insert(tableName, chain);
|
||||
} else if (method == 3) {
|
||||
pk = prepareBefore(tableName, pkRecord, field, pluginList, false, chain, row, tableColumns);
|
||||
sysUserService.dao().update(tableName, chain, Cnd.where(field, "=", row.get(field)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("导入方式不正确");
|
||||
}
|
||||
|
||||
applyAfterPlugins(tableName, tableColumns, chain, pk, pluginList);
|
||||
success++;
|
||||
} catch (Exception e) {
|
||||
log.error("高级导入失败, table={}, row={}", tableName, row, e);
|
||||
errors.add(new ImportError(Json.toJson(row), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
String cacheKey = "";
|
||||
if (!errors.isEmpty()) {
|
||||
cacheKey = R.UU32();
|
||||
redisService.setex(cacheKey, 60 * 60 * 5, Json.toJson(errors));
|
||||
}
|
||||
|
||||
sysUserService.clearCache();
|
||||
sysRoleService.clearCache();
|
||||
return Result.success(NutMap.NEW().addv("total", total).addv("success", success).addv("cacheKey", cacheKey));
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出错误记录")
|
||||
@SaCheckPermission("sys.data.advanced.tool")
|
||||
public void exportErrors(HttpServletResponse response, String cacheKey) {
|
||||
if (StrUtil.isBlank(cacheKey)) {
|
||||
CommonDownloadUtil.download("导入错误记录.xlsx",
|
||||
ExcelExportUtil.exportExcel(new ExportParams(), ImportError.class, new ArrayList<>()),
|
||||
response);
|
||||
return;
|
||||
}
|
||||
String json = redisService.get(cacheKey);
|
||||
List<ImportError> errors = StrUtil.isBlank(json) ? new ArrayList<>() : Json.fromJsonAsList(ImportError.class, json);
|
||||
CommonDownloadUtil.download("导入错误记录.xlsx",
|
||||
ExcelExportUtil.exportExcel(new ExportParams(), ImportError.class, errors),
|
||||
response);
|
||||
}
|
||||
|
||||
private void applyAfterPlugins(String tableName,
|
||||
Set<String> tableColumns,
|
||||
Chain chain,
|
||||
Object pk,
|
||||
List<SysDataImportPlugin> pluginList) {
|
||||
if (pluginList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SysDataImportPlugin.PluginContext context = new SysDataImportPlugin.PluginContext(
|
||||
tableName,
|
||||
tableColumns,
|
||||
chain,
|
||||
pk,
|
||||
sysUserService.dao(),
|
||||
sysRoleService
|
||||
);
|
||||
pluginList.forEach(plugin -> plugin.after(context));
|
||||
}
|
||||
|
||||
private Object prepareBefore(String tableName,
|
||||
Record pkRecord,
|
||||
String field,
|
||||
List<SysDataImportPlugin> pluginList,
|
||||
boolean save,
|
||||
Chain chain,
|
||||
NutMap row,
|
||||
Set<String> tableColumns) {
|
||||
Object pk = resolvePrimaryKeyValue(tableName, pkRecord, field, save, chain, row);
|
||||
SysDataImportPlugin.PluginContext context = new SysDataImportPlugin.PluginContext(
|
||||
tableName,
|
||||
tableColumns,
|
||||
chain,
|
||||
pk,
|
||||
sysUserService.dao(),
|
||||
sysRoleService
|
||||
);
|
||||
pluginList.forEach(plugin -> plugin.before(context));
|
||||
return pk;
|
||||
}
|
||||
|
||||
private Object resolvePrimaryKeyValue(String tableName,
|
||||
Record pkRecord,
|
||||
String field,
|
||||
boolean save,
|
||||
Chain chain,
|
||||
NutMap row) {
|
||||
if (pkRecord == null) {
|
||||
return null;
|
||||
}
|
||||
String pkColumnName = pkRecord.getString("column_name");
|
||||
String pkColumnType = pkRecord.getString("column_type");
|
||||
|
||||
Object pk;
|
||||
if (save) {
|
||||
pk = row.get(pkColumnName);
|
||||
if (pk == null) {
|
||||
pk = generatePrimaryKey(pkColumnType);
|
||||
chain.add(pkColumnName, pk);
|
||||
}
|
||||
} else {
|
||||
Record record = sysUserService.dao().fetch(tableName, Cnd.where(field, "=", row.get(field)));
|
||||
if (record == null) {
|
||||
throw new IllegalArgumentException("未找到关键字段匹配的记录");
|
||||
}
|
||||
pk = record.get(pkColumnName);
|
||||
}
|
||||
|
||||
if (pk == null) {
|
||||
throw new IllegalArgumentException("主键生成失败");
|
||||
}
|
||||
return pk;
|
||||
}
|
||||
|
||||
private Object generatePrimaryKey(String pkColumnType) {
|
||||
if (StrUtil.isBlank(pkColumnType)) {
|
||||
return R.UU32();
|
||||
}
|
||||
return switch (pkColumnType.toLowerCase()) {
|
||||
case "int", "integer" -> R.random(1000, 9999);
|
||||
case "varchar(16)" -> R.UU16();
|
||||
case "varchar(64)" -> R.UU64();
|
||||
default -> R.UU32();
|
||||
};
|
||||
}
|
||||
|
||||
private Chain buildChain(NutMap row, Set<String> tableColumns) {
|
||||
NutMap filtered = NutMap.NEW();
|
||||
row.forEach((key, value) -> {
|
||||
if (key != null && tableColumns.contains(key)) {
|
||||
filtered.addv(key, value);
|
||||
}
|
||||
});
|
||||
return filtered.isEmpty() ? null : Chain.from(filtered);
|
||||
}
|
||||
|
||||
private NutMap[] parseRelations(String relationJson) {
|
||||
List<NutMap> relationList = Json.fromJsonAsList(NutMap.class, relationJson);
|
||||
return relationList.toArray(new NutMap[0]);
|
||||
}
|
||||
|
||||
private List<SysDataImportPlugin> parsePlugins(String pluginsJson) {
|
||||
if (StrUtil.isBlank(pluginsJson)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> pluginNames = Json.fromJsonAsList(String.class, pluginsJson);
|
||||
List<SysDataImportPlugin> plugins = new ArrayList<>();
|
||||
for (String pluginName : pluginNames) {
|
||||
if (StrUtil.isBlank(pluginName)) {
|
||||
continue;
|
||||
}
|
||||
plugins.add(SysDataImportPlugin.valueOf(pluginName));
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private Optional<Record> getPrimaryKey(List<Record> columns) {
|
||||
return columns.stream().filter(column -> "PRI".equalsIgnoreCase(column.getString("column_key"))).findFirst();
|
||||
}
|
||||
|
||||
private List<Record> getColumns(String tableName) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
column_name,
|
||||
is_nullable,
|
||||
column_type,
|
||||
column_key,
|
||||
column_comment
|
||||
FROM
|
||||
information_schema.COLUMNS
|
||||
WHERE
|
||||
table_name = @tableName
|
||||
AND table_schema = (SELECT DATABASE())
|
||||
ORDER BY
|
||||
ordinal_position
|
||||
""").setParam("tableName", tableName);
|
||||
return sysUserService.list(sql);
|
||||
}
|
||||
|
||||
private List<NutMap> getDataForExcel(TempFile file, NutMap[] relation) throws IOException {
|
||||
List<NutMap> result = new ArrayList<>();
|
||||
Map<Integer, String> headerColumnMap = new HashMap<>();
|
||||
|
||||
Sheet sheet = WorkbookFactory.create(file.getInputStream()).getSheetAt(0);
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int columnNum = headerRow.getPhysicalNumberOfCells();
|
||||
for (int i = 0; i < columnNum; i++) {
|
||||
Cell cell = headerRow.getCell(i);
|
||||
if (cell == null) {
|
||||
continue;
|
||||
}
|
||||
String header = cell.getStringCellValue();
|
||||
for (NutMap item : relation) {
|
||||
if (StrUtil.equals(item.getString("relation"), header)) {
|
||||
headerColumnMap.put(i, item.getString("column_name"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rowNum = sheet.getPhysicalNumberOfRows();
|
||||
for (int i = 1; i < rowNum; i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
NutMap data = NutMap.NEW();
|
||||
for (int j = 0; j < columnNum; j++) {
|
||||
String columnName = headerColumnMap.get(j);
|
||||
if (columnName == null) {
|
||||
continue;
|
||||
}
|
||||
data.setv(columnName, getCellValue(row.getCell(j)));
|
||||
}
|
||||
if (!data.isEmpty()) {
|
||||
result.add(data);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object getCellValue(Cell cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
CellType cellType = cell.getCellType();
|
||||
if (cellType == CellType.NUMERIC) {
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
Date date = cell.getDateCellValue();
|
||||
return DATE_TIME_FORMATTER.format(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));
|
||||
}
|
||||
double numericValue = cell.getNumericCellValue();
|
||||
BigDecimal decimal = BigDecimal.valueOf(numericValue);
|
||||
if (String.valueOf(numericValue).contains("E")) {
|
||||
return decimal.toPlainString();
|
||||
}
|
||||
if (numericValue == Math.rint(numericValue)) {
|
||||
return decimal.toBigInteger();
|
||||
}
|
||||
return numericValue;
|
||||
}
|
||||
if (cellType == CellType.STRING) {
|
||||
return cell.getStringCellValue();
|
||||
}
|
||||
if (cellType == CellType.BOOLEAN) {
|
||||
return cell.getBooleanCellValue();
|
||||
}
|
||||
if (cellType == CellType.ERROR) {
|
||||
return cell.getErrorCellValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ImportError {
|
||||
@Excel(name = "行数据", width = 80)
|
||||
private String row;
|
||||
|
||||
@Excel(name = "错误原因", width = 40)
|
||||
private String errorMsg;
|
||||
}
|
||||
}
|
||||
@@ -150,8 +150,10 @@ public class SysHomeController {
|
||||
);
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<String> allMenuIds = menus.stream().map(Sys_menu::getId).toList();
|
||||
//List<Sys_menu> sysMenus = list.stream().filter(m -> allMenuIds.contains(m.getId())).sorted(Comparator.comparingInt(Sys_menu::getLocation)).toList();
|
||||
List<Sys_menu> sysMenus = list.stream().sorted(Comparator.comparingInt(Sys_menu::getLocation)).toList();
|
||||
List<Sys_menu> sysMenus = list.stream()
|
||||
.sorted(Comparator.comparing(Sys_menu::getLocation, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(Sys_menu::getId))
|
||||
.toList();
|
||||
return Result.success(sysMenus);
|
||||
}
|
||||
|
||||
|
||||
@@ -411,6 +411,24 @@ public class SysMenuController {
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json")
|
||||
@SaCheckPermission("sys.manager.menu.edit")
|
||||
public Object updateLocation(String id, Integer location) {
|
||||
try {
|
||||
if (StrUtil.isBlank(id) || location == null || location < 0) {
|
||||
return Result.error("排序编码必须是非负整数");
|
||||
}
|
||||
sysMenuService.update(Chain.make("location", location), Cnd.where("id", "=", id));
|
||||
sysMenuService.clearCache();
|
||||
sysUserService.clearCache();
|
||||
sysRoleService.clearCache();
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json")
|
||||
@SaCheckPermission("sys.manager.menu")
|
||||
|
||||
@@ -257,7 +257,7 @@ public class SysUnionController {
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("sys.manager.union")
|
||||
public Result branchUnionUserPageData(PageForm pageForm, String unionId) {
|
||||
public Result branchUnionUserPageData(PageForm pageForm, String unionId, @Param("j") String j) {
|
||||
List<Sys_dict> branchUnionRoles = sysDictService.getSubListByCode("BRANCH_UNION_ROLES");
|
||||
if (Lang.isEmpty(branchUnionRoles)) {
|
||||
return Result.success();
|
||||
@@ -267,12 +267,18 @@ public class SysUnionController {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COALESCE(jDict.`name`, info.j, curJDict.`name`, curSession.j) AS jName,
|
||||
role.`name` AS roleName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
CASE
|
||||
WHEN info.isServing = 0 THEN '离任'
|
||||
WHEN t.id IS NOT NULL THEN t.displayName
|
||||
ELSE '在任'
|
||||
END AS displayStatus,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
@@ -287,6 +293,16 @@ public class SysUnionController {
|
||||
FROM
|
||||
sys_union_cadre info
|
||||
LEFT JOIN sys_role role ON role.`code` = info.roleCode
|
||||
LEFT JOIN sys_dict jParent ON jParent.`code` = 'TEACHER_CONGRESS_J'
|
||||
LEFT JOIN sys_dict jDict ON jDict.parentId = jParent.id AND jDict.`code` = info.j
|
||||
LEFT JOIN (
|
||||
SELECT j
|
||||
FROM teacher_congress_session
|
||||
WHERE enable = 1
|
||||
ORDER BY startDate DESC
|
||||
LIMIT 1
|
||||
) curSession ON info.j IS NULL OR info.j = ''
|
||||
LEFT JOIN sys_dict curJDict ON curJDict.parentId = jParent.id AND curJDict.`code` = curSession.j
|
||||
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
|
||||
@@ -303,6 +319,9 @@ public class SysUnionController {
|
||||
case "roleName" -> cnd.where().andLike("role.`name`", pageForm.getSearchKeyword());
|
||||
}
|
||||
}
|
||||
if (StrUtil.isNotBlank(j)) {
|
||||
cnd.and("COALESCE(NULLIF(info.j, ''), curSession.j)", "=", j);
|
||||
}
|
||||
sql.setVar("constructionSql",
|
||||
new Static(" group by info.id order by field(role.code," + branchUnionRoleCodes.stream().map(code -> "'" + code + "'").collect(Collectors.joining(",")) + ")")
|
||||
);
|
||||
@@ -311,11 +330,51 @@ public class SysUnionController {
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("sys.manager.union")
|
||||
public Result branchUnionUserUsedJData(String unionId) {
|
||||
List<Sys_dict> branchUnionRoles = sysDictService.getSubListByCode("BRANCH_UNION_ROLES");
|
||||
if (Lang.isEmpty(branchUnionRoles)) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<String> branchUnionRoleCodes = branchUnionRoles.stream().map(Sys_dict::getCode).toList();
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
COALESCE(NULLIF(info.j, ''), curSession.j) AS j,
|
||||
COALESCE(jDict.location, curJDict.location, 0) AS jLocation
|
||||
FROM
|
||||
sys_union_cadre info
|
||||
LEFT JOIN sys_role role ON role.`code` = info.roleCode
|
||||
LEFT JOIN sys_dict jParent ON jParent.`code` = 'TEACHER_CONGRESS_J'
|
||||
LEFT JOIN sys_dict jDict ON jDict.parentId = jParent.id AND jDict.`code` = info.j
|
||||
LEFT JOIN (
|
||||
SELECT j
|
||||
FROM teacher_congress_session
|
||||
WHERE enable = 1
|
||||
ORDER BY startDate DESC
|
||||
LIMIT 1
|
||||
) curSession ON info.j IS NULL OR info.j = ''
|
||||
LEFT JOIN sys_dict curJDict ON curJDict.parentId = jParent.id AND curJDict.`code` = curSession.j
|
||||
$condition
|
||||
ORDER BY jLocation DESC
|
||||
""");
|
||||
Cnd cnd = Cnd.where("role.`code`", "in", branchUnionRoleCodes);
|
||||
cnd.and("info.unionId", "=", unionId);
|
||||
sql.setCondition(cnd);
|
||||
List<String> usedJCodes = sysUserService.listMap(sql).stream()
|
||||
.map(item -> item.getString("j"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
return Result.success(usedJCodes);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("sys.manager.union.branchOfficer")
|
||||
@ApiOperation("添加分工会人员角色")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result insertBranchUnionUserRole(String userId, String roleCode, String unionId) {
|
||||
public Result insertBranchUnionUserRole(String userId, String roleCode, String unionId, String j) {
|
||||
Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode));
|
||||
if (Lang.isEmpty(role)) {
|
||||
return Result.success("无法找到" + roleCode + "对应编码的角色");
|
||||
@@ -353,7 +412,9 @@ public class SysUnionController {
|
||||
unionCadre.setLoginName(user.getLoginname());
|
||||
unionCadre.setUserName(user.getUsername());
|
||||
unionCadre.setRoleCode(roleCode);
|
||||
unionCadre.setJ(j);
|
||||
unionCadre.setApplyDate(new Date());
|
||||
unionCadre.setIsServing(true);
|
||||
unionCadre.setIsJoin(true);
|
||||
dao.insert(unionCadre);
|
||||
|
||||
@@ -382,6 +443,39 @@ public class SysUnionController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分工会干部离任")
|
||||
@SaCheckPermission("sys.manager.union.branchOfficer")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result leaveBranchUnionUserRole(String id, String leaveDate) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择离任人员");
|
||||
}
|
||||
if (StrUtil.isBlank(leaveDate)) {
|
||||
return Result.error("请选择离任时间");
|
||||
}
|
||||
Sys_union_cadre unionCadre = dao.fetch(Sys_union_cadre.class, id);
|
||||
if (Lang.isEmpty(unionCadre)) {
|
||||
return Result.error("未找到对应干部记录");
|
||||
}
|
||||
Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", unionCadre.getRoleCode()));
|
||||
if (Lang.isEmpty(role)) {
|
||||
throw new BaseException("无法找到{}对应编码的角色", unionCadre.getRoleCode());
|
||||
}
|
||||
|
||||
Date parsedLeaveDate = cn.hutool.core.date.DateUtil.parseDate(leaveDate);
|
||||
dao.update(Sys_union_cadre.class,
|
||||
Chain.make("isServing", false).add("leaveDate", parsedLeaveDate),
|
||||
Cnd.where("id", "=", id));
|
||||
|
||||
sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", role.getId())
|
||||
.and("userId", "=", unionCadre.getUserId())
|
||||
.and("unionId", "=", unionCadre.getUnionId()));
|
||||
sysRoleService.clearCache();
|
||||
sysUserService.clearCache();
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除分工会人员角色")
|
||||
@SaCheckPermission("sys.manager.union.branchOfficer")
|
||||
|
||||
@@ -72,6 +72,7 @@ public class SysV4AppsController {
|
||||
m.href,
|
||||
m.icon,
|
||||
m.picIcon,
|
||||
m.location,
|
||||
sm.`name` AS moduleName,
|
||||
CASE
|
||||
WHEN f.userId IS NOT NULL THEN
|
||||
@@ -91,6 +92,7 @@ public class SysV4AppsController {
|
||||
cnd.and("m.id","in", menus.stream().map(Sys_menu::getId).toArray());
|
||||
cnd.andEX("m.moduleId", "=", categoryId);
|
||||
cnd.asc("m.location");
|
||||
cnd.asc("m.id");
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
cnd.where().andLike("m.name", keyword);
|
||||
}
|
||||
@@ -145,6 +147,7 @@ public class SysV4AppsController {
|
||||
m.href,
|
||||
m.icon,
|
||||
m.picIcon,
|
||||
m.location,
|
||||
sm.`name` AS moduleName,
|
||||
1 AS isFavorite
|
||||
FROM
|
||||
@@ -159,7 +162,8 @@ public class SysV4AppsController {
|
||||
AND m.platform = @platform
|
||||
AND m.disabled = 0
|
||||
ORDER BY
|
||||
m.location ASC;
|
||||
m.location ASC,
|
||||
m.id ASC;
|
||||
""");
|
||||
sql.setParam("userId", SecurityUtil.getUserId());
|
||||
sql.setParam("platform", platform);
|
||||
|
||||
@@ -46,13 +46,14 @@ public class SysV4ServController {
|
||||
@ApiOperation("获取应用列表")
|
||||
public Result list(@Param("categoryId") String categoryId, @Param("letter") String letter, @Param("keyword") String keyword, HttpServletRequest req) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT t.*,(select picIcon from wf_process_design where name = t.name) as picIcon
|
||||
SELECT t.*, d.picIcon, d.sortNo
|
||||
FROM wf_process_define t
|
||||
INNER JOIN (
|
||||
SELECT name, MAX(id) AS max_id
|
||||
FROM wf_process_define
|
||||
GROUP BY name
|
||||
) sub ON t.name = sub.name AND t.id = sub.max_id
|
||||
LEFT JOIN wf_process_design d ON d.name = t.name
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
@@ -61,6 +62,8 @@ public class SysV4ServController {
|
||||
}
|
||||
cnd.andEX("t.category", "=", categoryId);
|
||||
cnd.andEX("t.pinyinName", "=", letter);
|
||||
cnd.asc("d.sortNo");
|
||||
cnd.asc("t.id");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = sysMenuService.listMap(sql);
|
||||
return Result.success(list);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.budwk.app.sys.enums;
|
||||
|
||||
import com.budwk.app.base.annotation.DictEnum;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.utils.PwdUtil;
|
||||
import com.budwk.app.sys.models.Sys_role;
|
||||
import com.budwk.app.sys.models.Sys_user_role;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import lombok.Getter;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.lang.random.R;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 高级导入工具插件。
|
||||
*/
|
||||
@Getter
|
||||
@DictEnum(key = "sysDataImportPlugin", name = "系统数据高级工具插件")
|
||||
public enum SysDataImportPlugin {
|
||||
|
||||
USER_INIT(1, "初始化系统用户"),
|
||||
MEMBER_INIT(2, "设置为会员");
|
||||
|
||||
private final int location;
|
||||
private final String description;
|
||||
|
||||
SysDataImportPlugin(int location, String description) {
|
||||
this.location = location;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void before(PluginContext context) {
|
||||
if (!context.isSysUserTable()) {
|
||||
return;
|
||||
}
|
||||
if (this == USER_INIT) {
|
||||
String salt = R.UU32();
|
||||
context.addValue("salt", salt);
|
||||
context.addValue("password", PwdUtil.getPassword(PwdUtil.generate(12), salt));
|
||||
context.addValue("loginCount", 0);
|
||||
context.addValue("disabled", false);
|
||||
return;
|
||||
}
|
||||
if (this == MEMBER_INIT) {
|
||||
context.addValue("member", true);
|
||||
context.addValue("memberTime", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
public void after(PluginContext context) {
|
||||
if (!context.isSysUserTable()) {
|
||||
return;
|
||||
}
|
||||
if (this == USER_INIT) {
|
||||
context.ensureRole(RoleConstant.PUBLIC);
|
||||
return;
|
||||
}
|
||||
if (this == MEMBER_INIT) {
|
||||
context.ensureRole(RoleConstant.MEMBER);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class PluginContext {
|
||||
private final String tableName;
|
||||
private final Set<String> tableColumns;
|
||||
private final Chain chain;
|
||||
private final Object pk;
|
||||
private final Dao dao;
|
||||
private final SysRoleService sysRoleService;
|
||||
|
||||
public PluginContext(String tableName,
|
||||
Set<String> tableColumns,
|
||||
Chain chain,
|
||||
Object pk,
|
||||
Dao dao,
|
||||
SysRoleService sysRoleService) {
|
||||
this.tableName = tableName;
|
||||
this.tableColumns = tableColumns;
|
||||
this.chain = chain;
|
||||
this.pk = pk;
|
||||
this.dao = dao;
|
||||
this.sysRoleService = sysRoleService;
|
||||
}
|
||||
|
||||
public boolean isSysUserTable() {
|
||||
return "sys_user".equalsIgnoreCase(tableName);
|
||||
}
|
||||
|
||||
public boolean hasColumn(String columnName) {
|
||||
return tableColumns.contains(columnName);
|
||||
}
|
||||
|
||||
public void addValue(String columnName, Object value) {
|
||||
if (hasColumn(columnName)) {
|
||||
chain.add(columnName, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureRole(RoleConstant roleConstant) {
|
||||
if (pk == null || !isSysUserTable()) {
|
||||
return;
|
||||
}
|
||||
Sys_role sysRole = sysRoleService.getByCode(roleConstant);
|
||||
if (sysRole == null) {
|
||||
return;
|
||||
}
|
||||
int count = dao.count(Sys_user_role.class, Cnd.where("userId", "=", String.valueOf(pk))
|
||||
.and("roleId", "=", sysRole.getId()));
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
Sys_user_role userRole = new Sys_user_role();
|
||||
userRole.setUserId(String.valueOf(pk));
|
||||
userRole.setRoleId(sysRole.getId());
|
||||
dao.insert(userRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,11 +61,26 @@ public class Sys_union_cadre extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String roleCode;
|
||||
|
||||
@Column
|
||||
@Comment("届数")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 8)
|
||||
private String j;
|
||||
|
||||
@Column
|
||||
@Comment("添加时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date applyDate;
|
||||
|
||||
@Column
|
||||
@Comment("离任时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date leaveDate;
|
||||
|
||||
@Column
|
||||
@Comment("是否在任")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean isServing;
|
||||
|
||||
@Column
|
||||
@Comment("加入还是退出")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
|
||||
+53
-10
@@ -317,10 +317,7 @@ public class ActivityBasicScopeController {
|
||||
cnd.orderBy(activityUserScopePageParam.getPageOrderName(), activityUserScopePageParam.getPageOrderBy().equalsIgnoreCase("ascending") ? "ASC" : "DESC");
|
||||
}
|
||||
|
||||
if (activityUserScopePageParam.getActivityGroupId() != null) {
|
||||
Sql sqlx = activityBasicScopeService.buildGroupUserIdSubSql(activityUserScopePageParam.getActivityGroupId());
|
||||
cnd.and("u.id", activityUserScopePageParam.getReverseSelection() ? "IN" : "NOT IN", sqlx);
|
||||
}
|
||||
appendActivityGroupCondition(cnd, activityUserScopePageParam);
|
||||
|
||||
cnd.andEX("u.id", IN_OR_NIN_OP, activityUserScopePageParam.getUserId());
|
||||
|
||||
@@ -332,7 +329,7 @@ public class ActivityBasicScopeController {
|
||||
cnd.and("u.welfareMember", EQ_OR_NEQ_OP, 1);
|
||||
}
|
||||
if (ArrayUtils.contains(activityUserScopePageParam.getMemberTypes(), "基金会员")) {
|
||||
cnd.and(new Static(" u.loginname " + IN_OR_NIN_OP + " (select loginname from sick_fund_member)"));
|
||||
cnd.and("u.aidFundMember", EQ_OR_NEQ_OP, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,8 +375,12 @@ public class ActivityBasicScopeController {
|
||||
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
}
|
||||
cnd.andEX("u.unionid", EQ_OR_NEQ_OP, activityUserScopePageParam.getUnionId());
|
||||
cnd.andEX("u.unitid", EQ_OR_NEQ_OP, activityUserScopePageParam.getUnitId());
|
||||
String[] unionIds = getUnionIds(activityUserScopePageParam);
|
||||
if (Lang.isNotEmpty(unionIds)) {
|
||||
cnd.andEX("u.unionid", IN_OR_NIN_OP, unionIds);
|
||||
} else {
|
||||
cnd.andEX("u.unionid", EQ_OR_NEQ_OP, activityUserScopePageParam.getUnionId());
|
||||
}
|
||||
cnd.andEX("u.personType", IN_OR_NIN_OP, activityUserScopePageParam.getPersonTypes());
|
||||
cnd.andEX("u.userState", IN_OR_NIN_OP, activityUserScopePageParam.getUserStates());
|
||||
cnd.andEX("u.sex", IN_OR_NIN_OP, activityUserScopePageParam.getSexTypes());
|
||||
@@ -394,6 +395,34 @@ public class ActivityBasicScopeController {
|
||||
|
||||
}
|
||||
|
||||
private void appendActivityGroupCondition(Cnd cnd, ActivityUserScopePageParam activityUserScopePageParam) {
|
||||
Integer activityGroupId = activityUserScopePageParam.getActivityGroupId();
|
||||
if (activityGroupId == null) {
|
||||
return;
|
||||
}
|
||||
boolean reverseSelection = Boolean.TRUE.equals(activityUserScopePageParam.getReverseSelection());
|
||||
Integer groupType = activityBasicScopeService.getGroupType(activityGroupId);
|
||||
if (GROUP_TYPE_RESULT == groupType) {
|
||||
String existsSql = """
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM activity_user_scope aus
|
||||
WHERE aus.groupId = %d
|
||||
AND aus.userId = u.id
|
||||
)
|
||||
""".formatted(activityGroupId);
|
||||
cnd.and(new Static(reverseSelection ? existsSql : "NOT " + existsSql));
|
||||
return;
|
||||
}
|
||||
|
||||
ActivityUserScope groupInfo = activityBasicScopeService.getGroupInfo(activityGroupId);
|
||||
if (groupInfo == null || StrUtil.isBlank(groupInfo.getGroupSql())) {
|
||||
return;
|
||||
}
|
||||
String groupSql = "(" + groupInfo.getGroupSql() + ")";
|
||||
cnd.and(new Static(reverseSelection ? groupSql : "NOT " + groupSql));
|
||||
}
|
||||
|
||||
/**
|
||||
* 结果分组会把当前筛选出的用户快照保存下来,因此只落 userId 明细数据。
|
||||
*/
|
||||
@@ -577,7 +606,7 @@ public class ActivityBasicScopeController {
|
||||
conditionList.add("u.welfareMember " + eqOrNeq + " 1");
|
||||
}
|
||||
if (ArrayUtils.contains(activityUserScopePageParam.getMemberTypes(), "基金会员")) {
|
||||
conditionList.add("u.loginname " + inOrNotIn + " (select loginname from sick_fund_member)");
|
||||
conditionList.add("u.aidFundMember " + eqOrNeq + " 1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,8 +630,12 @@ public class ActivityBasicScopeController {
|
||||
conditionList.add("u.unionid = " + wrapSqlValue(SecurityUtil.getUnionId()));
|
||||
}
|
||||
|
||||
addSingleValueCondition(conditionList, "u.unionid", eqOrNeq, activityUserScopePageParam.getUnionId());
|
||||
addSingleValueCondition(conditionList, "u.unitid", eqOrNeq, activityUserScopePageParam.getUnitId());
|
||||
String[] unionIds = getUnionIds(activityUserScopePageParam);
|
||||
if (Lang.isNotEmpty(unionIds)) {
|
||||
addArrayCondition(conditionList, "u.unionid", inOrNotIn, unionIds);
|
||||
} else {
|
||||
addSingleValueCondition(conditionList, "u.unionid", eqOrNeq, activityUserScopePageParam.getUnionId());
|
||||
}
|
||||
addArrayCondition(conditionList, "u.personType", inOrNotIn, activityUserScopePageParam.getPersonTypes());
|
||||
addArrayCondition(conditionList, "u.userState", inOrNotIn, activityUserScopePageParam.getUserStates());
|
||||
addArrayCondition(conditionList, "u.sex", inOrNotIn, activityUserScopePageParam.getSexTypes());
|
||||
@@ -637,6 +670,16 @@ public class ActivityBasicScopeController {
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getUnionIds(ActivityUserScopePageParam activityUserScopePageParam) {
|
||||
if (Lang.isNotEmpty(activityUserScopePageParam.getUnionIds())) {
|
||||
return activityUserScopePageParam.getUnionIds();
|
||||
}
|
||||
if (StrUtil.isNotBlank(activityUserScopePageParam.getUnionId())) {
|
||||
return new String[]{activityUserScopePageParam.getUnionId()};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addArrayCondition(List<String> conditionList, String columnName, String operator, String[] values) {
|
||||
if (Lang.isNotEmpty(values)) {
|
||||
conditionList.add(columnName + " " + operator + " (" + buildSqlStringList(values) + ")");
|
||||
|
||||
@@ -20,7 +20,8 @@ import java.io.Serializable;
|
||||
@Comment("活动人员范围设置")
|
||||
@TableIndexes({
|
||||
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_GROUPID", fields = {"groupId"}, unique = false),
|
||||
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_USERID", fields = {"userId"}, unique = false)
|
||||
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_USERID", fields = {"userId"}, unique = false),
|
||||
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_GROUPID_USERID", fields = {"groupId", "userId"}, unique = false)
|
||||
})
|
||||
public class ActivityUserScope extends BaseModel implements Serializable {
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public class ActivityUserScopePageParam extends PageForm {
|
||||
|
||||
// 工会id
|
||||
private String unionId;
|
||||
private String[] unionIds;
|
||||
// 单位id
|
||||
private String unitId;
|
||||
// 人类型
|
||||
|
||||
+561
-19
@@ -15,6 +15,7 @@ import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
|
||||
import com.budwk.app.sys.models.SysHoliday;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
@@ -29,6 +30,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
@@ -40,16 +43,19 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.time.Year;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api(tags = "场地预约-场馆")
|
||||
@Api(tags = "\u573a\u5730\u9884\u7ea6-\u573a\u9986")
|
||||
@At("/platform/siteCug/apply")
|
||||
public class SiteCugApplyController {
|
||||
|
||||
@@ -67,20 +73,23 @@ public class SiteCugApplyController {
|
||||
@At("/")
|
||||
@SaCheckPermission("siteCug.apply")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/siteCug/apply/index.html")
|
||||
public void index() {}
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/siteCug/apply/index.html")
|
||||
public void h5Index() {}
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/form/h5")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/siteCug/apply/form/index.html")
|
||||
public void h5Form() {}
|
||||
public void h5Form() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询场地")
|
||||
@ApiOperation("\u5206\u9875\u67e5\u8be2\u573a\u5730")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm, @Param("type") String type) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
@@ -102,17 +111,530 @@ public class SiteCugApplyController {
|
||||
|
||||
List<SiteCugFunctionType> typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW());
|
||||
Map<String, String> typeMap = typeList.stream().collect(Collectors.toMap(SiteCugFunctionType::getId, SiteCugFunctionType::getName));
|
||||
List<String> siteIds = listMap.stream().map(item -> item.getString("id")).filter(StrUtil::isNotBlank).toList();
|
||||
String today = DateUtil.today();
|
||||
String dayStart = today + " 00:00:00";
|
||||
String dayEnd = today + " 23:59:59";
|
||||
Map<String, List<SiteCugApply>> applyMap = queryTodayApplyMap(siteIds, dayStart, dayEnd);
|
||||
Set<String> holidaySet = dao.query(SysHoliday.class, Cnd.NEW()).stream()
|
||||
.map(SysHoliday::getDay)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
for (NutMap map : listMap) {
|
||||
map.put("typeName", typeMap.get(map.getString("typeId")));
|
||||
map.put("reserveTimeTypeName", getReserveTimeTypeName(map.getInt("reserveTimeType")));
|
||||
map.put("timelineSegments", buildTimelineSegments(map, applyMap.getOrDefault(map.getString("id"), new ArrayList<>()), today, holidaySet));
|
||||
}
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("提交预约")
|
||||
@ApiOperation("\u67e5\u8be2\u573a\u5730\u53ef\u9884\u7ea6\u65f6\u6bb5")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "场馆功能管理-场地预约", msg = "提交场地预约")
|
||||
public Result availability(@Param("siteId") String siteId, @Param("date") String date) {
|
||||
if (StrUtil.isBlank(siteId)) {
|
||||
return Result.error("\u573a\u5730\u4e0d\u80fd\u4e3a\u7a7a");
|
||||
}
|
||||
String targetDate = StrUtil.isBlank(date) ? DateUtil.today() : date;
|
||||
if (!targetDate.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
|
||||
return Result.error("\u9884\u7ea6\u65e5\u671f\u683c\u5f0f\u4e0d\u6b63\u786e");
|
||||
}
|
||||
SiteCugInfo siteInfo = infoService.fetch(siteId);
|
||||
if (siteInfo == null) {
|
||||
return Result.error("\u573a\u5730\u4e0d\u5b58\u5728");
|
||||
}
|
||||
Set<String> holidaySet = queryHolidaySet();
|
||||
List<SiteCugApply> applyList = queryDayApplyList(siteId, targetDate + " 00:00:00", targetDate + " 23:59:59");
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("date", targetDate)
|
||||
.addv("reserveTimeType", siteInfo.getReserveTimeType())
|
||||
.addv("reserveTimeTypeName", getReserveTimeTypeName(siteInfo.getReserveTimeType()))
|
||||
.addv("blocks", buildAvailabilityBlocks(siteInfo, targetDate, applyList, holidaySet)));
|
||||
}
|
||||
|
||||
private Map<String, List<SiteCugApply>> queryTodayApplyMap(List<String> siteIds, String dayStart, String dayEnd) {
|
||||
if (siteIds == null || siteIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
sa.*
|
||||
from
|
||||
site_cug_apply sa
|
||||
left join wf_process_instance ins on ins.businessNo = sa.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("sa.siteId", "in", siteIds);
|
||||
cnd.and("ins.state", "in", List.of(
|
||||
ProcessInstanceStateEnum.DOING.getCode(),
|
||||
ProcessInstanceStateEnum.FINISHED.getCode(),
|
||||
ProcessInstanceStateEnum.PENDING.getCode()
|
||||
));
|
||||
cnd.and("sa.reserveEndTime", ">=", dayStart);
|
||||
cnd.and("sa.reserveStartTime", "<=", dayEnd);
|
||||
sql.setCondition(cnd);
|
||||
sql.setEntity(dao.getEntity(SiteCugApply.class));
|
||||
sql.setCallback(Sqls.callback.entities());
|
||||
dao.execute(sql);
|
||||
List<SiteCugApply> applyList = sql.getList(SiteCugApply.class);
|
||||
if (applyList == null || applyList.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
return applyList.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(SiteCugApply::getSiteId));
|
||||
}
|
||||
|
||||
private List<SiteCugApply> queryDayApplyList(String siteId, String dayStart, String dayEnd) {
|
||||
if (StrUtil.isBlank(siteId)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
sa.*
|
||||
from
|
||||
site_cug_apply sa
|
||||
left join wf_process_instance ins on ins.businessNo = sa.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("sa.siteId", "=", siteId);
|
||||
cnd.and("ins.state", "in", List.of(
|
||||
ProcessInstanceStateEnum.DOING.getCode(),
|
||||
ProcessInstanceStateEnum.FINISHED.getCode(),
|
||||
ProcessInstanceStateEnum.PENDING.getCode()
|
||||
));
|
||||
cnd.and("sa.reserveEndTime", ">=", dayStart);
|
||||
cnd.and("sa.reserveStartTime", "<=", dayEnd);
|
||||
sql.setCondition(cnd);
|
||||
sql.setEntity(dao.getEntity(SiteCugApply.class));
|
||||
sql.setCallback(Sqls.callback.entities());
|
||||
dao.execute(sql);
|
||||
List<SiteCugApply> applyList = sql.getList(SiteCugApply.class);
|
||||
return applyList == null ? new ArrayList<>() : applyList;
|
||||
}
|
||||
|
||||
private Set<String> queryHolidaySet() {
|
||||
return dao.query(SysHoliday.class, Cnd.NEW()).stream()
|
||||
.map(SysHoliday::getDay)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private String getReserveTimeTypeName(Integer reserveTimeType) {
|
||||
if (reserveTimeType != null && reserveTimeType == 2) {
|
||||
return "\u5168\u5929\u5019\u9884\u7ea6";
|
||||
}
|
||||
return "\u5206\u6bb5\u9884\u7ea6";
|
||||
}
|
||||
|
||||
private List<NutMap> buildTimelineSegments(NutMap site, List<SiteCugApply> applyList, String today, Set<String> holidaySet) {
|
||||
List<NutMap> segments = new ArrayList<>();
|
||||
boolean isWeekend = isWeekend(today);
|
||||
boolean isHoliday = holidaySet.contains(today);
|
||||
List<int[]> openRanges = buildOpenRanges(site, isWeekend, isHoliday);
|
||||
List<int[]> disabledRanges = buildDisabledRanges(site, today);
|
||||
List<int[]> reservedRanges = buildReservedRanges(applyList, today);
|
||||
int totalMinutes = 24 * 60;
|
||||
int segmentMinutes = 30;
|
||||
for (int start = 0; start < totalMinutes; start += segmentMinutes) {
|
||||
int end = start + segmentMinutes;
|
||||
String status = "closed";
|
||||
if (intersectsAny(start, end, openRanges)) {
|
||||
status = "available";
|
||||
}
|
||||
if (intersectsAny(start, end, disabledRanges)) {
|
||||
status = "closed";
|
||||
}
|
||||
if (!"closed".equals(status) && intersectsAny(start, end, reservedRanges)) {
|
||||
status = "reserved";
|
||||
}
|
||||
segments.add(NutMap.NEW()
|
||||
.addv("status", status)
|
||||
.addv("startMinute", start)
|
||||
.addv("endMinute", end));
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private List<int[]> buildOpenRanges(NutMap site, boolean isWeekend, boolean isHoliday) {
|
||||
if (isWeekend) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
if (Boolean.TRUE.equals(site.getBoolean("filterHolidays")) && isHoliday) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Integer reserveTimeType = site.getInt("reserveTimeType");
|
||||
if (reserveTimeType != null && reserveTimeType == 2) {
|
||||
NutMap fullDayOpenHour = toNutMap(site.get("fullDayOpenHour"));
|
||||
if (fullDayOpenHour.isEmpty()) {
|
||||
List<NutMap> openHours = toNutMapList(site.get("openHours"));
|
||||
fullDayOpenHour = openHours.isEmpty() ? NutMap.NEW() : openHours.get(0);
|
||||
}
|
||||
return parseTimeRanges(List.of(fullDayOpenHour));
|
||||
}
|
||||
List<NutMap> segmentedOpenHours = toNutMapList(site.get("segmentedOpenHours"));
|
||||
if (segmentedOpenHours.isEmpty()) {
|
||||
segmentedOpenHours = toNutMapList(site.get("openHours"));
|
||||
}
|
||||
return parseTimeRanges(segmentedOpenHours);
|
||||
}
|
||||
|
||||
private List<int[]> buildDisabledRanges(NutMap site, String today) {
|
||||
List<NutMap> disabledList = toNutMapList(site.get("notApplyTimeList"));
|
||||
List<int[]> result = new ArrayList<>();
|
||||
for (NutMap item : disabledList) {
|
||||
if (item == null || !StrUtil.equals(today, item.getString("date"))) {
|
||||
continue;
|
||||
}
|
||||
Integer start = parseTimeToMinutes(item.getString("startTime"));
|
||||
Integer end = parseTimeToMinutes(item.getString("endTime"));
|
||||
if (start != null && end != null && end > start) {
|
||||
result.add(new int[]{start, end});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<int[]> buildReservedRanges(List<SiteCugApply> applyList, String today) {
|
||||
List<int[]> result = new ArrayList<>();
|
||||
String dayStart = today + " 00:00:00";
|
||||
String dayEnd = today + " 23:59:59";
|
||||
DateTime startOfDay = DateUtil.parseDateTime(dayStart);
|
||||
DateTime endOfDay = DateUtil.parseDateTime(dayEnd);
|
||||
for (SiteCugApply item : applyList) {
|
||||
if (item == null || StrUtil.hasBlank(item.getReserveStartTime(), item.getReserveEndTime())) {
|
||||
continue;
|
||||
}
|
||||
DateTime reserveStart = DateUtil.parseDateTime(item.getReserveStartTime());
|
||||
DateTime reserveEnd = DateUtil.parseDateTime(item.getReserveEndTime());
|
||||
if (!reserveEnd.isAfter(reserveStart)) {
|
||||
continue;
|
||||
}
|
||||
DateTime actualStart = reserveStart.isBefore(startOfDay) ? startOfDay : reserveStart;
|
||||
DateTime actualEnd = reserveEnd.isAfter(endOfDay) ? endOfDay : reserveEnd;
|
||||
int startMinute = actualStart.hour(true) * 60 + actualStart.minute();
|
||||
int endMinute = actualEnd.hour(true) * 60 + actualEnd.minute();
|
||||
if (actualEnd.second() > 0) {
|
||||
endMinute = Math.min(24 * 60, endMinute + 1);
|
||||
}
|
||||
if (endMinute > startMinute) {
|
||||
result.add(new int[]{startMinute, endMinute});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<int[]> parseTimeRanges(List<NutMap> sourceList) {
|
||||
List<int[]> result = new ArrayList<>();
|
||||
for (NutMap item : sourceList) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
Integer start = parseTimeToMinutes(item.getString("startTime"));
|
||||
Integer end = parseTimeToMinutes(item.getString("endTime"));
|
||||
if (start != null && end != null && end > start) {
|
||||
result.add(new int[]{start, end});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NutMap toNutMap(Object value) {
|
||||
if (value instanceof NutMap nutMap) {
|
||||
return nutMap;
|
||||
}
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
NutMap nutMap = NutMap.NEW();
|
||||
map.forEach((key, val) -> nutMap.put(String.valueOf(key), val));
|
||||
return nutMap;
|
||||
}
|
||||
return NutMap.NEW();
|
||||
}
|
||||
|
||||
private List<NutMap> toNutMapList(Object value) {
|
||||
List<NutMap> result = new ArrayList<>();
|
||||
if (!(value instanceof List<?> list)) {
|
||||
return result;
|
||||
}
|
||||
for (Object item : list) {
|
||||
result.add(toNutMap(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Integer parseTimeToMinutes(String timeText) {
|
||||
if (StrUtil.isBlank(timeText)) {
|
||||
return null;
|
||||
}
|
||||
List<String> parts = Arrays.stream(timeText.split(":"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.toList();
|
||||
if (parts.size() < 2) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
int hour = Integer.parseInt(parts.get(0));
|
||||
int minute = Integer.parseInt(parts.get(1));
|
||||
if (hour < 0 || hour > 24 || minute < 0 || minute > 59) {
|
||||
return null;
|
||||
}
|
||||
if (hour == 24 && minute > 0) {
|
||||
return null;
|
||||
}
|
||||
return hour * 60 + minute;
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean intersectsAny(int start, int end, List<int[]> ranges) {
|
||||
for (int[] range : ranges) {
|
||||
if (range != null && start < range[1] && end > range[0]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isWeekend(String dateText) {
|
||||
DateTime date = DateUtil.parseDate(dateText);
|
||||
int week = date.dayOfWeek() - 1;
|
||||
return week == 0 || week == 6;
|
||||
}
|
||||
|
||||
private List<NutMap> buildAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
|
||||
if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) {
|
||||
return buildFullDayAvailabilityBlocks(siteInfo, day, applyList, holidaySet);
|
||||
}
|
||||
return buildSegmentedAvailabilityBlocks(siteInfo, day, applyList, holidaySet);
|
||||
}
|
||||
|
||||
private List<NutMap> buildSegmentedAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
|
||||
List<NutMap> sourceList = siteInfo.getSegmentedOpenHours();
|
||||
if (sourceList == null || sourceList.isEmpty()) {
|
||||
sourceList = siteInfo.getOpenHours();
|
||||
}
|
||||
List<NutMap> result = new ArrayList<>();
|
||||
List<int[]> reservedRanges = buildReservedRanges(applyList, day);
|
||||
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
|
||||
boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet);
|
||||
for (int i = 0; i < sourceList.size(); i++) {
|
||||
NutMap item = toNutMap(sourceList.get(i));
|
||||
Integer slotStart = parseTimeToMinutes(item.getString("startTime"));
|
||||
Integer slotEnd = parseTimeToMinutes(item.getString("endTime"));
|
||||
int unitMinutes = parseTimeUnitMinutes(item.get("timeUnit"));
|
||||
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
|
||||
continue;
|
||||
}
|
||||
String groupLabel = "\u573a\u6b21" + (i + 1) + " " + formatMinutes(slotStart) + "-" + formatMinutes(slotEnd) + " / " + formatUnitText(unitMinutes);
|
||||
for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) {
|
||||
int blockEnd = cursor + unitMinutes;
|
||||
result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, i, groupLabel));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<NutMap> buildFullDayAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
|
||||
NutMap source = getFullDayOpenHourConfig(siteInfo);
|
||||
Integer slotStart = parseTimeToMinutes(source.getString("startTime"));
|
||||
Integer slotEnd = parseTimeToMinutes(source.getString("endTime"));
|
||||
int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit"));
|
||||
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<NutMap> result = new ArrayList<>();
|
||||
List<int[]> reservedRanges = buildReservedRanges(applyList, day);
|
||||
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
|
||||
boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet);
|
||||
String groupLabel = "\u5168\u5929\u5019\u65f6\u6bb5 / " + formatUnitText(unitMinutes);
|
||||
for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) {
|
||||
int blockEnd = cursor + unitMinutes;
|
||||
result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, 0, groupLabel));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NutMap buildAvailabilityBlock(String day, int startMinute, int endMinute, List<int[]> reservedRanges, List<int[]> disabledRanges, boolean siteClosed, int groupIndex, String groupLabel) {
|
||||
String status = "available";
|
||||
if (siteClosed || intersectsAny(startMinute, endMinute, disabledRanges)) {
|
||||
status = "closed";
|
||||
} else if (intersectsAny(startMinute, endMinute, reservedRanges)) {
|
||||
status = "reserved";
|
||||
}
|
||||
return NutMap.NEW()
|
||||
.addv("key", day + "_" + startMinute + "_" + endMinute)
|
||||
.addv("groupIndex", groupIndex)
|
||||
.addv("groupLabel", groupLabel)
|
||||
.addv("status", status)
|
||||
.addv("startMinute", startMinute)
|
||||
.addv("endMinute", endMinute)
|
||||
.addv("startTime", formatMinutes(startMinute))
|
||||
.addv("endTime", formatMinutes(endMinute))
|
||||
.addv("label", formatMinutes(startMinute) + "-" + formatMinutes(endMinute))
|
||||
.addv("startDateTime", day + " " + formatMinutes(startMinute) + ":00")
|
||||
.addv("endDateTime", day + " " + formatMinutes(endMinute) + ":00");
|
||||
}
|
||||
|
||||
private boolean isSiteClosed(SiteCugInfo siteInfo, String day, Set<String> holidaySet) {
|
||||
if (isWeekend(day)) {
|
||||
return true;
|
||||
}
|
||||
return Boolean.TRUE.equals(siteInfo.getFilterHolidays()) && holidaySet.contains(day);
|
||||
}
|
||||
|
||||
private NutMap toSiteNutMap(SiteCugInfo siteInfo) {
|
||||
return NutMap.NEW()
|
||||
.addv("notApplyTimeList", siteInfo.getNotApplyTimeList())
|
||||
.addv("filterHolidays", siteInfo.getFilterHolidays())
|
||||
.addv("reserveTimeType", siteInfo.getReserveTimeType())
|
||||
.addv("openHours", siteInfo.getOpenHours())
|
||||
.addv("segmentedOpenHours", siteInfo.getSegmentedOpenHours())
|
||||
.addv("fullDayOpenHour", siteInfo.getFullDayOpenHour());
|
||||
}
|
||||
|
||||
private NutMap getFullDayOpenHourConfig(SiteCugInfo siteInfo) {
|
||||
NutMap source = toNutMap(siteInfo.getFullDayOpenHour());
|
||||
if (!source.isEmpty()) {
|
||||
return source;
|
||||
}
|
||||
List<NutMap> openHours = siteInfo.getOpenHours();
|
||||
return openHours == null || openHours.isEmpty() ? NutMap.NEW() : toNutMap(openHours.get(0));
|
||||
}
|
||||
|
||||
private int parseTimeUnitMinutes(Object value) {
|
||||
if (value == null) {
|
||||
return 60;
|
||||
}
|
||||
try {
|
||||
double unit = Double.parseDouble(String.valueOf(value));
|
||||
if (unit <= 0) {
|
||||
return 60;
|
||||
}
|
||||
return (int) Math.round(unit);
|
||||
} catch (NumberFormatException e) {
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
private String formatMinutes(int totalMinutes) {
|
||||
int hour = totalMinutes / 60;
|
||||
int minute = totalMinutes % 60;
|
||||
return String.format("%02d:%02d", hour, minute);
|
||||
}
|
||||
|
||||
private String formatUnitText(int unitMinutes) {
|
||||
return unitMinutes + "\u5206\u949f";
|
||||
}
|
||||
|
||||
private Result validateApplySelectionBySchedule(SiteCugApply apply) {
|
||||
if (apply == null || StrUtil.hasBlank(apply.getSiteId(), apply.getReserveStartTime(), apply.getReserveEndTime())) {
|
||||
return Result.success();
|
||||
}
|
||||
SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId());
|
||||
if (siteInfo == null) {
|
||||
return Result.error("\u573a\u5730\u4e0d\u5b58\u5728");
|
||||
}
|
||||
DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime());
|
||||
DateTime end = DateUtil.parseDateTime(apply.getReserveEndTime());
|
||||
String startDay = DateUtil.formatDate(start);
|
||||
String endDay = DateUtil.formatDate(end);
|
||||
if (!StrUtil.equals(startDay, endDay)) {
|
||||
return Result.error("\u9884\u7ea6\u65f6\u95f4\u5fc5\u987b\u5728\u540c\u4e00\u5929\u5185");
|
||||
}
|
||||
Set<String> holidaySet = queryHolidaySet();
|
||||
List<SiteCugApply> applyList = queryDayApplyList(apply.getSiteId(), startDay + " 00:00:00", startDay + " 23:59:59").stream()
|
||||
.filter(item -> item != null && !StrUtil.equals(item.getId(), apply.getId()))
|
||||
.collect(Collectors.toList());
|
||||
if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) {
|
||||
if (!isWithinOpenRanges(siteInfo, startDay, start, end, holidaySet)) {
|
||||
return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u8bf7\u5728\u5f53\u5929\u5f00\u653e\u65f6\u95f4\u5185\u6309\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5");
|
||||
}
|
||||
if (!isAlignedWithFullDayTimeUnit(siteInfo, start, end)) {
|
||||
return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u7684\u5f00\u59cb\u548c\u7ed3\u675f\u65f6\u95f4\u5fc5\u987b\u7b26\u5408\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d");
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
List<NutMap> blocks = buildSegmentedAvailabilityBlocks(siteInfo, startDay, applyList, holidaySet);
|
||||
String startText = apply.getReserveStartTime();
|
||||
String endText = apply.getReserveEndTime();
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
NutMap block = blocks.get(i);
|
||||
if (!"available".equals(block.getString("status"))) {
|
||||
continue;
|
||||
}
|
||||
if (!StrUtil.equals(startText, block.getString("startDateTime"))) {
|
||||
continue;
|
||||
}
|
||||
String currentEnd = block.getString("endDateTime");
|
||||
if (StrUtil.equals(endText, currentEnd)) {
|
||||
return Result.success();
|
||||
}
|
||||
for (int j = i + 1; j < blocks.size(); j++) {
|
||||
NutMap nextBlock = blocks.get(j);
|
||||
NutMap prevBlock = blocks.get(j - 1);
|
||||
if (!"available".equals(nextBlock.getString("status"))) {
|
||||
break;
|
||||
}
|
||||
if (!StrUtil.equals(prevBlock.getString("endDateTime"), nextBlock.getString("startDateTime"))) {
|
||||
break;
|
||||
}
|
||||
currentEnd = nextBlock.getString("endDateTime");
|
||||
if (StrUtil.equals(endText, currentEnd)) {
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.error("\u5206\u6bb5\u9884\u7ea6\u8bf7\u6309\u573a\u6b21\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5");
|
||||
}
|
||||
|
||||
private boolean isWithinOpenRanges(SiteCugInfo siteInfo, String day, DateTime start, DateTime end, Set<String> holidaySet) {
|
||||
if (isSiteClosed(siteInfo, day, holidaySet)) {
|
||||
return false;
|
||||
}
|
||||
List<int[]> openRanges = buildOpenRanges(toSiteNutMap(siteInfo), false, false);
|
||||
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
|
||||
int startMinute = start.hour(true) * 60 + start.minute();
|
||||
int endMinute = end.hour(true) * 60 + end.minute();
|
||||
if (end.second() > 0) {
|
||||
endMinute = Math.min(24 * 60, endMinute + 1);
|
||||
}
|
||||
final int finalEndMinute = endMinute;
|
||||
boolean inOpenRange = openRanges.stream().anyMatch(range -> startMinute >= range[0] && finalEndMinute <= range[1]);
|
||||
return inOpenRange && !intersectsAny(startMinute, finalEndMinute, disabledRanges);
|
||||
}
|
||||
|
||||
private boolean isAlignedWithFullDayTimeUnit(SiteCugInfo siteInfo, DateTime start, DateTime end) {
|
||||
if (start == null || end == null || start.second() > 0 || end.second() > 0) {
|
||||
return false;
|
||||
}
|
||||
NutMap source = getFullDayOpenHourConfig(siteInfo);
|
||||
Integer slotStart = parseTimeToMinutes(source.getString("startTime"));
|
||||
Integer slotEnd = parseTimeToMinutes(source.getString("endTime"));
|
||||
int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit"));
|
||||
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
|
||||
return false;
|
||||
}
|
||||
int startMinute = start.hour(true) * 60 + start.minute();
|
||||
int endMinute = end.hour(true) * 60 + end.minute();
|
||||
return startMinute >= slotStart
|
||||
&& endMinute <= slotEnd
|
||||
&& endMinute > startMinute
|
||||
&& (startMinute - slotStart) % unitMinutes == 0
|
||||
&& (endMinute - slotStart) % unitMinutes == 0;
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("\u63d0\u4ea4\u9884\u7ea6")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u63d0\u4ea4\u573a\u5730\u9884\u7ea6")
|
||||
public Result submit(@Param("data") SiteCugApply apply) {
|
||||
Result scheduleValidate = validateApplySelectionBySchedule(apply);
|
||||
if (scheduleValidate.getCode() != 0) {
|
||||
return scheduleValidate;
|
||||
}
|
||||
Map<Boolean, String> validate = applyService.validateApply(apply);
|
||||
if (validate.containsKey(false)) {
|
||||
return Result.error(validate.get(false));
|
||||
@@ -122,19 +644,23 @@ public class SiteCugApplyController {
|
||||
}
|
||||
|
||||
@At("/submitYearly")
|
||||
@ApiOperation("按本年后续同星期同时间段批量提交预约")
|
||||
@ApiOperation("\u6309\u672c\u5e74\u540e\u7eed\u540c\u661f\u671f\u540c\u65f6\u95f4\u6bb5\u6279\u91cf\u63d0\u4ea4\u9884\u7ea6")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "场馆功能管理-场地预约", msg = "批量提交本年场地预约")
|
||||
@SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u6279\u91cf\u63d0\u4ea4\u672c\u5e74\u573a\u5730\u9884\u7ea6")
|
||||
public Result submitYearly(@Param("data") SiteCugApply apply) {
|
||||
List<SiteCugApply> applyList = buildYearlyApplyList(apply);
|
||||
if (applyList.isEmpty()) {
|
||||
return Result.error("未生成可预约的日期");
|
||||
return Result.error("\u672a\u751f\u6210\u53ef\u9884\u7ea6\u7684\u65e5\u671f");
|
||||
}
|
||||
for (SiteCugApply item : applyList) {
|
||||
Result scheduleValidate = validateApplySelectionBySchedule(item);
|
||||
if (scheduleValidate.getCode() != 0) {
|
||||
return scheduleValidate;
|
||||
}
|
||||
Map<Boolean, String> validate = applyService.validateApply(item);
|
||||
if (validate.containsKey(false)) {
|
||||
return Result.error(String.format("%s 预约失败:%s", item.getReserveStartTime(), validate.get(false)));
|
||||
return Result.error(String.format("%s \u9884\u7ea6\u5931\u8d25\uff1a%s", item.getReserveStartTime(), validate.get(false)));
|
||||
}
|
||||
}
|
||||
String yearlyBatchNo = YEARLY_BATCH_PREFIX + UUID.randomUUID();
|
||||
@@ -142,11 +668,11 @@ public class SiteCugApplyController {
|
||||
item.setBackOption(yearlyBatchNo);
|
||||
submitSingleApply(item);
|
||||
}
|
||||
return Result.success(String.format("已成功预约本年剩余%d个时间段", applyList.size()));
|
||||
return Result.success(String.format("\u5df2\u6210\u529f\u9884\u7ea6\u622a\u81f3%s\u5171%d\u4e2a\u65f6\u95f4\u6bb5", apply.getYearlyReserveEndDate(), applyList.size()));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询预约限制配置")
|
||||
@ApiOperation("\u67e5\u8be2\u9884\u7ea6\u9650\u5236\u914d\u7f6e")
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
public Result timeLimitConfig(@Param("siteId") String siteId) {
|
||||
if (StrUtil.isBlank(siteId)) {
|
||||
@@ -174,7 +700,7 @@ public class SiteCugApplyController {
|
||||
}
|
||||
|
||||
private List<SiteCugApply> buildYearlyApplyList(SiteCugApply apply) {
|
||||
if (apply == null || StrUtil.hasBlank(apply.getReserveStartTime(), apply.getReserveEndTime())) {
|
||||
if (apply == null || StrUtil.hasBlank(apply.getReserveStartTime(), apply.getReserveEndTime(), apply.getYearlyReserveEndDate())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime());
|
||||
@@ -182,6 +708,10 @@ public class SiteCugApplyController {
|
||||
if (!end.isAfter(start)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
DateTime reserveEndDate = parseReserveEndDate(apply.getYearlyReserveEndDate());
|
||||
if (reserveEndDate == null || reserveEndDate.isBefore(DateUtil.beginOfDay(start))) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId());
|
||||
if (siteInfo == null) {
|
||||
return new ArrayList<>();
|
||||
@@ -205,9 +735,9 @@ public class SiteCugApplyController {
|
||||
int endMinute = end.minute();
|
||||
int endSecond = end.second();
|
||||
Date current = DateUtil.beginOfDay(start);
|
||||
Date endOfYear = DateUtil.endOfYear(start);
|
||||
Date batchEndDate = DateUtil.endOfDay(reserveEndDate);
|
||||
List<SiteCugApply> result = new ArrayList<>();
|
||||
while (current.compareTo(endOfYear) <= 0) {
|
||||
while (current.compareTo(batchEndDate) <= 0) {
|
||||
DateTime currentDate = DateUtil.date(current);
|
||||
String currentDay = DateUtil.formatDate(currentDate);
|
||||
if (currentDate.dayOfWeek() - 1 == targetWeek && !holidayList.contains(currentDay)) {
|
||||
@@ -222,6 +752,17 @@ public class SiteCugApplyController {
|
||||
return result;
|
||||
}
|
||||
|
||||
private DateTime parseReserveEndDate(String endDate) {
|
||||
if (StrUtil.isBlank(endDate)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateUtil.parseDate(endDate);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime buildDateTime(DateTime date, int hour, int minute, int second) {
|
||||
return DateUtil.parseDateTime(String.format("%s %02d:%02d:%02d", DateUtil.formatDate(date), hour, minute, second));
|
||||
}
|
||||
@@ -245,7 +786,8 @@ public class SiteCugApplyController {
|
||||
.setReserveEndTime(apply.getReserveEndTime())
|
||||
.setJoinCount(apply.getJoinCount())
|
||||
.setApplyCause(apply.getApplyCause())
|
||||
.setBackOption(apply.getBackOption());
|
||||
.setBackOption(apply.getBackOption())
|
||||
.setYearlyReserveEndDate(apply.getYearlyReserveEndDate());
|
||||
}
|
||||
|
||||
private void submitSingleApply(SiteCugApply apply) {
|
||||
@@ -261,4 +803,4 @@ public class SiteCugApplyController {
|
||||
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-10
@@ -27,12 +27,13 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api(tags = "场地管理-场馆")
|
||||
@Api(tags = "siteCug manage")
|
||||
@At("/platform/siteCug/manage")
|
||||
public class SiteCugManageController {
|
||||
|
||||
@@ -47,7 +48,7 @@ public class SiteCugManageController {
|
||||
public void index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@ApiOperation("page data")
|
||||
@SaCheckPermission("siteCug.manage")
|
||||
public Result pageData(PageForm pageForm, @Param("type") String type) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
@@ -76,9 +77,9 @@ public class SiteCugManageController {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增/修改场地")
|
||||
@ApiOperation("submit")
|
||||
@SaCheckPermission("siteCug.manage")
|
||||
@SLog(tag = "场馆功能管理-场地管理", msg = "新增/修改场地")
|
||||
@SLog(tag = "siteCug.manage", msg = "submit site")
|
||||
public Object submit(@Param("data") SiteCugInfo info) {
|
||||
List<NutMap> notApplyTimeList = info.getNotApplyTimeList();
|
||||
if (notApplyTimeList != null) {
|
||||
@@ -89,27 +90,81 @@ public class SiteCugManageController {
|
||||
|| StrUtil.isBlank(item.getString("endTime"))
|
||||
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0);
|
||||
if (invalid) {
|
||||
return Result.error("禁用时间设置有误");
|
||||
return Result.error("Invalid disabled time settings");
|
||||
}
|
||||
}
|
||||
|
||||
if (info.getReserveTimeType() == null) {
|
||||
info.setReserveTimeType(1);
|
||||
}
|
||||
List<NutMap> segmentedOpenHours = info.getSegmentedOpenHours();
|
||||
NutMap fullDayOpenHour = info.getFullDayOpenHour();
|
||||
if ((segmentedOpenHours == null || segmentedOpenHours.isEmpty()) && info.getReserveTimeType() == 1) {
|
||||
segmentedOpenHours = info.getOpenHours();
|
||||
}
|
||||
if ((fullDayOpenHour == null || fullDayOpenHour.isEmpty()) && info.getReserveTimeType() == 2 && info.getOpenHours() != null && !info.getOpenHours().isEmpty()) {
|
||||
fullDayOpenHour = info.getOpenHours().get(0);
|
||||
}
|
||||
if (info.getReserveTimeType() == 1) {
|
||||
if (segmentedOpenHours == null || segmentedOpenHours.isEmpty()) {
|
||||
return Result.error("Please add at least one booking slot");
|
||||
}
|
||||
boolean invalid = segmentedOpenHours.stream().anyMatch(item ->
|
||||
item == null
|
||||
|| StrUtil.isBlank(item.getString("startTime"))
|
||||
|| StrUtil.isBlank(item.getString("endTime"))
|
||||
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0
|
||||
|| !isPositiveNumber(item.get("timeUnit")));
|
||||
if (invalid) {
|
||||
return Result.error("Invalid segmented booking slot settings");
|
||||
}
|
||||
if (hasOverlap(segmentedOpenHours)) {
|
||||
return Result.error("Segmented booking slots cannot overlap");
|
||||
}
|
||||
segmentedOpenHours = segmentedOpenHours.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(this::normalizeOpenHourTimeUnit)
|
||||
.collect(Collectors.toList());
|
||||
info.setSegmentedOpenHours(segmentedOpenHours);
|
||||
info.setOpenHours(segmentedOpenHours);
|
||||
} else if (info.getReserveTimeType() == 2) {
|
||||
if (fullDayOpenHour == null || fullDayOpenHour.isEmpty()) {
|
||||
return Result.error("Please set the full-day booking time range");
|
||||
}
|
||||
NutMap item = fullDayOpenHour;
|
||||
if (item == null
|
||||
|| StrUtil.isBlank(item.getString("startTime"))
|
||||
|| StrUtil.isBlank(item.getString("endTime"))
|
||||
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0) {
|
||||
return Result.error("Invalid full-day booking time range");
|
||||
}
|
||||
if (!isPositiveNumber(item.get("timeUnit"))) {
|
||||
return Result.error("Invalid full-day booking time unit");
|
||||
}
|
||||
fullDayOpenHour = normalizeOpenHourTimeUnit(fullDayOpenHour);
|
||||
info.setFullDayOpenHour(fullDayOpenHour);
|
||||
info.setOpenHours(List.of(fullDayOpenHour));
|
||||
}
|
||||
|
||||
if (info.getFilterHolidays() == null) {
|
||||
info.setFilterHolidays(false);
|
||||
}
|
||||
infoService.insertOrUpdate(info);
|
||||
return Result.success();
|
||||
SiteCugInfo savedInfo = infoService.fetch(info.getId());
|
||||
return Result.success(savedInfo == null ? info : savedInfo);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除场地")
|
||||
@ApiOperation("delete")
|
||||
@SaCheckPermission("siteCug.manage")
|
||||
@SLog(tag = "场馆功能管理-场地管理", msg = "删除场地")
|
||||
@SLog(tag = "siteCug.manage", msg = "delete site")
|
||||
public Object delete(String id) {
|
||||
infoService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询场地")
|
||||
@ApiOperation("query sites")
|
||||
@SaCheckLogin
|
||||
public Result querySites() {
|
||||
List<SiteCugInfo> list = infoService.query(Cnd.where(SiteCugInfo::getState, "=", true).desc(SiteCugInfo::getSortNum));
|
||||
@@ -117,7 +172,7 @@ public class SiteCugManageController {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询单个场地")
|
||||
@ApiOperation("query site info")
|
||||
@SaCheckLogin
|
||||
public Result info(String id) {
|
||||
SiteCugInfo info = infoService.fetch(id);
|
||||
@@ -129,4 +184,59 @@ public class SiteCugManageController {
|
||||
map.put("typeName", type == null ? "" : type.getName());
|
||||
return Result.success(map);
|
||||
}
|
||||
|
||||
private boolean isPositiveNumber(Object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(String.valueOf(value)) > 0;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private NutMap normalizeOpenHourTimeUnit(NutMap item) {
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
item.put("timeUnit", normalizeTimeUnitMinutes(item.get("timeUnit")));
|
||||
return item;
|
||||
}
|
||||
|
||||
private int normalizeTimeUnitMinutes(Object value) {
|
||||
if (value == null) {
|
||||
return 60;
|
||||
}
|
||||
try {
|
||||
double parsed = Double.parseDouble(String.valueOf(value));
|
||||
if (parsed <= 0) {
|
||||
return 60;
|
||||
}
|
||||
return (int) Math.round(parsed);
|
||||
} catch (Exception e) {
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasOverlap(List<NutMap> openHours) {
|
||||
if (openHours == null || openHours.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
List<NutMap> validHours = openHours.stream()
|
||||
.filter(item -> item != null
|
||||
&& StrUtil.isNotBlank(item.getString("startTime"))
|
||||
&& StrUtil.isNotBlank(item.getString("endTime"))
|
||||
&& item.getString("startTime").compareTo(item.getString("endTime")) < 0)
|
||||
.sorted((a, b) -> a.getString("startTime").compareTo(b.getString("startTime")))
|
||||
.collect(Collectors.toList());
|
||||
for (int i = 1; i < validHours.size(); i++) {
|
||||
NutMap prev = validHours.get(i - 1);
|
||||
NutMap current = validHours.get(i);
|
||||
if (current.getString("startTime").compareTo(prev.getString("endTime")) < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -81,6 +81,7 @@ public class SiteCugMineController {
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
|
||||
CASE
|
||||
WHEN ins.state in (10, 20) AND (t.displayName LIKE '%校工会允许%' OR t.taskName = 'c8c03407-cf26-4e0b-82f1-b2afc9c79996')
|
||||
THEN 1
|
||||
@@ -105,7 +106,6 @@ public class SiteCugMineController {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("si.typeId", "=", siteType);
|
||||
|
||||
+32
-2
@@ -75,6 +75,7 @@ public class SiteCugSchoolUnionAuditController {
|
||||
SELECT
|
||||
info.*,
|
||||
si.name as siteName,
|
||||
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
@@ -113,7 +114,6 @@ public class SiteCugSchoolUnionAuditController {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("si.typeId", "=", siteType);
|
||||
@@ -129,7 +129,7 @@ public class SiteCugSchoolUnionAuditController {
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
cnd.orderBy(resolveOrderColumn(pageForm.getPageOrderName()), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
} else {
|
||||
cnd.desc("info.createdAt");
|
||||
}
|
||||
@@ -139,6 +139,36 @@ public class SiteCugSchoolUnionAuditController {
|
||||
return Result.success(pageVO);
|
||||
}
|
||||
|
||||
private String resolveOrderColumn(String pageOrderName) {
|
||||
if (StrUtil.isBlank(pageOrderName)) {
|
||||
return "info.createdAt";
|
||||
}
|
||||
switch (pageOrderName) {
|
||||
case "siteName":
|
||||
return "si.name";
|
||||
case "applyUserName":
|
||||
return "info.applyUserName";
|
||||
case "applyLoginName":
|
||||
return "info.applyLoginName";
|
||||
case "yearlyBatch":
|
||||
return "yearlyBatch";
|
||||
case "reserveTargetName":
|
||||
return "reserveTargetName";
|
||||
case "reserveStartTime":
|
||||
return "info.reserveStartTime";
|
||||
case "reserveEndTime":
|
||||
return "info.reserveEndTime";
|
||||
case "applyMobile":
|
||||
return "info.applyMobile";
|
||||
case "curTaskName":
|
||||
return "curTaskName";
|
||||
case "instanceState":
|
||||
return "ins.state";
|
||||
default:
|
||||
return "info.createdAt";
|
||||
}
|
||||
}
|
||||
|
||||
@At("/executeTask")
|
||||
@ApiOperation("执行审核任务")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
|
||||
@@ -109,4 +109,6 @@ public class SiteCugApply extends BaseModel {
|
||||
@Comment("反馈意见")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 200)
|
||||
private String backOption;
|
||||
|
||||
private String yearlyReserveEndDate;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,27 @@ public class SiteCugInfo extends BaseModel {
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<NutMap> notApplyTimeList;
|
||||
|
||||
@Column
|
||||
@Comment("预约时间段类型(1分段预约,2全天候预约)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("1")
|
||||
private Integer reserveTimeType;
|
||||
|
||||
@Column
|
||||
@Comment("场次信息")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<NutMap> openHours;
|
||||
|
||||
@Column
|
||||
@Comment("分段预约场次信息")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<NutMap> segmentedOpenHours;
|
||||
|
||||
@Column
|
||||
@Comment("全天候预约时间段")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private NutMap fullDayOpenHour;
|
||||
|
||||
@Column
|
||||
@Comment("开启状态")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@@ -94,4 +115,9 @@ public class SiteCugInfo extends BaseModel {
|
||||
@Comment("场地介绍")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String introduce;
|
||||
}
|
||||
|
||||
@Column
|
||||
@Comment("场地照片")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||
private String sitePhoto;
|
||||
}
|
||||
|
||||
+1
-1
@@ -136,6 +136,7 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
si.name AS siteName,
|
||||
ins.id AS instanceId,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch,
|
||||
CASE WHEN info.reserveType = 'club' THEN '协会预约' ELSE '分工会预约' END AS reserveTypeName,
|
||||
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
|
||||
DATE_FORMAT(info.createdAt, '%Y-%m-%d %H:%i:%s') AS applyTime
|
||||
@@ -155,7 +156,6 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getReserveTargetKeyword())) {
|
||||
|
||||
+10
-8
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.staffmanage.member.controller.statistics;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
@@ -39,14 +40,12 @@ public class MemberAnalysisController {
|
||||
|
||||
@At
|
||||
@SaCheckPermission("member.statistics.analysis")
|
||||
public Result pageData(@Param(value = "queryId") String queryId,
|
||||
@Param(value = "queryType") String queryType,
|
||||
@Param(value = "currentYear") Integer currentYear) {
|
||||
public Result pageData(@Param("..") MemberStatisticsPageForm pageForm) {
|
||||
List<NutMap> list = new ArrayList<>();
|
||||
if ("fgh".equals(queryType)) {
|
||||
list = memberStatisticsService.getUnionAnalysisData(queryId, currentYear);
|
||||
if ("fgh".equals(pageForm.getQueryType())) {
|
||||
list = memberStatisticsService.getUnionAnalysisData(pageForm);
|
||||
} else {
|
||||
list = memberStatisticsService.getUnitAnalysisData(queryId, currentYear);
|
||||
list = memberStatisticsService.getUnitAnalysisData(pageForm);
|
||||
}
|
||||
return Result.success(list);
|
||||
}
|
||||
@@ -58,10 +57,13 @@ public class MemberAnalysisController {
|
||||
public void doExport(@Param("queryType") String queryType, @Param("currentYear") String currentYear,
|
||||
@Param("queryId") String queryId, HttpServletResponse response) {
|
||||
List<NutMap> list = new ArrayList<>();
|
||||
MemberStatisticsPageForm pageForm = new MemberStatisticsPageForm();
|
||||
pageForm.setQueryId(queryId);
|
||||
pageForm.setCurrentYear(Integer.valueOf(currentYear));
|
||||
if ("union".equals(queryType)) {
|
||||
list = memberStatisticsService.getUnionAnalysisData(queryId, Integer.valueOf(currentYear));
|
||||
list = memberStatisticsService.getUnionAnalysisData(pageForm);
|
||||
} else {
|
||||
list = memberStatisticsService.getUnitAnalysisData(queryId, Integer.valueOf(currentYear));
|
||||
list = memberStatisticsService.getUnitAnalysisData(pageForm);
|
||||
}
|
||||
NutMap sumMap = NutMap.NEW();
|
||||
sumMap.put("union".equals(queryType) ? "unionName" : "unitName", "合计");
|
||||
|
||||
+16
-2
@@ -40,7 +40,14 @@ public interface MemberStatisticsService extends BaseService<Sys_user> {
|
||||
* @param currentYear 所属年度
|
||||
* @return
|
||||
*/
|
||||
List<NutMap> getUnionAnalysisData(String unionId, Integer currentYear);
|
||||
/**
|
||||
* 获取工会分析数据。
|
||||
* pageForm 中除了机构和年份外,还会带上人员分类、人员属性等附加筛选条件。
|
||||
*
|
||||
* @param pageForm 统计查询参数
|
||||
* @return 工会分析结果
|
||||
*/
|
||||
List<NutMap> getUnionAnalysisData(MemberStatisticsPageForm pageForm);
|
||||
|
||||
|
||||
/**
|
||||
@@ -49,7 +56,14 @@ public interface MemberStatisticsService extends BaseService<Sys_user> {
|
||||
* @param currentYear 所属年度
|
||||
* @return
|
||||
*/
|
||||
List<NutMap> getUnitAnalysisData(String unitId, Integer currentYear);
|
||||
/**
|
||||
* 获取单位分析数据。
|
||||
* pageForm 中除了机构和年份外,还会带上人员分类、人员属性等附加筛选条件。
|
||||
*
|
||||
* @param pageForm 统计查询参数
|
||||
* @return 单位分析结果
|
||||
*/
|
||||
List<NutMap> getUnitAnalysisData(MemberStatisticsPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 导出分析数据
|
||||
|
||||
+170
-17
@@ -29,6 +29,7 @@ import org.nutz.lang.util.NutMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
@@ -212,32 +213,79 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
|
||||
|
||||
@Override
|
||||
public List<NutMap> getUnionAnalysisData(String queryId, Integer currentYear) {
|
||||
public List<NutMap> getUnionAnalysisData(MemberStatisticsPageForm pageForm) {
|
||||
String queryId = pageForm.getQueryId();
|
||||
Integer currentYear = pageForm.getCurrentYear();
|
||||
String lastYearPersonFilter = getAnalysisPersonFilterSql(pageForm, "his");
|
||||
String currentPersonFilter = getAnalysisPersonFilterSql(pageForm, "u");
|
||||
boolean useCurrentUserView = ((Integer) DateUtil.thisYear()).equals(currentYear);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
gh.id,
|
||||
gh.unionCode,
|
||||
gh.`name` as unionName,
|
||||
( SELECT count( 1 ) FROM member_history his LEFT JOIN vw_user u on u.id = his.userId WHERE his.`year` = @lastYear AND u.unionId = gh.id ) AS lastYearMemberNum,
|
||||
( SELECT count( 1 ) FROM member_apply_record WHERE changeUnionId is not null AND unionId = gh.id AND unionId != changeUnionId AND YEAR ( applyDateTime ) = @currentYear ) AS memberUnitChangeNumIn,
|
||||
( SELECT count( 1 ) FROM member_apply_record WHERE changeUnionId is not null AND changeUnionId = gh.id AND unionId != changeUnionId AND YEAR ( applyDateTime ) = @currentYear ) AS memberUnitChangeNumOut,
|
||||
( SELECT count( 1 ) FROM sys_user_history his LEFT JOIN `vw_user` u ON u.loginname = his.loginname WHERE JSON_CONTAINS(his.changeTypes, '["NEW", "RESTORE"]', '$') AND u.unionId = gh.id AND YEAR ( his.changeTime ) = @currentYear ) AS newResetMemberNum,
|
||||
( SELECT count( 1 ) FROM sys_user_history his LEFT JOIN `vw_user` u ON u.loginname = his.loginname WHERE JSON_CONTAINS(his.changeTypes, '["WITHDRAWAL"]', '$') AND u.unionId = gh.id AND YEAR ( his.changeTime ) = @currentYear ) AS reduceOtherNum,
|
||||
( SELECT count( 1 ) FROM $table where member = 1 and unionId = gh.id) as currentYearMemberNum
|
||||
IFNULL(lastYear.lastYearMemberNum, 0) AS lastYearMemberNum,
|
||||
IFNULL(changeIn.memberUnitChangeNumIn, 0) AS memberUnitChangeNumIn,
|
||||
IFNULL(changeOut.memberUnitChangeNumOut, 0) AS memberUnitChangeNumOut,
|
||||
IFNULL(newReset.newResetMemberNum, 0) AS newResetMemberNum,
|
||||
IFNULL(reduceOther.reduceOtherNum, 0) AS reduceOtherNum,
|
||||
IFNULL(currentMember.currentYearMemberNum, 0) AS currentYearMemberNum
|
||||
FROM
|
||||
sys_union gh
|
||||
LEFT JOIN (
|
||||
SELECT his.unionId, COUNT(1) AS lastYearMemberNum
|
||||
FROM member_history his
|
||||
WHERE his.`year` = @lastYear $lastYearPersonFilter
|
||||
GROUP BY his.unionId
|
||||
) lastYear ON lastYear.unionId = gh.id
|
||||
LEFT JOIN (
|
||||
SELECT unionId, COUNT(1) AS memberUnitChangeNumIn
|
||||
FROM member_apply_record
|
||||
WHERE changeUnionId is not null AND unionId != changeUnionId AND YEAR(applyDateTime) = @currentYear
|
||||
GROUP BY unionId
|
||||
) changeIn ON changeIn.unionId = gh.id
|
||||
LEFT JOIN (
|
||||
SELECT changeUnionId AS unionId, COUNT(1) AS memberUnitChangeNumOut
|
||||
FROM member_apply_record
|
||||
WHERE changeUnionId is not null AND unionId != changeUnionId AND YEAR(applyDateTime) = @currentYear
|
||||
GROUP BY changeUnionId
|
||||
) changeOut ON changeOut.unionId = gh.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unionId, COUNT(1) AS newResetMemberNum
|
||||
FROM sys_user_history his
|
||||
JOIN vw_user u ON u.loginname = his.loginname
|
||||
WHERE JSON_CONTAINS(his.changeTypes, '["NEW", "RESTORE"]', '$')
|
||||
AND YEAR(his.changeTime) = @currentYear $currentPersonFilter
|
||||
GROUP BY u.unionId
|
||||
) newReset ON newReset.unionId = gh.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unionId, COUNT(1) AS reduceOtherNum
|
||||
FROM sys_user_history his
|
||||
JOIN vw_user u ON u.loginname = his.loginname
|
||||
WHERE JSON_CONTAINS(his.changeTypes, '["WITHDRAWAL"]', '$')
|
||||
AND YEAR(his.changeTime) = @currentYear $currentPersonFilter
|
||||
GROUP BY u.unionId
|
||||
) reduceOther ON reduceOther.unionId = gh.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unionId, COUNT(1) AS currentYearMemberNum
|
||||
FROM $table u
|
||||
WHERE u.member = 1 $currentPersonFilter $currentYearFilter
|
||||
GROUP BY u.unionId
|
||||
) currentMember ON currentMember.unionId = gh.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("lastYear", currentYear - 1);
|
||||
sql.setParam("currentYear", currentYear);
|
||||
sql.setVar("table", ((Integer) DateUtil.thisYear()).equals(currentYear) ? "`vw_user`" : "member_history");
|
||||
sql.setVar("table", useCurrentUserView ? "`vw_user`" : "member_history");
|
||||
sql.setVar("lastYearPersonFilter", new Static(lastYearPersonFilter));
|
||||
sql.setVar("currentPersonFilter", new Static(currentPersonFilter));
|
||||
sql.setVar("currentYearFilter", new Static(getAnalysisYearFilterSql("u", useCurrentUserView, currentYear)));
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
cnd.and("gh.id", "=", SecurityUtil.getUnionId());
|
||||
} else {
|
||||
cnd.andEX("gh.id", "=", queryId);
|
||||
}
|
||||
cnd.groupBy("gh.id, gh.unionCode, gh.name");
|
||||
cnd.asc("gh.unionCode");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = listMap(sql);
|
||||
@@ -250,24 +298,73 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
|
||||
|
||||
@Override
|
||||
public List<NutMap> getUnitAnalysisData(String queryId, Integer currentYear) {
|
||||
public List<NutMap> getUnitAnalysisData(MemberStatisticsPageForm pageForm) {
|
||||
String queryId = pageForm.getQueryId();
|
||||
Integer currentYear = pageForm.getCurrentYear();
|
||||
String lastYearPersonFilter = getAnalysisPersonFilterSql(pageForm, "his");
|
||||
String currentPersonFilter = getAnalysisPersonFilterSql(pageForm, "u");
|
||||
boolean useCurrentUserView = ((Integer) DateUtil.thisYear()).equals(currentYear);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
dw.id,
|
||||
dw.unitcode as unitCode,
|
||||
dw.`name` as unitName,
|
||||
( SELECT count( 1 ) FROM member_history WHERE `year` = @lastYear AND unitId = dw.id ) AS lastYearMemberNum,
|
||||
( SELECT count( 1 ) FROM member_apply_record WHERE changeUnitId is not null AND unitId = dw.id AND unitId != changeUnitId AND YEAR ( applyDateTime ) = @currentYear ) AS memberUnitChangeNumIn,
|
||||
( SELECT count( 1 ) FROM member_apply_record WHERE changeUnitId is not null AND changeUnitId = dw.id AND unitId != changeUnitId AND YEAR ( applyDateTime ) = @currentYear ) AS memberUnitChangeNumOut,
|
||||
( SELECT count( 1 ) FROM sys_user_history his LEFT JOIN vw_user u ON u.loginname = his.loginname WHERE JSON_CONTAINS(his.changeTypes, '["NEW", "RESTORE"]', '$') AND u.unitId = dw.id AND YEAR ( his.changeTime ) = @currentYear ) AS newResetMemberNum,
|
||||
( SELECT count( 1 ) FROM sys_user_history his LEFT JOIN vw_user u ON u.loginname = his.loginname WHERE JSON_CONTAINS(his.changeTypes, '["WITHDRAWAL"]', '$') AND u.unitId = dw.id AND YEAR ( his.changeTime ) = @currentYear ) AS reduceOtherNum,
|
||||
( SELECT count( 1 ) from vw_user where member = 1 and unitId = dw.id) as currentYearMemberNum
|
||||
IFNULL(lastYear.lastYearMemberNum, 0) AS lastYearMemberNum,
|
||||
IFNULL(changeIn.memberUnitChangeNumIn, 0) AS memberUnitChangeNumIn,
|
||||
IFNULL(changeOut.memberUnitChangeNumOut, 0) AS memberUnitChangeNumOut,
|
||||
IFNULL(newReset.newResetMemberNum, 0) AS newResetMemberNum,
|
||||
IFNULL(reduceOther.reduceOtherNum, 0) AS reduceOtherNum,
|
||||
IFNULL(currentMember.currentYearMemberNum, 0) AS currentYearMemberNum
|
||||
FROM
|
||||
sys_unit dw
|
||||
LEFT JOIN (
|
||||
SELECT his.unitId, COUNT(1) AS lastYearMemberNum
|
||||
FROM member_history his
|
||||
WHERE his.`year` = @lastYear $lastYearPersonFilter
|
||||
GROUP BY his.unitId
|
||||
) lastYear ON lastYear.unitId = dw.id
|
||||
LEFT JOIN (
|
||||
SELECT unitId, COUNT(1) AS memberUnitChangeNumIn
|
||||
FROM member_apply_record
|
||||
WHERE changeUnitId is not null AND unitId != changeUnitId AND YEAR(applyDateTime) = @currentYear
|
||||
GROUP BY unitId
|
||||
) changeIn ON changeIn.unitId = dw.id
|
||||
LEFT JOIN (
|
||||
SELECT changeUnitId AS unitId, COUNT(1) AS memberUnitChangeNumOut
|
||||
FROM member_apply_record
|
||||
WHERE changeUnitId is not null AND unitId != changeUnitId AND YEAR(applyDateTime) = @currentYear
|
||||
GROUP BY changeUnitId
|
||||
) changeOut ON changeOut.unitId = dw.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unitId, COUNT(1) AS newResetMemberNum
|
||||
FROM sys_user_history his
|
||||
JOIN vw_user u ON u.loginname = his.loginname
|
||||
WHERE JSON_CONTAINS(his.changeTypes, '["NEW", "RESTORE"]', '$')
|
||||
AND YEAR(his.changeTime) = @currentYear $currentPersonFilter
|
||||
GROUP BY u.unitId
|
||||
) newReset ON newReset.unitId = dw.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unitId, COUNT(1) AS reduceOtherNum
|
||||
FROM sys_user_history his
|
||||
JOIN vw_user u ON u.loginname = his.loginname
|
||||
WHERE JSON_CONTAINS(his.changeTypes, '["WITHDRAWAL"]', '$')
|
||||
AND YEAR(his.changeTime) = @currentYear $currentPersonFilter
|
||||
GROUP BY u.unitId
|
||||
) reduceOther ON reduceOther.unitId = dw.id
|
||||
LEFT JOIN (
|
||||
SELECT u.unitId, COUNT(1) AS currentYearMemberNum
|
||||
FROM $table u
|
||||
WHERE u.member = 1 $currentPersonFilter $currentYearFilter
|
||||
GROUP BY u.unitId
|
||||
) currentMember ON currentMember.unitId = dw.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("lastYear", currentYear - 1);
|
||||
sql.setParam("currentYear", currentYear);
|
||||
sql.setVar("lastYearPersonFilter", new Static(lastYearPersonFilter));
|
||||
sql.setVar("currentPersonFilter", new Static(currentPersonFilter));
|
||||
sql.setVar("table", useCurrentUserView ? "`vw_user`" : "member_history");
|
||||
sql.setVar("currentYearFilter", new Static(getAnalysisYearFilterSql("u", useCurrentUserView, currentYear)));
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("dw.unitLevel", "=", 3);
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
@@ -275,7 +372,6 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
} else {
|
||||
cnd.andEX("dw.id", "=", queryId);
|
||||
}
|
||||
cnd.groupBy("dw.id", "dw.unitcode", "dw.name");
|
||||
cnd.asc("dw.unitcode");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = listMap(sql);
|
||||
@@ -286,6 +382,61 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析统计的 SQL 由多个子查询组成,外层机构条件不会自动作用到人员子查询。
|
||||
* 这里统一拼出人员分类、人员属性的过滤片段,分别注入到各个统计子查询里,
|
||||
* 保证 analysis 页面和前端标签筛选口径一致。
|
||||
*
|
||||
* @param pageForm 查询参数
|
||||
* @param alias 子查询里的人员表别名
|
||||
* @return 可直接拼接到子查询 WHERE 末尾的 SQL 片段
|
||||
*/
|
||||
private String getAnalysisPersonFilterSql(MemberStatisticsPageForm pageForm, String alias) {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append(getAnalysisInSql(alias, "aidFundMemberUserType", pageForm.getAidFundMemberUserTypes()));
|
||||
sqlBuilder.append(getAnalysisInSql(alias, "userAttribute", pageForm.getUserAttributes()));
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* analysis 的当前数量在查历史年度时会切到 member_history,
|
||||
* 这里统一补年度条件,避免历史年度把整张历史表都统计进去。
|
||||
*
|
||||
* @param alias 子查询别名
|
||||
* @param useCurrentUserView 是否使用当前人员视图
|
||||
* @param currentYear 查询年度
|
||||
* @return 年度过滤 SQL 片段
|
||||
*/
|
||||
private String getAnalysisYearFilterSql(String alias, boolean useCurrentUserView, Integer currentYear) {
|
||||
if (useCurrentUserView) {
|
||||
return "";
|
||||
}
|
||||
return " AND " + alias + ".`year` = " + currentYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里仅用于拼接 analysis 模块固定字段的 IN 条件,
|
||||
* 会过滤空值并转义单引号,避免直接把前端原始值拼回 SQL。
|
||||
*
|
||||
* @param alias 子查询别名
|
||||
* @param columnName 字段名
|
||||
* @param values 筛选值列表
|
||||
* @return IN 条件 SQL 片段
|
||||
*/
|
||||
private String getAnalysisInSql(String alias, String columnName, List<String> values) {
|
||||
if (Lang.isEmpty(values)) {
|
||||
return "";
|
||||
}
|
||||
List<String> validValues = values.stream().filter(Strings::isNotBlank).collect(Collectors.toList());
|
||||
if (Lang.isEmpty(validValues)) {
|
||||
return "";
|
||||
}
|
||||
String inValueSql = validValues.stream()
|
||||
.map(value -> "'" + value.replace("'", "''") + "'")
|
||||
.collect(Collectors.joining(", "));
|
||||
return " AND " + alias + "." + columnName + " IN (" + inValueSql + ")";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Workbook exportAnalysisExcel(List<NutMap> list, String queryType) {
|
||||
@@ -348,6 +499,7 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
// sql.setVar("abbr", new Static("his"));
|
||||
}
|
||||
cnd.andEX("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("u.aidFundMemberUserType", "in", pageForm.getAidFundMemberUserTypes());
|
||||
cnd.groupBy("un.id", "un.unionCode", "un.name");
|
||||
cnd.asc("un.unionCode");
|
||||
sql.setCondition(cnd);
|
||||
@@ -388,6 +540,7 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
sql.setVar("yearCnd", new Static(" AND u.`year` = %d".formatted(currentYear)));
|
||||
}
|
||||
cnd.andEX("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("u.aidFundMemberUserType", "in", pageForm.getAidFundMemberUserTypes());
|
||||
cnd.and("un.unitLevel", "=", 2);
|
||||
cnd.groupBy("un.id", "un.unitcode", "un.name");
|
||||
cnd.asc("un.unitcode");
|
||||
|
||||
@@ -55,4 +55,10 @@ public class WelfareListPageForm extends PageForm {
|
||||
@ApiModelProperty("在职状态")
|
||||
private String[] userStates;
|
||||
|
||||
@ApiModelProperty("人员分类")
|
||||
private String aidFundMemberUserType;
|
||||
|
||||
@ApiModelProperty("人员属性")
|
||||
private String userAttribute;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +31,10 @@ public class WelfareSelectionSituationPageForm extends PageForm {
|
||||
|
||||
@ApiModelProperty("是否已选择")
|
||||
private Boolean isSelect;
|
||||
|
||||
@ApiModelProperty("人员分类")
|
||||
private String aidFundMemberUserType;
|
||||
|
||||
@ApiModelProperty("人员属性")
|
||||
private String userAttribute;
|
||||
}
|
||||
|
||||
@@ -417,6 +417,8 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
cnd.andEX("t1.personType", "in", pageForm.getPersonTypes());
|
||||
cnd.andEX("t1.preparedBy", "in", pageForm.getPreparedBys());
|
||||
cnd.andEX("t1.userState", "in", pageForm.getUserStates());
|
||||
cnd.andEX("t2.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("t2.userAttribute", "=", pageForm.getUserAttribute());
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.asc("t1.welfareUnionName");
|
||||
cnd.asc("t1.welfareUnitName");
|
||||
|
||||
+2
@@ -83,6 +83,8 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
|
||||
|
||||
cnd.and("t1.projectId", "=", pageForm.getProjectId());
|
||||
cnd.andEX("t2.selectOptionId", "=", pageForm.getWelfareOptionId());
|
||||
cnd.andEX("t4.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("t4.userAttribute", "=", pageForm.getUserAttribute());
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getUserName())) {
|
||||
cnd.where().andLike("t4.username", pageForm.getUserName());
|
||||
|
||||
@@ -148,6 +148,7 @@
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:formatter="column.formatter"
|
||||
></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="操作" width="100">
|
||||
<template slot-scope="{ row }">
|
||||
@@ -232,7 +233,7 @@ module.exports = {
|
||||
{prop: "loginName", label: "工号"},
|
||||
{prop: "userName", label: "姓名"},
|
||||
{prop: "sex", label: "性别", sortable: true},
|
||||
{prop: "birthday", label: "出生年月"},
|
||||
{prop: "birthday", label: "出生年月", formatter: this.formatDate},
|
||||
{prop: "mobile", label: "联系电话"},
|
||||
{prop: "personType", label: "教职工类别", sortable: true},
|
||||
{prop: "userState", label: "在职状态", sortable: true},
|
||||
@@ -263,6 +264,9 @@ module.exports = {
|
||||
}
|
||||
return group.groupName + "(人员结果)"
|
||||
},
|
||||
formatDate(row, column, cellValue) {
|
||||
return cellValue ? moment(cellValue).format("YYYY-MM-DD") : ""
|
||||
},
|
||||
viewGroupName() {
|
||||
if (this.pageForm.groupId) {
|
||||
const group = this.activityGroupList.find((v) => v.groupId === this.pageForm.groupId)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="category" label="流程分类">
|
||||
<el-table-column prop="category" label="流程分类" sortable="custom">
|
||||
<template slot-scope="{row}">
|
||||
{{categoryOptions.find(v=>v.id === row.category)?.name}}
|
||||
</template>
|
||||
@@ -54,6 +54,19 @@ layout("/layouts/platform.html"){
|
||||
<el-tag size="mini" v-else-if="row.isDeployed===0" type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序编码" prop="sortNo" width="120" align="center" header-align="center" sortable="custom">
|
||||
<template slot-scope="{row}">
|
||||
<el-input-number
|
||||
v-model="row.sortNo"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
size="mini"
|
||||
style="width: 88px"
|
||||
@change="updateSortNo(row)"
|
||||
></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="450px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="onDesign(row)">设计</el-button>
|
||||
@@ -237,6 +250,22 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
})
|
||||
},
|
||||
updateSortNo(row) {
|
||||
const sortNo = Number(row.sortNo)
|
||||
if (!Number.isInteger(sortNo) || sortNo < 0) {
|
||||
this.$message.error("排序编码必须是非负整数")
|
||||
return
|
||||
}
|
||||
$.post("/flow/design/updateSortNo", {
|
||||
id: row.id,
|
||||
sortNo
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
width: calc(100% - 20px);
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.tool-card .el-card__body {
|
||||
height: calc(100% - 57px);
|
||||
}
|
||||
|
||||
.tool-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 180px);
|
||||
}
|
||||
|
||||
.tool-body-center {
|
||||
width: 70%;
|
||||
min-width: 900px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wizard-box {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wizard-panel {
|
||||
padding: 18px;
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
.option-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.option-list {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 470px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.option-list .el-radio,
|
||||
.option-list .el-checkbox {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0 0 12px 0 !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.option-list .el-radio.is-bordered,
|
||||
.option-list .el-checkbox.is-bordered {
|
||||
margin-left: 0 !important;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.option-list .el-radio__label,
|
||||
.option-list .el-checkbox__label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.option-extra {
|
||||
color: #f56c6c;
|
||||
margin-left: auto;
|
||||
padding-left: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.option-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.option-item-main {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.relation-table {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.submit-panel {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.result-panel {
|
||||
min-height: 420px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wizard-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="tool-card" shadow="never">
|
||||
<div slot="header">
|
||||
<span>高级工具</span>
|
||||
</div>
|
||||
|
||||
<div class="tool-body">
|
||||
<div class="tool-body-center">
|
||||
<el-steps :active="active" align-center finish-status="success">
|
||||
<el-step title="选择数据表" icon="el-icon-coin"></el-step>
|
||||
<el-step title="选择字段" icon="el-icon-s-grid"></el-step>
|
||||
<el-step title="字段对应" icon="el-icon-connection"></el-step>
|
||||
<el-step title="提交导入" icon="el-icon-upload2"></el-step>
|
||||
<el-step title="导入结果" icon="el-icon-success"></el-step>
|
||||
</el-steps>
|
||||
|
||||
<div class="wizard-box">
|
||||
<div class="wizard-panel" v-if="active === 0">
|
||||
<div class="option-toolbar">
|
||||
<el-input
|
||||
v-model.trim="tableKeyword"
|
||||
clearable
|
||||
placeholder="输入表名或备注搜索"
|
||||
style="width: 260px">
|
||||
</el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="filterTables">搜索</el-button>
|
||||
<el-button v-if="formData.tableName" type="text">当前选择:{{ formData.tableName }}</el-button>
|
||||
</div>
|
||||
<el-radio-group v-model="formData.tableName" class="option-list">
|
||||
<el-radio
|
||||
v-for="item in filteredTables"
|
||||
:key="item.table_name"
|
||||
:label="item.table_name"
|
||||
border>
|
||||
<div class="option-item-content">
|
||||
<span class="option-item-main">{{ item.table_name }}</span>
|
||||
<span v-if="item.table_comment" class="option-item-main">({{ item.table_comment }})</span>
|
||||
<span class="option-extra">rows: {{ item.table_rows || 0 }}</span>
|
||||
</div>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="wizard-panel" v-if="active === 1">
|
||||
<div class="option-toolbar">
|
||||
<el-input
|
||||
v-model.trim="columnKeyword"
|
||||
clearable
|
||||
placeholder="输入字段名或备注搜索"
|
||||
style="width: 260px">
|
||||
</el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="filterColumns">搜索</el-button>
|
||||
<el-button type="text">{{ formData.tableName }}</el-button>
|
||||
</div>
|
||||
<el-checkbox-group v-model="formData.columns" class="option-list">
|
||||
<el-checkbox
|
||||
v-for="item in filteredColumns"
|
||||
:key="item.column_name"
|
||||
:label="item.column_name"
|
||||
border>
|
||||
<div class="option-item-content">
|
||||
<span class="option-item-main">{{ item.column_name }}</span>
|
||||
<span v-if="item.column_comment" class="option-item-main">({{ item.column_comment }})</span>
|
||||
<span class="option-extra">{{ item.column_type }}</span>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
|
||||
<div class="wizard-panel" v-if="active === 2">
|
||||
<el-alert
|
||||
title="这里填写 Excel 第一行表头名称,用来和数据库字段建立对应关系。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 16px">
|
||||
</el-alert>
|
||||
<el-table :data="relation" border class="relation-table">
|
||||
<el-table-column prop="column_name" label="字段名" min-width="180"></el-table-column>
|
||||
<el-table-column prop="column_comment" label="字段备注" min-width="220" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="column_type" label="字段类型" width="160"></el-table-column>
|
||||
<el-table-column prop="column_key" label="键" width="80"></el-table-column>
|
||||
<el-table-column label="Excel 表头" min-width="260">
|
||||
<template slot-scope="{ row }">
|
||||
<el-input v-model.trim="row.relation" placeholder="请输入 Excel 表头名称"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="wizard-panel" v-if="active === 3">
|
||||
<div class="submit-panel">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="导入方式">
|
||||
<el-radio-group v-model="formData.method">
|
||||
<el-radio-button :label="1">追加或更新</el-radio-button>
|
||||
<el-radio-button :label="2">仅追加</el-radio-button>
|
||||
<el-radio-button :label="3">仅更新</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="关键字段" v-if="[1, 3].includes(formData.method)">
|
||||
<el-select
|
||||
v-model="formData.field"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择关键字段"
|
||||
style="width: 360px">
|
||||
<el-option
|
||||
v-for="item in relation"
|
||||
:key="item.column_name"
|
||||
:label="item.column_name"
|
||||
:value="item.column_name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="可选插件">
|
||||
<el-checkbox-group v-model="formData.plugins">
|
||||
<el-checkbox
|
||||
v-for="item in plugins"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
border>
|
||||
{{ item.description }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Excel 文件" required>
|
||||
<el-upload
|
||||
ref="upload"
|
||||
drag
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleFileRemove">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div slot="tip" class="el-upload__tip">仅支持 `xls`、`xlsx` 文件</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-panel" v-if="active === 4">
|
||||
<div class="result-panel">
|
||||
<el-progress type="circle" :percentage="resultPercentage" status="success"></el-progress>
|
||||
<div style="margin-top: 18px; color: #606266;">
|
||||
共 {{ result.total || 0 }} 条,成功 {{ result.success || 0 }} 条
|
||||
</div>
|
||||
<div v-if="result.cacheKey" style="margin-top: 10px;">
|
||||
<el-link type="primary" :href="loc() + '/exportErrors?cacheKey=' + result.cacheKey">下载错误记录</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-footer">
|
||||
<el-button :disabled="active === 0 || loading" @click="prevStep">上一步</el-button>
|
||||
<el-button v-if="active < 4" type="primary" :loading="loading" @click="nextStep">下一步</el-button>
|
||||
<el-button v-else type="primary" @click="restart">重新开始</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
loading: false,
|
||||
tableKeyword: "",
|
||||
columnKeyword: "",
|
||||
tables: [],
|
||||
filteredTables: [],
|
||||
columns: [],
|
||||
filteredColumns: [],
|
||||
relation: [],
|
||||
plugins: [],
|
||||
fileList: [],
|
||||
result: {
|
||||
total: 0,
|
||||
success: 0,
|
||||
cacheKey: ""
|
||||
},
|
||||
formData: {
|
||||
tableName: "",
|
||||
columns: [],
|
||||
method: 1,
|
||||
field: "",
|
||||
plugins: []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resultPercentage() {
|
||||
if (!this.result.total) {
|
||||
return 0
|
||||
}
|
||||
return Number(((this.result.success / this.result.total) * 100).toFixed(2))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeRecord(item) {
|
||||
const normalized = {}
|
||||
Object.keys(item || {}).forEach(key => {
|
||||
normalized[(key || "").toLowerCase()] = item[key]
|
||||
})
|
||||
return normalized
|
||||
},
|
||||
async loadTables() {
|
||||
const resp = await this.$axios.post(loc() + "/tableNames")
|
||||
if (resp.code === 0) {
|
||||
this.tables = Array.isArray(resp.data)
|
||||
? resp.data.map(item => this.normalizeRecord(item))
|
||||
: []
|
||||
this.filteredTables = this.tables.slice()
|
||||
}
|
||||
},
|
||||
async loadPlugins() {
|
||||
this.plugins = await this.$businessTool.getEnumOptions("SysDataImportPlugin")
|
||||
},
|
||||
async loadColumns() {
|
||||
const resp = await this.$axios.post(loc() + "/columns", {
|
||||
tableName: this.formData.tableName
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.columns = Array.isArray(resp.data)
|
||||
? resp.data.map(item => this.normalizeRecord(item))
|
||||
: []
|
||||
this.filteredColumns = this.columns.slice()
|
||||
}
|
||||
},
|
||||
filterTables() {
|
||||
const keyword = (this.tableKeyword || "").toLowerCase()
|
||||
if (!keyword) {
|
||||
this.filteredTables = this.tables.slice()
|
||||
return
|
||||
}
|
||||
this.filteredTables = this.tables.filter(item => {
|
||||
const tableName = (item.table_name || "").toLowerCase()
|
||||
const tableComment = (item.table_comment || "").toLowerCase()
|
||||
return tableName.includes(keyword) || tableComment.includes(keyword)
|
||||
})
|
||||
},
|
||||
filterColumns() {
|
||||
const keyword = (this.columnKeyword || "").toLowerCase()
|
||||
if (!keyword) {
|
||||
this.filteredColumns = this.columns.slice()
|
||||
return
|
||||
}
|
||||
this.filteredColumns = this.columns.filter(item => {
|
||||
const columnName = (item.column_name || "").toLowerCase()
|
||||
const columnComment = (item.column_comment || "").toLowerCase()
|
||||
return columnName.includes(keyword) || columnComment.includes(keyword)
|
||||
})
|
||||
},
|
||||
buildRelation() {
|
||||
this.relation = this.columns
|
||||
.filter(item => this.formData.columns.includes(item.column_name))
|
||||
.map(item => Object.assign({}, item, { relation: "" }))
|
||||
},
|
||||
async nextStep() {
|
||||
if (this.active === 0) {
|
||||
if (!this.formData.tableName) {
|
||||
this.$message.warning("请选择数据表")
|
||||
return
|
||||
}
|
||||
await this.loadColumns()
|
||||
this.formData.columns = []
|
||||
this.formData.field = ""
|
||||
} else if (this.active === 1) {
|
||||
if (!this.formData.columns.length) {
|
||||
this.$message.warning("请至少选择一个字段")
|
||||
return
|
||||
}
|
||||
this.buildRelation()
|
||||
} else if (this.active === 2) {
|
||||
const hasEmptyRelation = this.relation.some(item => !item.relation)
|
||||
if (hasEmptyRelation) {
|
||||
this.$message.warning("请填写所有 Excel 表头对应关系")
|
||||
return
|
||||
}
|
||||
} else if (this.active === 3) {
|
||||
const success = await this.submitImport()
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.active += 1
|
||||
},
|
||||
prevStep() {
|
||||
if (this.active > 0) {
|
||||
this.active -= 1
|
||||
}
|
||||
},
|
||||
handleFileRemove(file, fileList) {
|
||||
this.fileList = fileList
|
||||
},
|
||||
handleFileChange(file, fileList) {
|
||||
const extension = ((file.name || "").split(".").pop() || "").toLowerCase()
|
||||
const removeIndex = fileList.findIndex(item => item.uid === file.uid)
|
||||
if (!["xls", "xlsx"].includes(extension)) {
|
||||
this.$message.warning("仅支持 xls、xlsx 文件")
|
||||
if (removeIndex > -1) {
|
||||
fileList.splice(removeIndex, 1)
|
||||
}
|
||||
} else if (file.size === 0) {
|
||||
this.$message.warning("上传文件不能为空")
|
||||
if (removeIndex > -1) {
|
||||
fileList.splice(removeIndex, 1)
|
||||
}
|
||||
}
|
||||
this.fileList = fileList
|
||||
},
|
||||
async submitImport() {
|
||||
if ([1, 3].includes(this.formData.method) && !this.formData.field) {
|
||||
this.$message.warning("请选择关键字段")
|
||||
return false
|
||||
}
|
||||
if (!this.fileList.length) {
|
||||
this.$message.warning("请上传 Excel 文件")
|
||||
return false
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("tableName", this.formData.tableName)
|
||||
formData.append("method", this.formData.method)
|
||||
formData.append("field", this.formData.field || "")
|
||||
formData.append("relation", JSON.stringify(this.relation))
|
||||
formData.append("plugins", JSON.stringify(this.formData.plugins || []))
|
||||
formData.append("file", this.fileList[0].raw, this.fileList[0].name)
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post(loc() + "/importData", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
})
|
||||
if (resp.code !== 0) {
|
||||
this.$message.error(resp.msg)
|
||||
return false
|
||||
}
|
||||
this.result = resp.data || { total: 0, success: 0, cacheKey: "" }
|
||||
this.$message.success(resp.msg)
|
||||
return true
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
restart() {
|
||||
this.active = 0
|
||||
this.tableKeyword = ""
|
||||
this.columnKeyword = ""
|
||||
this.columns = []
|
||||
this.filteredColumns = []
|
||||
this.relation = []
|
||||
this.fileList = []
|
||||
this.result = {
|
||||
total: 0,
|
||||
success: 0,
|
||||
cacheKey: ""
|
||||
}
|
||||
this.formData = {
|
||||
tableName: "",
|
||||
columns: [],
|
||||
method: 1,
|
||||
field: "",
|
||||
plugins: []
|
||||
}
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
this.filterTables()
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadTables()
|
||||
await this.loadPlugins()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -65,6 +65,19 @@ layout("/layouts/platform.html"){
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column align="left" header-align="left" label="菜单名称" prop="name" width="200"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="排序编码" prop="location" width="120">
|
||||
<template slot-scope="{row}">
|
||||
<el-input-number
|
||||
v-model="row.location"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
size="mini"
|
||||
style="width: 88px"
|
||||
@change="updateLocation(row)"
|
||||
></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="菜单图标" prop="icon" width="100">
|
||||
<template slot-scope="scope">
|
||||
<template v-if="pageForm.platform==='PC'">
|
||||
@@ -325,6 +338,26 @@ layout("/layouts/platform.html"){
|
||||
this.moduleOptions = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
updateLocation(row) {
|
||||
const location = Number(row.location)
|
||||
if (!Number.isInteger(location) || location < 0) {
|
||||
this.$message.error("排序编码必须是非负整数")
|
||||
return
|
||||
}
|
||||
$.post("/platform/sys/menu/updateLocation", {
|
||||
id: row.id,
|
||||
location
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
if (row.parentId) {
|
||||
this.loadChildByExpandedKeys()
|
||||
} else {
|
||||
this.initTreeTable()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -20,6 +20,24 @@ const branchUnionUserManage = {
|
||||
<el-option label="工号" value="loginname"></el-option>
|
||||
</el-select>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-model="pageForm.j"
|
||||
placeholder="届数"
|
||||
size="small"
|
||||
clearable
|
||||
class="j-search-select"
|
||||
@change="handleJChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in jOptions"
|
||||
:key="item.code"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<span v-if="hasCadreData(item.code)" class="j-used-mark">*</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" icon="el-icon-search" @click="doSearch"></el-button>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" style="margin-left: auto"
|
||||
@click="openAdd" v-if="$auth.hasPermission('sys.manager.union.branchOfficer')">添加
|
||||
@@ -31,11 +49,23 @@ const branchUnionUserManage = {
|
||||
<el-table-column prop="loginName" label="工号"></el-table-column>
|
||||
<el-table-column prop="userName" label="姓名"></el-table-column>
|
||||
<el-table-column prop="mobile" label="联系方式"></el-table-column>
|
||||
<el-table-column label="届数">
|
||||
<template scope="{row}">
|
||||
{{ row.jName || row.j }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleName" label="职务"></el-table-column>
|
||||
<el-table-column prop="taskName" label="当前状态"></el-table-column>
|
||||
<el-table-column label="操作" width="100px"
|
||||
<el-table-column prop="displayStatus" label="当前状态"></el-table-column>
|
||||
<el-table-column label="操作" width="150px"
|
||||
v-if="$auth.hasPermission('sys.manager.union.branchOfficer')">
|
||||
<template scope="scope">
|
||||
<el-button
|
||||
v-if="!isLeft(scope.row)"
|
||||
size="mini"
|
||||
type="warning"
|
||||
icon="el-icon-switch-button"
|
||||
@click="openLeave(scope.row)"
|
||||
></el-button>
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete"
|
||||
@click="doDelete(scope.row)"></el-button>
|
||||
</template>
|
||||
@@ -47,6 +77,9 @@ const branchUnionUserManage = {
|
||||
<el-form-item prop="roleCode" label="角色">
|
||||
<dict-select v-model="formData.roleCode" code="BRANCH_UNION_ROLES" placeholder="请选择角色"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="j" label="届数">
|
||||
<dict-select v-model="formData.j" code="TEACHER_CONGRESS_J" placeholder="请选择届数"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="userId" label="人员">
|
||||
<user-select
|
||||
v-model="formData.userId"
|
||||
@@ -66,6 +99,23 @@ const branchUnionUserManage = {
|
||||
<el-button type="primary" @click="doSubmit">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog title="离任" :visible.sync="leaveDialogVisible" width="420px" :close-on-click-modal="false">
|
||||
<el-form :model="leaveForm" ref="leaveForm" size="small" label-width="90px">
|
||||
<el-form-item label="离任时间" required>
|
||||
<el-date-picker
|
||||
v-model="leaveForm.leaveDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择离任时间"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="leaveDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="doLeave">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
`,
|
||||
mixins: [initTableMixins],
|
||||
@@ -78,32 +128,114 @@ const branchUnionUserManage = {
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
searchName: "username"
|
||||
searchName: "username",
|
||||
j: ""
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
formData: {}
|
||||
formData: {},
|
||||
leaveDialogVisible: false,
|
||||
leaveForm: {
|
||||
id: "",
|
||||
leaveDate: ""
|
||||
},
|
||||
jOptions: [],
|
||||
usedJCodes: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
union_id(val) {
|
||||
this.doSearch()
|
||||
this.pageForm.j = ""
|
||||
this.initJSearch()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadJOptions() {
|
||||
return $.get("/open/common/dictOptions", { code: "TEACHER_CONGRESS_J" }).then((res) => {
|
||||
this.jOptions = res.data || []
|
||||
})
|
||||
},
|
||||
loadUsedJCodes() {
|
||||
if (!this.union_id) {
|
||||
this.usedJCodes = []
|
||||
return $.Deferred().resolve().promise()
|
||||
}
|
||||
return $.get("/platform/sys/union/branchUnionUserUsedJData", {
|
||||
unionId: this.union_id
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.usedJCodes = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
hasCadreData(code) {
|
||||
return this.usedJCodes.includes(code)
|
||||
},
|
||||
getHighestUsedJ() {
|
||||
if (!this.usedJCodes.length) {
|
||||
return ""
|
||||
}
|
||||
for (let i = this.jOptions.length - 1; i >= 0; i--) {
|
||||
if (this.usedJCodes.includes(this.jOptions[i].code)) {
|
||||
return this.jOptions[i].code
|
||||
}
|
||||
}
|
||||
return this.usedJCodes[0]
|
||||
},
|
||||
initJSearch() {
|
||||
const optionTask = this.jOptions.length ? $.Deferred().resolve().promise() : this.loadJOptions()
|
||||
$.when(optionTask, this.loadUsedJCodes()).then(() => {
|
||||
this.pageForm.j = this.getHighestUsedJ()
|
||||
this.pageData()
|
||||
})
|
||||
},
|
||||
handleJChange(val) {
|
||||
this.pageForm.j = val || ""
|
||||
this.doSearch()
|
||||
},
|
||||
isLeft(row) {
|
||||
return row.isServing === false || row.isServing === 0 || row.displayStatus === "离任"
|
||||
},
|
||||
openAdd() {
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.formData = {}
|
||||
})
|
||||
},
|
||||
openLeave(row) {
|
||||
this.leaveForm = {
|
||||
id: row.id,
|
||||
leaveDate: ""
|
||||
}
|
||||
this.leaveDialogVisible = true
|
||||
},
|
||||
doSubmit() {
|
||||
this.$axios.post("/platform/sys/union/insertBranchUnionUserRole", {
|
||||
if (!this.formData.j) {
|
||||
this.$message.error("请选择届数")
|
||||
return
|
||||
}
|
||||
$.post("/platform/sys/union/insertBranchUnionUserRole", {
|
||||
...this.formData,
|
||||
unionId: this.union_id
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$message.success(res.msg)
|
||||
this.loadUsedJCodes()
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
doLeave() {
|
||||
if (!this.leaveForm.leaveDate) {
|
||||
this.$message.error("请选择离任时间")
|
||||
return
|
||||
}
|
||||
$.post("/platform/sys/union/leaveBranchUnionUserRole", this.leaveForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.leaveDialogVisible = false
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
@@ -123,6 +255,7 @@ const branchUnionUserManage = {
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.loadUsedJCodes()
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
@@ -131,7 +264,8 @@ const branchUnionUserManage = {
|
||||
pageData() {
|
||||
$.get("/platform/sys/union/branchUnionUserPageData", {
|
||||
...this.pageForm,
|
||||
unionId: this.union_id
|
||||
unionId: this.union_id,
|
||||
j: this.pageForm.j || ""
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
@@ -140,11 +274,19 @@ const branchUnionUserManage = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.initJSearch()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/deep/ .el-select {
|
||||
width: 100% !important;
|
||||
}
|
||||
/deep/ .j-search-select {
|
||||
width: 80px !important;
|
||||
}
|
||||
.j-used-mark {
|
||||
color: #f56c6c;
|
||||
margin-left: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@ layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-tip {
|
||||
color: #c64120;
|
||||
line-height: 24px;
|
||||
@@ -11,9 +18,14 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<div class="left-span-label">活动类型年度报名次数配置</div>
|
||||
<div class="page-tip mt10">
|
||||
注:这里配置的是“同一活动类型在同一年度内最多可报名多少次”。未单独配置过的活动类型默认按 2 次处理。
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="left-span-label">活动分类年度报名次数配置</div>
|
||||
<div class="page-tip mt10">
|
||||
注:这里配置的是“同一活动分类在同一年度内最多可报名多少次”。未单独配置过的活动分类默认按 2 次处理。
|
||||
</div>
|
||||
</div>
|
||||
<el-button plain icon="el-icon-back" @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -22,8 +34,8 @@ layout("/layouts/platform.html"){
|
||||
<el-form ref="formRef" :model="formData">
|
||||
<el-table :data="formData.configList">
|
||||
<el-table-column label="序号" type="index" width="80"></el-table-column>
|
||||
<el-table-column label="活动类型" prop="trainTypeName"></el-table-column>
|
||||
<el-table-column label="活动类型编码" prop="trainType"></el-table-column>
|
||||
<el-table-column label="活动分类" prop="trainTypeName"></el-table-column>
|
||||
<el-table-column label="活动分类编码" prop="trainType"></el-table-column>
|
||||
<el-table-column label="每年可报名次数" prop="annualLimitCount" width="220">
|
||||
<template v-slot="{row}">
|
||||
<el-input-number v-model="row.annualLimitCount" :min="1" :precision="0" :step="1"></el-input-number>
|
||||
@@ -33,7 +45,8 @@ layout("/layouts/platform.html"){
|
||||
</el-form>
|
||||
|
||||
<div style="margin-top: 20px; text-align: right">
|
||||
<el-button type="primary" @click="onSave">保 存</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -49,6 +62,9 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack: function () {
|
||||
commonUtil.pjaxPush('/platform/trainSignUp/type')
|
||||
},
|
||||
fetchData: function () {
|
||||
this.$axios.post('/platform/trainSignUp/annualLimit/listData').then((res) => {
|
||||
if (res.code === 0) {
|
||||
@@ -63,10 +79,10 @@ layout("/layouts/platform.html"){
|
||||
return !item.annualLimitCount || item.annualLimitCount <= 0
|
||||
})
|
||||
if (hasInvalidData) {
|
||||
this.$message.warning('每年可报名次数必须大于0')
|
||||
this.$message.warning('每年可报名次数必须大于 0')
|
||||
return
|
||||
}
|
||||
// 页面直接按活动类型整表提交,后端会按活动类型编码进行新增或更新。
|
||||
// 页面直接按活动分类整表提交,后端会按活动分类编码进行新增或更新。
|
||||
this.$axios.post('/platform/trainSignUp/annualLimit/save', {
|
||||
configList: JSON.stringify(this.formData.configList)
|
||||
}).then((res) => {
|
||||
|
||||
@@ -2,9 +2,9 @@ const apply = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<el-row :gutter="20" class="mb10">
|
||||
<el-col :span="24" style="text-align: center">
|
||||
<div style="font-size: large;color: #303133">
|
||||
您正在预约【<span style="color: #409EFF">{{ row.name }}</span>】活动场地
|
||||
<el-col :span="24" style="text-align: center;">
|
||||
<div style="font-size: large; color: #303133;">
|
||||
您正在预约【<span style="color: #409EFF;">{{ row.name }}</span>】活动场地
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -28,26 +28,33 @@ const apply = {
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预约类型" prop="reserveType">
|
||||
<el-radio-group v-model="formData.reserveType" @change="reserveTypeChange">
|
||||
<el-radio-group v-if="availableReserveTypes.length" v-model="formData.reserveType" @change="reserveTypeChange" style="display: none;">
|
||||
<el-radio-button label="union">分工会预约</el-radio-button>
|
||||
<el-radio-button label="club">协会预约</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-radio-group v-if="availableReserveTypes.length" v-model="formData.reserveType" @change="reserveTypeChange">
|
||||
<el-radio-button
|
||||
v-for="item in availableReserveTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span v-else style="color: #909399;">当前无可用的预约权限</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分工会" prop="applyUnionName" v-if="formData.reserveType === 'union'">
|
||||
<el-input readonly v-model="formData.applyUnionName"
|
||||
placeholder="自动读取当前登录人的分工会"></el-input>
|
||||
<el-col :span="12" v-if="formData.reserveType === 'union'">
|
||||
<el-form-item label="分工会" prop="applyUnionName">
|
||||
<el-input readonly v-model="formData.applyUnionName" placeholder="自动读取当前登录人的分工会"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="formData.reserveType === 'club'">
|
||||
<el-form-item
|
||||
label="协会"
|
||||
prop="clubId"
|
||||
v-if="formData.reserveType === 'club'"
|
||||
:rules="[{ required: true, message: '请选择您管理的协会', trigger: ['change', 'blur'] }]">
|
||||
<el-select v-model="formData.clubId" placeholder="请选择您管理的协会" filterable clearable
|
||||
style="width: 100%" @change="clubChange">
|
||||
style="width: 100%;" @change="clubChange">
|
||||
<el-option
|
||||
v-for="item in clubOptions"
|
||||
:key="item.id"
|
||||
@@ -62,6 +69,81 @@ const apply = {
|
||||
<el-input maxlength="11" placeholder="请输入联系电话" v-model="formData.applyMobile"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<div class="apply-visual-panel">
|
||||
<div class="apply-visual-panel__toolbar">
|
||||
<div>
|
||||
<div class="apply-visual-panel__title">可预约时段</div>
|
||||
<div class="apply-visual-panel__subtitle">{{ scheduleRuleText }}</div>
|
||||
</div>
|
||||
<div class="apply-visual-panel__actions">
|
||||
<el-tag size="small" type="success">{{ reserveModeText }}</el-tag>
|
||||
<el-date-picker
|
||||
v-model="scheduleDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择预约日期"
|
||||
:picker-options="{ disabledDate: isDisabledDate }"
|
||||
@change="onScheduleDateChange">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="apply-visual-panel__legend">
|
||||
<span class="apply-legend__item"><i class="apply-legend__dot apply-legend__dot--available"></i>未预约</span>
|
||||
<span class="apply-legend__item"><i class="apply-legend__dot apply-legend__dot--reserved"></i>已预约</span>
|
||||
<span class="apply-legend__item"><i class="apply-legend__dot apply-legend__dot--selected"></i>已选择</span>
|
||||
<span class="apply-legend__item"><i class="apply-legend__dot apply-legend__dot--closed"></i>不可预约</span>
|
||||
</div>
|
||||
|
||||
<el-skeleton :loading="availabilityLoading" animated>
|
||||
<template slot="template">
|
||||
<div style="padding-top: 8px;">
|
||||
<el-skeleton-item variant="p" style="width: 100%; height: 80px;"></el-skeleton-item>
|
||||
</div>
|
||||
</template>
|
||||
<template>
|
||||
<el-empty v-if="!availabilityBlocks.length" description="当前日期暂无可选时段"></el-empty>
|
||||
|
||||
<div v-else-if="isSegmentedMode">
|
||||
<div v-for="group in segmentedBlockGroups" :key="group.groupIndex" class="apply-block-group">
|
||||
<div class="apply-block-group__title">{{ group.groupLabel }}</div>
|
||||
<div class="apply-block-grid">
|
||||
<button
|
||||
v-for="block in group.blocks"
|
||||
:key="block.key"
|
||||
type="button"
|
||||
:class="getBlockClass(block)"
|
||||
@click="onAvailabilityBlockClick(block)">
|
||||
<span>{{ block.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="apply-block-group">
|
||||
<div class="apply-block-group__title">全天候时段</div>
|
||||
<div class="apply-block-grid">
|
||||
<button
|
||||
v-for="block in availabilityBlocks"
|
||||
:key="block.key"
|
||||
type="button"
|
||||
:class="getBlockClass(block)"
|
||||
@click="onAvailabilityBlockClick(block)">
|
||||
<span>{{ block.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
|
||||
<div class="apply-visual-panel__summary" v-if="selectionSummary">
|
||||
当前选择:{{ selectionSummary }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预约开始时间" prop="reserveStartTime">
|
||||
<el-date-picker
|
||||
@@ -71,7 +153,7 @@ const apply = {
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约开始时间"
|
||||
:picker-options="startPickerOptions"
|
||||
style="width: 100%"
|
||||
style="width: 100%;"
|
||||
@change="timeFieldChange('reserveStartTime')">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
@@ -85,21 +167,30 @@ const apply = {
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约结束时间"
|
||||
:picker-options="endPickerOptions"
|
||||
style="width: 100%"
|
||||
style="width: 100%;"
|
||||
@change="timeFieldChange('reserveEndTime')">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="批量预约">
|
||||
<div style="display:flex;align-items:flex-start;column-gap:12px;flex-wrap:wrap;line-height:1.7;">
|
||||
<div style="display: flex; align-items: flex-start; column-gap: 12px; flex-wrap: wrap; line-height: 1.7;">
|
||||
<el-checkbox v-model="yearlyReserve">预约本年后续每周同一时段</el-checkbox>
|
||||
<span style="color:#909399;">
|
||||
<span style="color: #909399;">
|
||||
例如先选某个周一 09:00-11:00,勾选后会自动预约本年剩余所有周一的这个时段。
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="yearlyReserve && yearlyReserveSummary" style="margin-top:8px;color:#E6A23C;">
|
||||
{{ yearlyReserveSummary }}
|
||||
<div v-if="yearlyReserve" style="margin-top: 10px; max-width: 320px;">
|
||||
<el-date-picker
|
||||
v-model="formData.yearlyReserveEndDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择批量预约截止日期"
|
||||
:picker-options="yearlyReserveEndDatePickerOptions">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
<div v-if="yearlyReserve && batchReserveSummary" style="margin-top: 8px; color: #E6A23C;">
|
||||
{{ batchReserveSummary }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -124,20 +215,30 @@ const apply = {
|
||||
row: {},
|
||||
clubOptions: [],
|
||||
yearlyReserve: false,
|
||||
scheduleDate: '',
|
||||
availabilityLoading: false,
|
||||
selectedBlockKeys: [],
|
||||
timeLimitConfig: {
|
||||
filterHolidays: false,
|
||||
holidayList: [],
|
||||
notApplyTimeList: [],
|
||||
},
|
||||
availabilityData: {
|
||||
date: '',
|
||||
reserveTimeType: 1,
|
||||
reserveTimeTypeName: '分段预约',
|
||||
blocks: [],
|
||||
},
|
||||
pickerRefreshKey: 0,
|
||||
formData: {
|
||||
reserveType: 'union',
|
||||
reserveType: '',
|
||||
applyUnionId: '',
|
||||
applyUnionName: '',
|
||||
clubId: '',
|
||||
clubName: '',
|
||||
reserveStartTime: '',
|
||||
reserveEndTime: '',
|
||||
yearlyReserveEndDate: '',
|
||||
joinCount: 1,
|
||||
applyCause: '',
|
||||
},
|
||||
@@ -152,17 +253,77 @@ const apply = {
|
||||
reserveEndTime: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
joinCount: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
applyCause: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
clubId: [{ validator: (rule, value, callback) => {
|
||||
if (this.formData.reserveType === 'club' && !value) {
|
||||
callback(new Error('请选择您管理的协会'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}, trigger: ['blur', 'change']}],
|
||||
clubId: [{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.formData.reserveType === 'club' && !value) {
|
||||
callback(new Error('请选择您管理的协会'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canApplyUnion() {
|
||||
return this.$auth.hasPermission('siteCug.apply.union')
|
||||
},
|
||||
canApplyClub() {
|
||||
return this.$auth.hasPermission('siteCug.apply.club')
|
||||
},
|
||||
availableReserveTypes() {
|
||||
const result = []
|
||||
if (this.canApplyUnion) {
|
||||
result.push('union')
|
||||
}
|
||||
if (this.canApplyClub) {
|
||||
result.push('club')
|
||||
}
|
||||
return result
|
||||
},
|
||||
availableReserveTypeOptions() {
|
||||
const result = []
|
||||
if (this.canApplyUnion) {
|
||||
result.push({value: 'union', label: '分工会预约'})
|
||||
}
|
||||
if (this.canApplyClub) {
|
||||
result.push({value: 'club', label: '协会预约'})
|
||||
}
|
||||
return result
|
||||
},
|
||||
availabilityBlocks() {
|
||||
return Array.isArray(this.availabilityData.blocks) ? this.availabilityData.blocks : []
|
||||
},
|
||||
isSegmentedMode() {
|
||||
return Number(this.availabilityData.reserveTimeType || this.row.reserveTimeType || 1) === 1
|
||||
},
|
||||
reserveModeText() {
|
||||
return this.availabilityData.reserveTimeTypeName || (this.isSegmentedMode ? '分段预约' : '全天候预约')
|
||||
},
|
||||
scheduleRuleText() {
|
||||
if (this.isSegmentedMode) {
|
||||
return '分段预约场地需按场次时间单位选择一个或多个连续时段,不能跨间隔跳选。'
|
||||
}
|
||||
return '全天候预约场地需按预约时间单位选择一个或多个连续时段,开始和结束时间会自动回填。'
|
||||
},
|
||||
segmentedBlockGroups() {
|
||||
const groups = []
|
||||
this.availabilityBlocks.forEach(block => {
|
||||
let target = groups.find(item => item.groupIndex === block.groupIndex)
|
||||
if (!target) {
|
||||
target = {
|
||||
groupIndex: block.groupIndex,
|
||||
groupLabel: block.groupLabel,
|
||||
blocks: [],
|
||||
}
|
||||
groups.push(target)
|
||||
}
|
||||
target.blocks.push(block)
|
||||
})
|
||||
return groups
|
||||
},
|
||||
startPickerOptions() {
|
||||
return {
|
||||
disabledDate: (time) => this.isDisabledDate(time),
|
||||
@@ -175,27 +336,38 @@ const apply = {
|
||||
selectableRange: this.buildSelectableRange(this.formData.reserveEndTime),
|
||||
}
|
||||
},
|
||||
yearlyReserveEndDatePickerOptions() {
|
||||
return {
|
||||
disabledDate: (time) => {
|
||||
if (!this.formData.reserveStartTime) {
|
||||
return false
|
||||
}
|
||||
return this.$moment(time).format('YYYY-MM-DD') < this.formData.reserveStartTime.slice(0, 10)
|
||||
},
|
||||
}
|
||||
},
|
||||
yearlyReserveDates() {
|
||||
if (!this.yearlyReserve || !this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
if (!this.yearlyReserve || !this.formData.reserveStartTime || !this.formData.reserveEndTime || !this.formData.yearlyReserveEndDate) {
|
||||
return []
|
||||
}
|
||||
const start = this.$moment(this.formData.reserveStartTime)
|
||||
const end = this.$moment(this.formData.reserveEndTime)
|
||||
if (!start.isValid() || !end.isValid() || !end.isAfter(start)) {
|
||||
const reserveEndDate = this.$moment(this.formData.yearlyReserveEndDate, 'YYYY-MM-DD')
|
||||
if (!start.isValid() || !end.isValid() || !end.isAfter(start) || !reserveEndDate.isValid() || reserveEndDate.isBefore(start, 'day')) {
|
||||
return []
|
||||
}
|
||||
const result = []
|
||||
const current = start.clone().startOf('day')
|
||||
const endOfYear = start.clone().endOf('year').startOf('day')
|
||||
const reserveEndDay = reserveEndDate.clone().startOf('day')
|
||||
const targetWeekDay = start.day()
|
||||
const startClock = start.format('HH:mm:ss')
|
||||
const endClock = end.format('HH:mm:ss')
|
||||
while (current.isSameOrBefore(endOfYear, 'day')) {
|
||||
while (current.isSameOrBefore(reserveEndDay, 'day')) {
|
||||
if (current.day() === targetWeekDay) {
|
||||
const day = current.format('YYYY-MM-DD')
|
||||
const startTime = day + ' ' + startClock
|
||||
const endTime = day + ' ' + endClock
|
||||
if (this.validateTimeLimit(false, startTime, endTime)) {
|
||||
if (this.validateTimeLimit(false, startTime, endTime) && this.validateGraphSelection(false, startTime, endTime, day)) {
|
||||
result.push(day)
|
||||
}
|
||||
}
|
||||
@@ -203,6 +375,21 @@ const apply = {
|
||||
}
|
||||
return result
|
||||
},
|
||||
batchReserveSummary() {
|
||||
if (!this.yearlyReserve) {
|
||||
return ''
|
||||
}
|
||||
if (!this.formData.yearlyReserveEndDate) {
|
||||
return '请选择批量预约截止日期'
|
||||
}
|
||||
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
return '请先选择开始和结束时间,再批量预约截止日期内的同星期时段'
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
return '当前时间段在截止日期内无法生成可批量预约的日期'
|
||||
}
|
||||
return '将从 ' + this.yearlyReserveDates[0] + ' 开始,自动预约到 ' + this.formData.yearlyReserveEndDate + ',共 ' + this.yearlyReserveDates.length + ' 个时段'
|
||||
},
|
||||
yearlyReserveSummary() {
|
||||
if (!this.yearlyReserve) {
|
||||
return ''
|
||||
@@ -215,13 +402,39 @@ const apply = {
|
||||
}
|
||||
return '将从 ' + this.yearlyReserveDates[0] + ' 开始,自动预约到 ' + this.yearlyReserveDates[this.yearlyReserveDates.length - 1] + ',共 ' + this.yearlyReserveDates.length + ' 个时段'
|
||||
},
|
||||
batchReserveSummary() {
|
||||
if (!this.yearlyReserve) {
|
||||
return ''
|
||||
}
|
||||
if (!this.formData.yearlyReserveEndDate) {
|
||||
return '请选择批量预约截止日期'
|
||||
}
|
||||
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
return '请先选择开始和结束时间,再设置批量预约截止日期'
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
return '当前截止日期内没有可批量预约的同星期时段'
|
||||
}
|
||||
return '将从 ' + this.formData.reserveStartTime.slice(0, 10) + ' 开始,自动预约到 ' + this.formData.yearlyReserveEndDate
|
||||
},
|
||||
selectionSummary() {
|
||||
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
return ''
|
||||
}
|
||||
return this.formData.reserveStartTime + ' 至 ' + this.formData.reserveEndTime
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
yearlyReserve(val) {
|
||||
if (val) {
|
||||
this.syncYearlyReserveEndDate(true)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onOpen(row) {
|
||||
this.row = row
|
||||
this.clubOptions = []
|
||||
this.yearlyReserve = false
|
||||
this.formData = {
|
||||
createDefaultFormData(row) {
|
||||
const reserveType = this.getDefaultReserveType()
|
||||
return {
|
||||
siteId: row.id,
|
||||
applyUserId: this.$store.state.user.id,
|
||||
applyUserName: this.$store.state.user.username,
|
||||
@@ -229,18 +442,71 @@ const apply = {
|
||||
applyLoginName: this.$store.state.user.loginname,
|
||||
applyUnitId: this.$store.state.user?.unit?.id,
|
||||
applyUnitName: this.$store.state.user?.unit?.name,
|
||||
reserveType: 'union',
|
||||
applyUnionId: this.$store.state.user?.union?.id,
|
||||
applyUnionName: this.$store.state.user?.union?.name,
|
||||
reserveType,
|
||||
applyUnionId: reserveType === 'union' ? (this.$store.state.user?.union?.id || '') : '',
|
||||
applyUnionName: reserveType === 'union' ? (this.$store.state.user?.union?.name || '') : '',
|
||||
clubId: '',
|
||||
clubName: '',
|
||||
applyMobile: this.$store.state.user.mobile,
|
||||
reserveStartTime: '',
|
||||
reserveEndTime: '',
|
||||
yearlyReserveEndDate: '',
|
||||
joinCount: 1,
|
||||
applyCause: '',
|
||||
}
|
||||
},
|
||||
getDefaultReserveType() {
|
||||
return this.availableReserveTypes[0] || ''
|
||||
},
|
||||
async onOpen(row) {
|
||||
this.row = row || {}
|
||||
this.clubOptions = []
|
||||
this.yearlyReserve = false
|
||||
this.selectedBlockKeys = []
|
||||
this.scheduleDate = ''
|
||||
this.availabilityData = {
|
||||
date: '',
|
||||
reserveTimeType: Number(row.reserveTimeType || 1),
|
||||
reserveTimeTypeName: row.reserveTimeTypeName || (Number(row.reserveTimeType || 1) === 2 ? '全天候预约' : '分段预约'),
|
||||
blocks: [],
|
||||
}
|
||||
this.formData = this.createDefaultFormData(row || {})
|
||||
if (this.formData.reserveType) {
|
||||
await this.reserveTypeChange(this.formData.reserveType)
|
||||
}
|
||||
this.syncYearlyReserveEndDate(true)
|
||||
await this.queryTimeLimitConfig()
|
||||
this.scheduleDate = this.getInitialScheduleDate()
|
||||
await this.queryAvailability()
|
||||
},
|
||||
getDefaultYearlyReserveEndDate() {
|
||||
const base = this.formData.reserveStartTime
|
||||
? this.$moment(this.formData.reserveStartTime)
|
||||
: (this.scheduleDate ? this.$moment(this.scheduleDate, 'YYYY-MM-DD') : this.$moment())
|
||||
if (!base.isValid()) {
|
||||
return this.$moment().endOf('month').format('YYYY-MM-DD')
|
||||
}
|
||||
return base.clone().endOf('month').format('YYYY-MM-DD')
|
||||
},
|
||||
syncYearlyReserveEndDate(force = false) {
|
||||
const defaultDate = this.getDefaultYearlyReserveEndDate()
|
||||
if (force || !this.formData.yearlyReserveEndDate) {
|
||||
this.$set(this.formData, 'yearlyReserveEndDate', defaultDate)
|
||||
return
|
||||
}
|
||||
if (this.formData.reserveStartTime && this.formData.yearlyReserveEndDate < this.formData.reserveStartTime.slice(0, 10)) {
|
||||
this.$set(this.formData, 'yearlyReserveEndDate', defaultDate)
|
||||
}
|
||||
},
|
||||
getInitialScheduleDate() {
|
||||
let current = this.$moment().startOf('day')
|
||||
for (let i = 0; i < 90; i++) {
|
||||
if (!this.isDisabledDate(current.toDate())) {
|
||||
return current.format('YYYY-MM-DD')
|
||||
}
|
||||
current = current.add(1, 'day')
|
||||
}
|
||||
return this.$moment().add(1, 'day').format('YYYY-MM-DD')
|
||||
},
|
||||
async queryTimeLimitConfig() {
|
||||
const fallback = {
|
||||
@@ -266,7 +532,57 @@ const apply = {
|
||||
}
|
||||
this.pickerRefreshKey += 1
|
||||
},
|
||||
async queryAvailability(date) {
|
||||
const targetDate = date || this.scheduleDate
|
||||
if (!targetDate || !this.row.id) {
|
||||
this.availabilityData.blocks = []
|
||||
return
|
||||
}
|
||||
this.availabilityLoading = true
|
||||
try {
|
||||
const res = await this.$axios.post('/platform/siteCug/apply/availability', {
|
||||
siteId: this.row.id,
|
||||
date: targetDate,
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
this.availabilityData = {
|
||||
date: res.data.date || targetDate,
|
||||
reserveTimeType: Number(res.data.reserveTimeType || this.row.reserveTimeType || 1),
|
||||
reserveTimeTypeName: res.data.reserveTimeTypeName || this.reserveModeText,
|
||||
blocks: Array.isArray(res.data.blocks) ? res.data.blocks : [],
|
||||
}
|
||||
} else {
|
||||
this.availabilityData = {
|
||||
date: targetDate,
|
||||
reserveTimeType: Number(this.row.reserveTimeType || 1),
|
||||
reserveTimeTypeName: Number(this.row.reserveTimeType || 1) === 2 ? '全天候预约' : '分段预约',
|
||||
blocks: [],
|
||||
}
|
||||
this.$message.warning((res && res.msg) || '预约时段加载失败')
|
||||
}
|
||||
} catch (e) {
|
||||
this.availabilityData = {
|
||||
date: targetDate,
|
||||
reserveTimeType: Number(this.row.reserveTimeType || 1),
|
||||
reserveTimeTypeName: Number(this.row.reserveTimeType || 1) === 2 ? '全天候预约' : '分段预约',
|
||||
blocks: [],
|
||||
}
|
||||
this.$message.warning('预约时段加载失败,请稍后重试')
|
||||
} finally {
|
||||
this.availabilityLoading = false
|
||||
this.syncSelectionFromFields()
|
||||
}
|
||||
},
|
||||
async onScheduleDateChange(value) {
|
||||
this.scheduleDate = value || ''
|
||||
this.clearSelection(true)
|
||||
await this.queryAvailability(value)
|
||||
},
|
||||
async reserveTypeChange(value) {
|
||||
if (!this.availableReserveTypes.includes(value)) {
|
||||
this.formData.reserveType = this.getDefaultReserveType()
|
||||
return
|
||||
}
|
||||
if (value === 'union') {
|
||||
this.formData.applyUnionId = this.$store.state.user?.union?.id || ''
|
||||
this.formData.applyUnionName = this.$store.state.user?.union?.name || ''
|
||||
@@ -288,19 +604,44 @@ const apply = {
|
||||
try {
|
||||
const res = await this.$axios.post('/platform/club/examine/apply/getClubsByRole')
|
||||
this.clubOptions = Array.isArray(res.data) ? res.data : []
|
||||
this.syncManagedClubSelection()
|
||||
if (!this.clubOptions.length) {
|
||||
this.$message.warning('您当前没有可预约的协会管理权限')
|
||||
}
|
||||
} catch (e) {
|
||||
this.clubOptions = []
|
||||
this.formData.clubId = ''
|
||||
this.formData.clubName = ''
|
||||
this.$message.warning('协会列表加载失败,请稍后重试')
|
||||
}
|
||||
},
|
||||
syncManagedClubSelection() {
|
||||
if (!this.clubOptions.length) {
|
||||
this.formData.clubId = ''
|
||||
this.formData.clubName = ''
|
||||
return
|
||||
}
|
||||
const currentClub = this.clubOptions.find(item => item.id === this.formData.clubId)
|
||||
if (currentClub) {
|
||||
this.formData.clubName = currentClub.clubName || ''
|
||||
return
|
||||
}
|
||||
if (this.clubOptions.length === 1) {
|
||||
this.formData.clubId = this.clubOptions[0].id
|
||||
this.formData.clubName = this.clubOptions[0].clubName || ''
|
||||
return
|
||||
}
|
||||
this.formData.clubId = ''
|
||||
this.formData.clubName = ''
|
||||
},
|
||||
clubChange(clubId) {
|
||||
const club = this.clubOptions.find(item => item.id === clubId)
|
||||
this.formData.clubName = club ? club.clubName : ''
|
||||
},
|
||||
async normalizeReserveTypeData() {
|
||||
if (!this.availableReserveTypes.includes(this.formData.reserveType)) {
|
||||
this.formData.reserveType = this.getDefaultReserveType()
|
||||
}
|
||||
if (this.formData.reserveType === 'union') {
|
||||
this.formData.applyUnionId = this.$store.state.user?.union?.id || ''
|
||||
this.formData.applyUnionName = this.$store.state.user?.union?.name || ''
|
||||
@@ -417,6 +758,15 @@ const apply = {
|
||||
}
|
||||
const start = this.$moment(startTime)
|
||||
const end = this.$moment(endTime)
|
||||
if (!start.isValid() || !end.isValid()) {
|
||||
return false
|
||||
}
|
||||
if (start.format('YYYY-MM-DD') !== end.format('YYYY-MM-DD')) {
|
||||
if (showMessage) {
|
||||
this.$message.warning('预约时间必须在同一天内')
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (this.isDateTimeBlocked(startTime) || this.isDateTimeBlocked(endTime)) {
|
||||
if (showMessage) {
|
||||
this.$message.warning('预约时间不能选择周末、节假日或禁用时间')
|
||||
@@ -437,9 +787,173 @@ const apply = {
|
||||
}
|
||||
return true
|
||||
},
|
||||
timeFieldChange(field) {
|
||||
clearSelection(clearTimeFields = false) {
|
||||
this.selectedBlockKeys = []
|
||||
if (clearTimeFields) {
|
||||
this.formData.reserveStartTime = ''
|
||||
this.formData.reserveEndTime = ''
|
||||
}
|
||||
},
|
||||
getSelectedIndices() {
|
||||
return this.availabilityBlocks
|
||||
.map((block, index) => this.selectedBlockKeys.includes(block.key) ? index : -1)
|
||||
.filter(index => index > -1)
|
||||
},
|
||||
getBlockClass(block) {
|
||||
return {
|
||||
'apply-block': true,
|
||||
'apply-block--available': block.status === 'available' && !this.selectedBlockKeys.includes(block.key),
|
||||
'apply-block--reserved': block.status === 'reserved',
|
||||
'apply-block--closed': block.status === 'closed',
|
||||
'apply-block--selected': this.selectedBlockKeys.includes(block.key),
|
||||
}
|
||||
},
|
||||
onAvailabilityBlockClick(block) {
|
||||
if (!block || !block.key) {
|
||||
return
|
||||
}
|
||||
if (block.status !== 'available' && !this.selectedBlockKeys.includes(block.key)) {
|
||||
this.$message.warning(block.status === 'reserved' ? '该时段已被预约,请选择其他时段' : '该时段当前不可预约')
|
||||
return
|
||||
}
|
||||
const index = this.availabilityBlocks.findIndex(item => item.key === block.key)
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
this.toggleContinuousSelection(index)
|
||||
},
|
||||
toggleContinuousSelection(index) {
|
||||
const selectedIndices = this.getSelectedIndices()
|
||||
if (!selectedIndices.length) {
|
||||
this.applySelectionRange(index, index)
|
||||
return
|
||||
}
|
||||
const selectedStart = selectedIndices[0]
|
||||
const selectedEnd = selectedIndices[selectedIndices.length - 1]
|
||||
if (selectedIndices.includes(index)) {
|
||||
if (selectedIndices.length === 1) {
|
||||
this.clearSelection(true)
|
||||
return
|
||||
}
|
||||
if (index === selectedStart) {
|
||||
this.applySelectionRange(selectedStart + 1, selectedEnd)
|
||||
return
|
||||
}
|
||||
if (index === selectedEnd) {
|
||||
this.applySelectionRange(selectedStart, selectedEnd - 1)
|
||||
return
|
||||
}
|
||||
this.applySelectionRange(index, index)
|
||||
return
|
||||
}
|
||||
const nextStart = Math.min(index, selectedStart)
|
||||
const nextEnd = Math.max(index, selectedEnd)
|
||||
if (!this.canSelectRange(nextStart, nextEnd)) {
|
||||
this.$message.warning(this.isSegmentedMode ? '分段预约只能连续选择一个或多个时间段,不能跨间隔跳选' : '全天候预约只能连续选择一个时间段')
|
||||
return
|
||||
}
|
||||
this.applySelectionRange(nextStart, nextEnd)
|
||||
},
|
||||
canSelectRange(startIndex, endIndex) {
|
||||
if (startIndex < 0 || endIndex >= this.availabilityBlocks.length || startIndex > endIndex) {
|
||||
return false
|
||||
}
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const block = this.availabilityBlocks[i]
|
||||
if (!block || block.status !== 'available') {
|
||||
return false
|
||||
}
|
||||
if (i > startIndex) {
|
||||
const prev = this.availabilityBlocks[i - 1]
|
||||
if (!prev || prev.endDateTime !== block.startDateTime) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
applySelectionRange(startIndex, endIndex) {
|
||||
const selected = this.availabilityBlocks.slice(startIndex, endIndex + 1)
|
||||
this.selectedBlockKeys = selected.map(item => item.key)
|
||||
if (selected.length) {
|
||||
this.formData.reserveStartTime = selected[0].startDateTime
|
||||
this.formData.reserveEndTime = selected[selected.length - 1].endDateTime
|
||||
this.scheduleDate = selected[0].startDateTime.slice(0, 10)
|
||||
}
|
||||
},
|
||||
findRangeIndicesByFields(startTime, endTime) {
|
||||
if (!startTime || !endTime) {
|
||||
return null
|
||||
}
|
||||
for (let i = 0; i < this.availabilityBlocks.length; i++) {
|
||||
const block = this.availabilityBlocks[i]
|
||||
if (block.status !== 'available' || block.startDateTime !== startTime) {
|
||||
continue
|
||||
}
|
||||
let currentEnd = block.endDateTime
|
||||
if (currentEnd === endTime) {
|
||||
return [i, i]
|
||||
}
|
||||
for (let j = i + 1; j < this.availabilityBlocks.length; j++) {
|
||||
const prev = this.availabilityBlocks[j - 1]
|
||||
const next = this.availabilityBlocks[j]
|
||||
if (next.status !== 'available' || prev.endDateTime !== next.startDateTime) {
|
||||
break
|
||||
}
|
||||
currentEnd = next.endDateTime
|
||||
if (currentEnd === endTime) {
|
||||
return [i, j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
syncSelectionFromFields() {
|
||||
const startTime = this.formData.reserveStartTime
|
||||
const endTime = this.formData.reserveEndTime
|
||||
if (!startTime || !endTime) {
|
||||
this.selectedBlockKeys = []
|
||||
return
|
||||
}
|
||||
if (startTime.slice(0, 10) !== this.scheduleDate || endTime.slice(0, 10) !== this.scheduleDate) {
|
||||
this.selectedBlockKeys = []
|
||||
return
|
||||
}
|
||||
const range = this.findRangeIndicesByFields(startTime, endTime)
|
||||
if (!range) {
|
||||
this.selectedBlockKeys = []
|
||||
return
|
||||
}
|
||||
this.selectedBlockKeys = this.availabilityBlocks.slice(range[0], range[1] + 1).map(item => item.key)
|
||||
},
|
||||
validateGraphSelection(showMessage = true, startTime = this.formData.reserveStartTime, endTime = this.formData.reserveEndTime, targetDay = this.scheduleDate) {
|
||||
if (!startTime || !endTime) {
|
||||
return true
|
||||
}
|
||||
if (!targetDay || startTime.slice(0, 10) !== targetDay || endTime.slice(0, 10) !== targetDay) {
|
||||
if (showMessage) {
|
||||
this.$message.warning('预约时间必须与当前选择的预约日期一致')
|
||||
}
|
||||
return false
|
||||
}
|
||||
const range = this.findRangeIndicesByFields(startTime, endTime)
|
||||
if (range) {
|
||||
return true
|
||||
}
|
||||
if (showMessage) {
|
||||
this.$message.warning(this.isSegmentedMode
|
||||
? '分段预约请按场次时间单位选择一个或多个连续时段'
|
||||
: '全天候预约请按预约时间单位选择一个或多个连续时段')
|
||||
}
|
||||
return false
|
||||
},
|
||||
async timeFieldChange(field) {
|
||||
const value = this.formData[field]
|
||||
if (!value) {
|
||||
if (field === 'reserveStartTime') {
|
||||
this.syncYearlyReserveEndDate(true)
|
||||
}
|
||||
this.syncSelectionFromFields()
|
||||
return
|
||||
}
|
||||
if (this.isDateTimeBlocked(value)) {
|
||||
@@ -447,16 +961,38 @@ const apply = {
|
||||
this.$set(this.formData, field, '')
|
||||
return
|
||||
}
|
||||
const day = this.$moment(value).format('YYYY-MM-DD')
|
||||
if (day !== this.scheduleDate) {
|
||||
this.scheduleDate = day
|
||||
await this.queryAvailability(day)
|
||||
}
|
||||
if (this.formData.reserveStartTime && this.formData.reserveEndTime && !this.validateTimeLimit()) {
|
||||
this.$set(this.formData, field, '')
|
||||
return
|
||||
}
|
||||
if (field === 'reserveStartTime') {
|
||||
this.syncYearlyReserveEndDate()
|
||||
}
|
||||
this.syncSelectionFromFields()
|
||||
},
|
||||
validateYearlyReserve() {
|
||||
if (!this.yearlyReserve) {
|
||||
return true
|
||||
}
|
||||
if (!this.formData.yearlyReserveEndDate) {
|
||||
this.$message.warning('请选择批量预约截止日期')
|
||||
return false
|
||||
}
|
||||
if (this.formData.reserveStartTime && this.formData.yearlyReserveEndDate < this.formData.reserveStartTime.slice(0, 10)) {
|
||||
this.$message.warning('批量预约截止日期不能早于开始日期')
|
||||
return false
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
this.$message.warning('?????????????????????')
|
||||
this.$message.warning('当前选择的时间段在截止日期内无法生成可批量预约的日期')
|
||||
return false
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
this.$message.warning('当前选择的时间段无法生成本年批量预约日期')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -465,10 +1001,58 @@ const apply = {
|
||||
return this.yearlyReserve ? '/platform/siteCug/apply/submitYearly' : '/platform/siteCug/apply/submit'
|
||||
},
|
||||
buildSubmitConfirmMessage() {
|
||||
const baseText = '您已选择 ' + this.formData.reserveStartTime + ' 至 ' + this.formData.reserveEndTime
|
||||
if (!this.yearlyReserve) {
|
||||
return '您确定要提交吗?'
|
||||
return baseText + ',是否确定提交预约?'
|
||||
}
|
||||
return '将一次性预约本年后续 ' + this.yearlyReserveDates.length + ' 个同星期时段,确认提交吗?'
|
||||
return baseText + '。系统将继续预约本年后续 ' + this.yearlyReserveDates.length + ' 个同星期时段,是否确定提交?'
|
||||
},
|
||||
validateYearlyReserve() {
|
||||
if (!this.yearlyReserve) {
|
||||
return true
|
||||
}
|
||||
if (!this.formData.yearlyReserveEndDate) {
|
||||
this.$message.warning('请选择批量预约截止日期')
|
||||
return false
|
||||
}
|
||||
if (this.formData.reserveStartTime && this.formData.yearlyReserveEndDate < this.formData.reserveStartTime.slice(0, 10)) {
|
||||
this.$message.warning('批量预约截止日期不能早于开始日期')
|
||||
return false
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
this.$message.warning('当前选择的时间段在截止日期内无法生成可批量预约的日期')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
buildSubmitConfirmMessage() {
|
||||
const baseText = '您已选择 ' + this.formData.reserveStartTime + ' 至 ' + this.formData.reserveEndTime
|
||||
if (!this.yearlyReserve) {
|
||||
return baseText + ',是否确定提交预约?'
|
||||
}
|
||||
return baseText + '。系统将继续预约到 ' + this.formData.yearlyReserveEndDate + ',共 ' + this.yearlyReserveDates.length + ' 个同星期时段,是否确定提交?'
|
||||
},
|
||||
batchReserveSummary() {
|
||||
if (!this.yearlyReserve) {
|
||||
return ''
|
||||
}
|
||||
if (!this.formData.yearlyReserveEndDate) {
|
||||
return '请选择批量预约截止日期'
|
||||
}
|
||||
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
return '请先选择开始和结束时间,再设置批量预约截止日期'
|
||||
}
|
||||
if (!this.yearlyReserveDates.length) {
|
||||
return '当前截止日期内没有可批量预约的同星期时段'
|
||||
}
|
||||
return '将从 ' + this.yearlyReserveDates[0] + ' 开始,自动预约到 ' + this.formData.yearlyReserveEndDate
|
||||
},
|
||||
buildSubmitConfirmMessage() {
|
||||
const baseText = '您已选择 ' + this.formData.reserveStartTime + ' 至 ' + this.formData.reserveEndTime
|
||||
if (!this.yearlyReserve) {
|
||||
return baseText + ',是否确定提交预约?'
|
||||
}
|
||||
return baseText + '。系统将继续预约到 ' + this.formData.yearlyReserveEndDate + ',是否确定提交?'
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate(async (valid) => {
|
||||
@@ -503,6 +1087,9 @@ const apply = {
|
||||
if (!this.validateTimeLimit()) {
|
||||
return
|
||||
}
|
||||
if (!this.validateGraphSelection()) {
|
||||
return
|
||||
}
|
||||
if (!this.validateYearlyReserve()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,27 +27,71 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<table-tool label="场地列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
<template v-slot="{ row }">
|
||||
<el-button @click="onViewSite(row)" size="mini" type="primary">查看场地</el-button>
|
||||
<el-button @click="onApply(row)" size="mini" type="primary">预约</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="tableData.length > 0" class="site-card-grid">
|
||||
<div v-for="row in tableData" :key="row.id" class="site-card">
|
||||
<div class="site-card__media">
|
||||
<el-image
|
||||
v-if="row.sitePhoto"
|
||||
:preview-src-list="[row.sitePhoto]"
|
||||
:src="row.sitePhoto"
|
||||
fit="cover"
|
||||
class="site-card__image">
|
||||
</el-image>
|
||||
<div v-else class="site-card__image site-card__image--placeholder">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
<span>暂无场地照片</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-card__content">
|
||||
<div class="site-card__header">
|
||||
<div class="site-card__title-wrap">
|
||||
<div class="site-card__title" :title="row.name">{{ row.name || '--' }}</div>
|
||||
<div class="site-card__meta">
|
||||
<i class="el-icon-location-outline"></i>
|
||||
<span :title="row.address">{{ row.address || '暂无场地地址' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-card__actions">
|
||||
<el-tag size="mini" effect="dark" type="success">{{ row.reserveTimeTypeName || '分段预约' }}</el-tag>
|
||||
<el-button size="mini" plain @click="onViewSite(row)">场地详情</el-button>
|
||||
<el-button size="mini" type="primary" @click="onApply(row)">预约</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="site-card__summary">
|
||||
<span>场地类型:{{ row.typeName || '--' }}</span>
|
||||
<span>容纳人数:{{ row.maxNum || 0 }}人</span>
|
||||
<span>联系人:{{ row.contactName || '--' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="site-card__timeline">
|
||||
<div class="site-card__timeline-title">今日预约情况</div>
|
||||
<div class="timeline-bar">
|
||||
<span
|
||||
v-for="(segment, index) in row.timelineSegments || []"
|
||||
:key="row.id + '-segment-' + index"
|
||||
:class="['timeline-bar__segment', 'timeline-bar__segment--' + (segment.status || 'closed')]">
|
||||
</span>
|
||||
</div>
|
||||
<div class="timeline-scale">
|
||||
<span v-for="hour in hourMarks" :key="row.id + '-hour-' + hour">{{ hour }}</span>
|
||||
</div>
|
||||
<div class="timeline-legend">
|
||||
<span class="timeline-legend__item">
|
||||
<i class="timeline-legend__dot timeline-legend__dot--available"></i>未预约
|
||||
</span>
|
||||
<span class="timeline-legend__item">
|
||||
<i class="timeline-legend__dot timeline-legend__dot--reserved"></i>已预约
|
||||
</span>
|
||||
<span class="timeline-legend__item">
|
||||
<i class="timeline-legend__dot timeline-legend__dot--closed"></i>未开放
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无可预约场地"></el-empty>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -74,14 +118,7 @@ layout("/layouts/platform.html"){
|
||||
data() {
|
||||
return {
|
||||
typeOptions: [],
|
||||
tableColumns: [
|
||||
{prop: 'name', label: '场地名称'},
|
||||
{prop: 'address', label: '场地地址'},
|
||||
{prop: 'contactName', label: '联系人'},
|
||||
{prop: 'contactPhone', label: '联系电话'},
|
||||
{prop: 'maxNum', label: '容纳人数'},
|
||||
{prop: 'typeName', label: '场地类型'},
|
||||
],
|
||||
hourMarks: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -120,6 +157,374 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.site-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.site-card {
|
||||
display: flex;
|
||||
min-height: 240px;
|
||||
padding: 18px;
|
||||
border: 1px solid #e8edf5;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||
box-shadow: 0 10px 30px rgba(21, 66, 120, 0.06);
|
||||
}
|
||||
|
||||
.site-card__media {
|
||||
flex: 0 0 180px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
.site-card__image {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.site-card__image--placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #8c9bb1;
|
||||
background: linear-gradient(135deg, #eef4fb 0%, #dde8f6 100%);
|
||||
font-size: 14px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.site-card__image--placeholder i {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.site-card__content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.site-card__title-wrap {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-card__title {
|
||||
overflow: hidden;
|
||||
color: #1f2d3d;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.site-card__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
color: #7f8ea3;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.site-card__meta span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.site-card__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.site-card__summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
color: #4f6277;
|
||||
font-size: 13px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.site-card__timeline {
|
||||
margin-top: auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.site-card__timeline-title {
|
||||
margin-bottom: 10px;
|
||||
color: #25364d;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.timeline-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(48, minmax(0, 1fr));
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.timeline-bar__segment {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: #e5ebf3;
|
||||
}
|
||||
|
||||
.timeline-bar__segment--available {
|
||||
background: #32b56c;
|
||||
}
|
||||
|
||||
.timeline-bar__segment--reserved {
|
||||
background: #9aa6b2;
|
||||
}
|
||||
|
||||
.timeline-bar__segment--closed {
|
||||
background: #e9edf3;
|
||||
}
|
||||
|
||||
.timeline-scale {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
margin-top: 10px;
|
||||
color: #607389;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.timeline-scale span {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.timeline-scale span:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeline-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 12px;
|
||||
color: #607389;
|
||||
font-size: 13px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.timeline-legend__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.timeline-legend__dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.timeline-legend__dot--available {
|
||||
background: #32b56c;
|
||||
}
|
||||
|
||||
.timeline-legend__dot--reserved {
|
||||
background: #9aa6b2;
|
||||
}
|
||||
|
||||
.timeline-legend__dot--closed {
|
||||
background: #e9edf3;
|
||||
border: 1px solid #d6dde8;
|
||||
}
|
||||
|
||||
.apply-visual-panel {
|
||||
margin-bottom: 18px;
|
||||
padding: 18px;
|
||||
border: 1px solid #e8edf5;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f6faff 100%);
|
||||
}
|
||||
|
||||
.apply-visual-panel__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.apply-visual-panel__title {
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.apply-visual-panel__subtitle {
|
||||
margin-top: 6px;
|
||||
color: #708399;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.apply-visual-panel__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.apply-visual-panel__legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 14px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.apply-legend__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #607389;
|
||||
font-size: 13px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.apply-legend__dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.apply-legend__dot--available {
|
||||
background: #32b56c;
|
||||
}
|
||||
|
||||
.apply-legend__dot--reserved {
|
||||
background: #9aa6b2;
|
||||
}
|
||||
|
||||
.apply-legend__dot--selected {
|
||||
background: #2f7df6;
|
||||
}
|
||||
|
||||
.apply-legend__dot--closed {
|
||||
background: #e9edf3;
|
||||
border: 1px solid #d6dde8;
|
||||
}
|
||||
|
||||
.apply-block-group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.apply-block-group__title {
|
||||
margin-bottom: 10px;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.apply-block-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.apply-block {
|
||||
min-height: 40px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.apply-block--available {
|
||||
color: #0f5132;
|
||||
background: #d9f6e5;
|
||||
border-color: #87d7a7;
|
||||
}
|
||||
|
||||
.apply-block--reserved {
|
||||
color: #4b5663;
|
||||
background: #e1e6ec;
|
||||
border-color: #c5ced8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.apply-block--closed {
|
||||
color: #8b97a6;
|
||||
background: #f3f5f8;
|
||||
border-color: #e2e8f0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.apply-block--selected {
|
||||
color: #ffffff;
|
||||
background: #2f7df6;
|
||||
border-color: #2f7df6;
|
||||
box-shadow: 0 6px 16px rgba(47, 125, 246, 0.22);
|
||||
}
|
||||
|
||||
.apply-block:not(.apply-block--reserved):not(.apply-block--closed):hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 16px rgba(30, 72, 124, 0.12);
|
||||
}
|
||||
|
||||
.apply-visual-panel__summary {
|
||||
margin-top: 16px;
|
||||
padding: 12px 14px;
|
||||
color: #1f4e8c;
|
||||
font-size: 13px;
|
||||
background: #eef5ff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1400px) {
|
||||
.site-card-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.site-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.site-card__media {
|
||||
flex: none;
|
||||
margin-right: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.site-card__image {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.site-card__header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.site-card__actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.apply-visual-panel__toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
|
||||
+501
-102
@@ -2,109 +2,226 @@
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="110px" label-position="left">
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="创建人" prop="createUserName">
|
||||
<el-input disabled type="text" v-model="formData.createUserName" maxlength="20" placeholder="请输入创建人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="sortNum" label="排序编号">
|
||||
<el-input v-model="formData.sortNum" placeholder="请输入排序编号" type="number"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="场地基本信息" name="basicInfo">
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="创建人" prop="createUserName">
|
||||
<el-input disabled type="text" v-model="formData.createUserName" maxlength="20" placeholder="请输入创建人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="sortNum" label="排序编号">
|
||||
<el-input v-model="formData.sortNum" placeholder="请输入排序编号" type="number"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地名称" prop="name">
|
||||
<el-input type="text" v-model="formData.name" maxlength="50" placeholder="请输入场地名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地地址" prop="address">
|
||||
<el-input type="text" v-model="formData.address" maxlength="100" placeholder="请输入场地地址"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地名称" prop="name">
|
||||
<el-input type="text" v-model="formData.name" maxlength="50" placeholder="请输入场地名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地地址" prop="address">
|
||||
<el-input type="text" v-model="formData.address" maxlength="100" placeholder="请输入场地地址"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input type="text" v-model="formData.contactName" maxlength="20" placeholder="请输入联系人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input type="text" v-model="formData.contactPhone" maxlength="20" placeholder="请输入联系电话"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input type="text" v-model="formData.contactName" maxlength="20" placeholder="请输入联系人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input type="text" v-model="formData.contactPhone" maxlength="20" placeholder="请输入联系电话"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="容纳人数" prop="maxNum">
|
||||
<el-input v-model="formData.maxNum" placeholder="请输入容纳人数" type="number"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地类型" prop="typeId">
|
||||
<el-select v-model="formData.typeId" clearable filterable placeholder="请选择场地类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in typeList"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="容纳人数" prop="maxNum">
|
||||
<el-input v-model="formData.maxNum" placeholder="请输入容纳人数" type="number"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地类型" prop="typeId">
|
||||
<el-select v-model="formData.typeId" clearable filterable placeholder="请选择场地类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in typeList"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="sexLimit" label="性别限制">
|
||||
<el-radio-group v-model="formData.sexLimit" size="medium">
|
||||
<el-radio-button :label="0">不限制</el-radio-button>
|
||||
<el-radio-button :label="1">男</el-radio-button>
|
||||
<el-radio-button :label="2">女</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-form-item label="场地介绍" prop="introduce">
|
||||
<text-editor v-model="formData.introduce"></text-editor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="state" label="开启状态">
|
||||
<el-radio-group v-model="formData.state" size="medium">
|
||||
<el-radio-button :label="true">开启</el-radio-button>
|
||||
<el-radio-button :label="false">禁用</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="filterHolidays" label="排除节假日">
|
||||
<el-radio-group v-model="formData.filterHolidays" size="medium">
|
||||
<el-radio-button :label="true">是</el-radio-button>
|
||||
<el-radio-button :label="false">否</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-form-item label="场地照片" prop="sitePhoto">
|
||||
<file-upload
|
||||
:value.sync="formData.sitePhoto"
|
||||
:upload_number="1"
|
||||
:upload_size="1024 * 1024 * 10"
|
||||
accept=".jpg,.jpeg,.png"
|
||||
upload_result_category="interval"
|
||||
complete_result>
|
||||
</file-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="禁用时间">
|
||||
<el-button type="primary" @click="openSetUpTime" size="small">点击设置禁用时间</el-button>
|
||||
<span style="color: #c64120">您已设置{{ formData.notApplyTimeList ? formData.notApplyTimeList.length : 0 }}个禁用时间</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-form-item label="场地介绍" prop="introduce">
|
||||
<text-editor v-model="formData.introduce"></text-editor>
|
||||
</el-form-item>
|
||||
<el-tab-pane label="设置场地" name="siteSetting">
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="state" label="开启状态">
|
||||
<el-radio-group v-model="formData.state" size="medium">
|
||||
<el-radio-button :label="true">开启</el-radio-button>
|
||||
<el-radio-button :label="false">禁用</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="sexLimit" label="性别限制">
|
||||
<el-radio-group v-model="formData.sexLimit" size="medium">
|
||||
<el-radio-button :label="0">不限制</el-radio-button>
|
||||
<el-radio-button :label="1">男</el-radio-button>
|
||||
<el-radio-button :label="2">女</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="reserveTimeType" label="预约时间段类型">
|
||||
<el-radio-group v-model="formData.reserveTimeType" @change="handleReserveTimeTypeChange" size="medium">
|
||||
<el-radio-button :label="1">分段预约</el-radio-button>
|
||||
<el-radio-button :label="2">全天候预约</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="filterHolidays" label="排除节假日">
|
||||
<el-radio-group v-model="formData.filterHolidays" size="medium">
|
||||
<el-radio-button :label="true">是</el-radio-button>
|
||||
<el-radio-button :label="false">否</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="禁用时间">
|
||||
<el-button type="primary" @click="openSetUpTime" size="small">点击设置禁用时间</el-button>
|
||||
<span style="color: #c64120">您已设置{{ formData.notApplyTimeList ? formData.notApplyTimeList.length : 0 }}个禁用时间</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">场次信息</el-divider>
|
||||
|
||||
<div v-if="formData.reserveTimeType === 1">
|
||||
<div class="left-span-label">分段预约:可设置一个或多个场次,预约人按拆分后的场次进行预约。</div>
|
||||
<el-table :data="formData.openHours" border size="mini">
|
||||
<el-table-column type="index" label="序号" align="center" header-align="center" width="80"></el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-time-select
|
||||
style="width: 100%"
|
||||
placeholder="开始时间"
|
||||
v-model="row.startTime"
|
||||
@change="checkSegmentedOpenHoursOverlap"
|
||||
:picker-options="{ start: '00:00', step: '00:30', end: '23:59' }">
|
||||
</el-time-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-time-select
|
||||
style="width: 100%"
|
||||
placeholder="结束时间"
|
||||
v-model="row.endTime"
|
||||
@change="checkSegmentedOpenHoursOverlap"
|
||||
:picker-options="{ start: '00:00', step: '00:30', end: '23:59' }">
|
||||
</el-time-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="timeUnit" label="预约时间单位(分钟)" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-input-number style="width: 100%" v-model="row.timeUnit" :min="1" :step="1" :precision="0" placeholder="请输入预约时间单位"></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" header-align="center" width="130">
|
||||
<template slot="header">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addOpenHour">添加场次</el-button>
|
||||
</template>
|
||||
<template v-slot="scope">
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="formData.openHours.length <= 1"
|
||||
@click="formData.openHours.splice(scope.$index, 1)">
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="left-span-label">全天候预约:设置可预约的开始时间、结束时间和预约时间单位(分钟),预约人按设定单位选择连续时段。</div>
|
||||
<el-row :gutter="20" type="flex">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间">
|
||||
<el-time-select
|
||||
style="width: 100%"
|
||||
placeholder="开始时间"
|
||||
v-model="fullDayOpenHour.startTime"
|
||||
:picker-options="{ start: '00:00', step: '00:30', end: '23:59' }">
|
||||
</el-time-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间">
|
||||
<el-time-select
|
||||
style="width: 100%"
|
||||
placeholder="结束时间"
|
||||
v-model="fullDayOpenHour.endTime"
|
||||
:picker-options="{ start: '00:00', step: '00:30', end: '23:59' }">
|
||||
</el-time-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预约时间单位(分钟)">
|
||||
<el-input-number
|
||||
style="width: 100%"
|
||||
v-model="fullDayOpenHour.timeUnit"
|
||||
:min="1"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
placeholder="请输入预约时间单位">
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<div class="mt10" style="color: #909399; font-size: 13px;">
|
||||
暂存:保存当前填写内容并留在本页继续编辑。正式提交:保存后返回列表页。
|
||||
</div>
|
||||
<el-row class="mt10" justify="end" type="flex">
|
||||
<el-button @click="$emit('refresh')">取消</el-button>
|
||||
<el-button @click="onSubmit" type="primary">提交</el-button>
|
||||
<el-button @click="onTempSave" plain type="warning">暂存并继续编辑</el-button>
|
||||
<el-button @click="onSubmit" type="primary">正式提交</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-dialog append-to-body :close-on-click-modal="false" :visible.sync="setUpTimeDialog" title="设置时间">
|
||||
@@ -202,10 +319,17 @@
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'basicInfo',
|
||||
segmentedOpenHoursCache: [{ startTime: '', endTime: '', timeUnit: 30 }],
|
||||
fullDayOpenHourCache: { startTime: '', endTime: '', timeUnit: 30 },
|
||||
formData: {
|
||||
state: true,
|
||||
sexLimit: 0,
|
||||
filterHolidays: false,
|
||||
reserveTimeType: 1,
|
||||
openHours: [{ startTime: '', endTime: '', timeUnit: 30 }],
|
||||
segmentedOpenHours: [{ startTime: '', endTime: '', timeUnit: 30 }],
|
||||
fullDayOpenHour: { startTime: '', endTime: '', timeUnit: 30 },
|
||||
notApplyTimeList: [],
|
||||
createUserName: this.$store.state.user.username,
|
||||
createUserId: this.$store.state.user.id,
|
||||
@@ -222,6 +346,7 @@
|
||||
sexLimit: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
filterHolidays: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
state: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
reserveTimeType: [{required: true, message: '必填', trigger: ['blur', 'change']}],
|
||||
},
|
||||
setUpTimeDialog: false,
|
||||
timeOneKeySet: {
|
||||
@@ -231,7 +356,218 @@
|
||||
multipleSelection: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullDayOpenHour() {
|
||||
if (!Array.isArray(this.formData.openHours)) {
|
||||
this.$set(this.formData, 'openHours', [])
|
||||
}
|
||||
if (this.formData.openHours.length === 0) {
|
||||
this.formData.openHours.push({ startTime: '', endTime: '', timeUnit: 30 })
|
||||
}
|
||||
if (!this.formData.openHours[0].timeUnit || this.formData.openHours[0].timeUnit <= 0) {
|
||||
this.$set(this.formData.openHours[0], 'timeUnit', 30)
|
||||
}
|
||||
return this.formData.openHours[0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeTimeUnit(value, defaultValue = 30) {
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num) || num <= 0) {
|
||||
return defaultValue
|
||||
}
|
||||
return Math.round(num)
|
||||
},
|
||||
syncOpenHoursCache() {
|
||||
if (this.formData.reserveTimeType === 1) {
|
||||
this.segmentedOpenHoursCache = Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0
|
||||
? clone(this.formData.openHours).map(item => ({
|
||||
startTime: item.startTime || '',
|
||||
endTime: item.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(item.timeUnit),
|
||||
}))
|
||||
: [{ startTime: '', endTime: '', timeUnit: 30 }]
|
||||
this.formData.segmentedOpenHours = clone(this.segmentedOpenHoursCache)
|
||||
} else {
|
||||
const first = (this.formData.openHours && this.formData.openHours[0]) || {}
|
||||
this.fullDayOpenHourCache = {
|
||||
startTime: first.startTime || '',
|
||||
endTime: first.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(first.timeUnit),
|
||||
}
|
||||
this.formData.fullDayOpenHour = clone(this.fullDayOpenHourCache)
|
||||
}
|
||||
},
|
||||
buildSubmitFormData() {
|
||||
this.syncOpenHoursCache()
|
||||
const submitData = clone(this.formData)
|
||||
submitData.segmentedOpenHours = clone(this.segmentedOpenHoursCache || [])
|
||||
submitData.fullDayOpenHour = clone(this.fullDayOpenHourCache || { startTime: '', endTime: '', timeUnit: 30 })
|
||||
if (submitData.reserveTimeType === 1) {
|
||||
submitData.openHours = clone(this.segmentedOpenHoursCache || [])
|
||||
} else {
|
||||
submitData.openHours = [clone(this.fullDayOpenHourCache || { startTime: '', endTime: '', timeUnit: 60 })]
|
||||
}
|
||||
return submitData
|
||||
},
|
||||
initOpenHoursCache() {
|
||||
const segmentedOpenHours = Array.isArray(this.formData.segmentedOpenHours) && this.formData.segmentedOpenHours.length > 0
|
||||
? clone(this.formData.segmentedOpenHours)
|
||||
: (Array.isArray(this.formData.openHours) ? clone(this.formData.openHours) : [])
|
||||
const fullDayOpenHour = this.formData.fullDayOpenHour && Object.keys(this.formData.fullDayOpenHour).length > 0
|
||||
? clone(this.formData.fullDayOpenHour)
|
||||
: ((Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0) ? clone(this.formData.openHours[0]) : {})
|
||||
if (this.formData.reserveTimeType === 2) {
|
||||
this.fullDayOpenHourCache = {
|
||||
startTime: fullDayOpenHour.startTime || '',
|
||||
endTime: fullDayOpenHour.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(fullDayOpenHour.timeUnit),
|
||||
}
|
||||
this.segmentedOpenHoursCache = segmentedOpenHours && segmentedOpenHours.length > 0
|
||||
? segmentedOpenHours.map(item => ({
|
||||
startTime: item.startTime || '',
|
||||
endTime: item.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(item.timeUnit),
|
||||
}))
|
||||
: [{ startTime: '', endTime: '', timeUnit: 30 }]
|
||||
this.formData.openHours = [clone(this.fullDayOpenHourCache)]
|
||||
} else {
|
||||
this.segmentedOpenHoursCache = segmentedOpenHours.length > 0
|
||||
? segmentedOpenHours.map(item => ({
|
||||
startTime: item.startTime || '',
|
||||
endTime: item.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(item.timeUnit),
|
||||
}))
|
||||
: [{ startTime: '', endTime: '', timeUnit: 30 }]
|
||||
const first = this.segmentedOpenHoursCache[0] || {}
|
||||
this.fullDayOpenHourCache = {
|
||||
startTime: fullDayOpenHour.startTime || first.startTime || '',
|
||||
endTime: fullDayOpenHour.endTime || first.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(fullDayOpenHour.timeUnit || first.timeUnit),
|
||||
}
|
||||
this.formData.openHours = clone(this.segmentedOpenHoursCache)
|
||||
}
|
||||
this.formData.segmentedOpenHours = clone(this.segmentedOpenHoursCache)
|
||||
this.formData.fullDayOpenHour = clone(this.fullDayOpenHourCache)
|
||||
},
|
||||
addOpenHour() {
|
||||
if (!Array.isArray(this.formData.openHours)) {
|
||||
this.$set(this.formData, 'openHours', [])
|
||||
}
|
||||
this.formData.openHours.push({ startTime: '', endTime: '', timeUnit: 30 })
|
||||
this.segmentedOpenHoursCache = clone(this.formData.openHours)
|
||||
},
|
||||
hasSegmentedOpenHoursOverlap(openHours) {
|
||||
const validHours = (openHours || [])
|
||||
.map((item, index) => ({ ...item, index }))
|
||||
.filter(item => item.startTime && item.endTime && item.startTime < item.endTime)
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime))
|
||||
for (let i = 1; i < validHours.length; i++) {
|
||||
const prev = validHours[i - 1]
|
||||
const current = validHours[i]
|
||||
if (current.startTime < prev.endTime) {
|
||||
return {
|
||||
overlap: true,
|
||||
prevIndex: prev.index,
|
||||
currentIndex: current.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
return { overlap: false }
|
||||
},
|
||||
checkSegmentedOpenHoursOverlap() {
|
||||
if (this.formData.reserveTimeType !== 1) {
|
||||
return false
|
||||
}
|
||||
const result = this.hasSegmentedOpenHoursOverlap(this.formData.openHours)
|
||||
if (result.overlap) {
|
||||
this.$message.warning('第' + (result.prevIndex + 1) + '条和第' + (result.currentIndex + 1) + '条场次时间有重叠,请调整后再保存')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleReserveTimeTypeChange(val) {
|
||||
if (!Array.isArray(this.formData.openHours)) {
|
||||
this.$set(this.formData, 'openHours', [])
|
||||
}
|
||||
if (val === 1) {
|
||||
const currentFullDay = this.formData.openHours[0] || this.fullDayOpenHourCache || {}
|
||||
this.fullDayOpenHourCache = {
|
||||
startTime: currentFullDay.startTime || '',
|
||||
endTime: currentFullDay.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(currentFullDay.timeUnit),
|
||||
}
|
||||
if (!Array.isArray(this.segmentedOpenHoursCache) || this.segmentedOpenHoursCache.length === 0) {
|
||||
this.segmentedOpenHoursCache = [{
|
||||
startTime: this.fullDayOpenHourCache.startTime || '',
|
||||
endTime: this.fullDayOpenHourCache.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(this.fullDayOpenHourCache.timeUnit),
|
||||
}]
|
||||
}
|
||||
this.formData.openHours = clone(this.segmentedOpenHoursCache)
|
||||
} else if (val === 2) {
|
||||
this.segmentedOpenHoursCache = Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0
|
||||
? clone(this.formData.openHours)
|
||||
: this.segmentedOpenHoursCache
|
||||
const first = this.fullDayOpenHourCache && (this.fullDayOpenHourCache.startTime || this.fullDayOpenHourCache.endTime)
|
||||
? this.fullDayOpenHourCache
|
||||
: (this.segmentedOpenHoursCache[0] || {})
|
||||
this.fullDayOpenHourCache = {
|
||||
startTime: first.startTime || '',
|
||||
endTime: first.endTime || '',
|
||||
timeUnit: this.normalizeTimeUnit(first.timeUnit || this.fullDayOpenHourCache.timeUnit),
|
||||
}
|
||||
this.formData.openHours = [clone(this.fullDayOpenHourCache)]
|
||||
}
|
||||
},
|
||||
validateOpenHours() {
|
||||
const openHours = this.formData.openHours || []
|
||||
if (this.formData.reserveTimeType === 1) {
|
||||
if (openHours.length === 0) {
|
||||
this.$message.warning('请至少添加一条场次信息')
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < openHours.length; i++) {
|
||||
const item = openHours[i] || {}
|
||||
if (!item.startTime) {
|
||||
this.$message.warning('第' + (i + 1) + '条场次信息中,开始时间必填')
|
||||
return false
|
||||
}
|
||||
if (!item.endTime) {
|
||||
this.$message.warning('第' + (i + 1) + '条场次信息中,结束时间必填')
|
||||
return false
|
||||
}
|
||||
if (item.startTime >= item.endTime) {
|
||||
this.$message.warning('第' + (i + 1) + '条场次信息中,开始时间必须早于结束时间')
|
||||
return false
|
||||
}
|
||||
if (!item.timeUnit || item.timeUnit <= 0) {
|
||||
this.$message.warning('第' + (i + 1) + '条场次信息中,预约时间单位必须大于0分钟')
|
||||
return false
|
||||
}
|
||||
}
|
||||
const overlapResult = this.hasSegmentedOpenHoursOverlap(openHours)
|
||||
if (overlapResult.overlap) {
|
||||
this.$message.warning('第' + (overlapResult.prevIndex + 1) + '条和第' + (overlapResult.currentIndex + 1) + '条场次时间有重叠,请调整后再保存')
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
const item = openHours[0] || {}
|
||||
if (!item.startTime || !item.endTime) {
|
||||
this.$message.warning('请设置全天候预约的开始时间和结束时间')
|
||||
return false
|
||||
}
|
||||
if (item.startTime >= item.endTime) {
|
||||
this.$message.warning('全天候预约的开始时间必须早于结束时间')
|
||||
return false
|
||||
}
|
||||
if (!item.timeUnit || item.timeUnit <= 0) {
|
||||
this.$message.warning('全天候预约的预约时间单位必须大于0分钟')
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
doConfirmSetUpCourse() {
|
||||
const timeList = this.formData.notApplyTimeList
|
||||
if (timeList && timeList.length > 0) {
|
||||
@@ -296,40 +632,103 @@
|
||||
if (this.formData.notApplyTimeList) {
|
||||
this.$set(this.formData, 'setUpDate', this.formData.notApplyTimeList.map(o => o.date))
|
||||
}
|
||||
this.activeTab = 'siteSetting'
|
||||
this.setUpTimeDialog = true
|
||||
},
|
||||
onOpen(row) {
|
||||
this.activeTab = 'basicInfo'
|
||||
if (row && row.id) {
|
||||
this.formData = clone(row)
|
||||
if (!Array.isArray(this.formData.notApplyTimeList)) {
|
||||
this.$set(this.formData, 'notApplyTimeList', [])
|
||||
}
|
||||
if (!this.formData.reserveTimeType) {
|
||||
this.$set(this.formData, 'reserveTimeType', 1)
|
||||
}
|
||||
if (!Array.isArray(this.formData.openHours) || this.formData.openHours.length === 0) {
|
||||
this.$set(this.formData, 'openHours', this.formData.reserveTimeType === 2 ? [{ startTime: '', endTime: '', timeUnit: 30 }] : [{ startTime: '', endTime: '', timeUnit: 30 }])
|
||||
}
|
||||
if (!Array.isArray(this.formData.segmentedOpenHours) || this.formData.segmentedOpenHours.length === 0) {
|
||||
this.$set(this.formData, 'segmentedOpenHours', [{ startTime: '', endTime: '', timeUnit: 30 }])
|
||||
}
|
||||
if (!this.formData.fullDayOpenHour) {
|
||||
this.$set(this.formData, 'fullDayOpenHour', { startTime: '', endTime: '', timeUnit: 30 })
|
||||
}
|
||||
this.initOpenHoursCache()
|
||||
} else {
|
||||
this.formData = {
|
||||
state: true,
|
||||
sexLimit: 0,
|
||||
filterHolidays: false,
|
||||
reserveTimeType: 1,
|
||||
openHours: [{ startTime: '', endTime: '', timeUnit: 30 }],
|
||||
segmentedOpenHours: [{ startTime: '', endTime: '', timeUnit: 30 }],
|
||||
fullDayOpenHour: { startTime: '', endTime: '', timeUnit: 30 },
|
||||
notApplyTimeList: [],
|
||||
createUserName: this.$store.state.user.username,
|
||||
createUserId: this.$store.state.user.id,
|
||||
}
|
||||
this.segmentedOpenHoursCache = [{ startTime: '', endTime: '', timeUnit: 30 }]
|
||||
this.fullDayOpenHourCache = { startTime: '', endTime: '', timeUnit: 30 }
|
||||
}
|
||||
},
|
||||
async doSave(stayOnPage) {
|
||||
if (!this.validateOpenHours()) {
|
||||
this.activeTab = 'siteSetting'
|
||||
return
|
||||
}
|
||||
const submitData = this.buildSubmitFormData()
|
||||
const resp = await this.$axios.post("/platform/siteCug/manage/submit", {data: JSON.stringify(submitData)})
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(stayOnPage ? '暂存成功,您可以继续编辑当前内容' : '正式提交成功,已返回列表页')
|
||||
if (resp.data) {
|
||||
this.formData = clone(resp.data)
|
||||
if (!Array.isArray(this.formData.notApplyTimeList)) {
|
||||
this.$set(this.formData, 'notApplyTimeList', [])
|
||||
}
|
||||
if (!this.formData.reserveTimeType) {
|
||||
this.$set(this.formData, 'reserveTimeType', 1)
|
||||
}
|
||||
if (!Array.isArray(this.formData.openHours) || this.formData.openHours.length === 0) {
|
||||
this.$set(this.formData, 'openHours', this.formData.reserveTimeType === 2 ? [{ startTime: '', endTime: '', timeUnit: 30 }] : [{ startTime: '', endTime: '', timeUnit: 30 }])
|
||||
}
|
||||
if (!Array.isArray(this.formData.segmentedOpenHours) || this.formData.segmentedOpenHours.length === 0) {
|
||||
this.$set(this.formData, 'segmentedOpenHours', [{ startTime: '', endTime: '', timeUnit: 30 }])
|
||||
}
|
||||
if (!this.formData.fullDayOpenHour) {
|
||||
this.$set(this.formData, 'fullDayOpenHour', { startTime: '', endTime: '', timeUnit: 30 })
|
||||
}
|
||||
this.initOpenHoursCache()
|
||||
}
|
||||
if (!stayOnPage) {
|
||||
this.$emit('refresh')
|
||||
}
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
},
|
||||
onTempSave() {
|
||||
this.$refs.formRef.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.$confirm("确认暂存当前内容,并继续留在本页编辑吗?", "暂存确认", {
|
||||
confirmButtonText: "确认暂存",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
await this.doSave(true)
|
||||
}).catch(() => {})
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$confirm("您确定要提交吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
this.$confirm("确认正式提交当前内容吗?提交成功后将返回列表页。", "正式提交确认", {
|
||||
confirmButtonText: "确认提交",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
type: "primary"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post("/platform/siteCug/manage/submit", {data: JSON.stringify(this.formData)})
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.$emit('refresh')
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
await this.doSave(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,15 +39,15 @@ layout("/layouts/platform.html"){
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template v-slot="{ row }" v-if="column.prop === 'state'">
|
||||
<el-switch
|
||||
@@ -57,6 +57,10 @@ layout("/layouts/platform.html"){
|
||||
inactive-color="#ff4949">
|
||||
</el-switch>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveTimeType'">
|
||||
<span v-if="row.reserveTimeType === 2">全天候预约</span>
|
||||
<span v-else>分段预约</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'sexLimit'">
|
||||
<span v-if="row.sexLimit === 0">不限制</span>
|
||||
<span v-else-if="row.sexLimit === 1">男</span>
|
||||
@@ -98,16 +102,16 @@ layout("/layouts/platform.html"){
|
||||
return {
|
||||
typeOptions: [],
|
||||
tableColumns: [
|
||||
{prop: 'createUserName', label: '创建人'},
|
||||
{prop: 'sortNum', label: '排序编号'},
|
||||
{prop: 'name', label: '场地名称'},
|
||||
{prop: 'address', label: '场地地址'},
|
||||
{prop: 'contactName', label: '联系人'},
|
||||
{prop: 'contactPhone', label: '联系电话'},
|
||||
{prop: 'maxNum', label: '容纳人数'},
|
||||
{prop: 'createUserName', label: '创建人', sortable: 'custom'},
|
||||
{prop: 'name', label: '场地名称', sortable: 'custom'},
|
||||
{prop: 'address', label: '场地地址', sortable: 'custom'},
|
||||
{prop: 'contactName', label: '联系人', sortable: 'custom'},
|
||||
{prop: 'contactPhone', label: '联系电话', sortable: 'custom'},
|
||||
{prop: 'maxNum', label: '容纳人数', sortable: 'custom'},
|
||||
{prop: 'typeName', label: '场地类型'},
|
||||
{prop: 'sexLimit', label: '性别限制'},
|
||||
{prop: 'state', label: '开启状态'},
|
||||
{prop: 'reserveTimeType', label: '预约时间段类型', sortable: 'custom'},
|
||||
{prop: 'sexLimit', label: '性别限制', sortable: 'custom'},
|
||||
{prop: 'state', label: '开启状态', sortable: 'custom'},
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -132,7 +136,7 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗, 是否继续?", "提示", {
|
||||
this.$confirm("您确定要删除吗? 是否继续?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
@@ -177,4 +181,4 @@ layout("/layouts/platform.html"){
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
#-->
|
||||
|
||||
@@ -1,51 +1,99 @@
|
||||
const siteInfo = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="创建人">{{ viewData.createUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="排序编号">{{ viewData.sortNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地名称">{{ viewData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地地址">{{ viewData.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ viewData.contactName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ viewData.contactPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="容纳人数">{{ viewData.maxNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地类型">{{ viewData.typeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="排除节假日">
|
||||
<span v-if="viewData.filterHolidays">是</span>
|
||||
<span v-else>否</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="禁用时间" :span="2">
|
||||
<el-table v-if="viewData.notApplyTimeList && viewData.notApplyTimeList.length > 0"
|
||||
:data="viewData.notApplyTimeList" max-height="300" size="mini">
|
||||
<el-table-column prop="date" label="日期"></el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间"></el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间"></el-table-column>
|
||||
</el-table>
|
||||
<span v-else>暂无禁用时间</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别限制">
|
||||
<span v-if="viewData.sexLimit === 0">不限制</span>
|
||||
<span v-if="viewData.sexLimit === 1">男</span>
|
||||
<span v-if="viewData.sexLimit === 2">女</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开启状态">
|
||||
<span v-if="viewData.state">开启</span>
|
||||
<span v-else>禁用</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="场地介绍" :span="2">
|
||||
<div v-if="viewData.introduce" v-html="viewData.introduce"></div>
|
||||
<div v-else>暂无场地介绍</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="场地基本信息" name="basicInfo">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="创建人">{{ viewData.createUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="排序编号">{{ viewData.sortNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地名称">{{ viewData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地地址">{{ viewData.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ viewData.contactName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ viewData.contactPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="容纳人数">{{ viewData.maxNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地类型">{{ viewData.typeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地介绍" :span="2">
|
||||
<div v-if="viewData.introduce" v-html="viewData.introduce"></div>
|
||||
<div v-else>暂无场地介绍</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="场地照片" :span="2">
|
||||
<el-image
|
||||
v-if="viewData.sitePhoto"
|
||||
:preview-src-list="[viewData.sitePhoto]"
|
||||
:src="viewData.sitePhoto"
|
||||
fit="cover"
|
||||
style="width: 160px; height: 160px; border-radius: 4px;">
|
||||
</el-image>
|
||||
<div v-else>暂无场地照片</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="设置场地" name="siteSetting">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="开启状态">
|
||||
<span v-if="viewData.state">开启</span>
|
||||
<span v-else>禁用</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别限制">
|
||||
<span v-if="viewData.sexLimit === 0">不限制</span>
|
||||
<span v-else-if="viewData.sexLimit === 1">男</span>
|
||||
<span v-else-if="viewData.sexLimit === 2">女</span>
|
||||
<span v-else>--</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排除节假日">
|
||||
<span v-if="viewData.filterHolidays">是</span>
|
||||
<span v-else>否</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时间段类型">
|
||||
<span v-if="viewData.reserveTimeType === 1">分段预约</span>
|
||||
<span v-else-if="viewData.reserveTimeType === 2">全天候预约</span>
|
||||
<span v-else>--</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="禁用时间" :span="2">
|
||||
<el-table v-if="viewData.notApplyTimeList && viewData.notApplyTimeList.length > 0"
|
||||
:data="viewData.notApplyTimeList" max-height="300" size="mini">
|
||||
<el-table-column prop="date" label="日期"></el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间"></el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间"></el-table-column>
|
||||
</el-table>
|
||||
<span v-else>暂无禁用时间</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="场次信息" :span="2">
|
||||
<div v-if="viewData.reserveTimeType === 1">
|
||||
<el-table v-if="viewData.openHours && viewData.openHours.length > 0"
|
||||
:data="viewData.openHours" max-height="300" size="mini">
|
||||
<el-table-column type="index" label="序号" width="80"></el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间"></el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间"></el-table-column>
|
||||
<el-table-column prop="timeUnit" label="预约时间单位(分钟)"></el-table-column>
|
||||
</el-table>
|
||||
<span v-else>暂无场次信息</span>
|
||||
</div>
|
||||
<div v-else-if="viewData.reserveTimeType === 2">
|
||||
<div v-if="viewData.openHours && viewData.openHours.length > 0">
|
||||
<div><span>开始时间:</span><span>{{ viewData.openHours[0].startTime || '--' }}</span></div>
|
||||
<div><span>结束时间:</span><span>{{ viewData.openHours[0].endTime || '--' }}</span></div>
|
||||
<div><span>预约时间单位(分钟):</span><span>{{ viewData.openHours[0].timeUnit || '--' }}</span></div>
|
||||
</div>
|
||||
<span v-else>暂无场次信息</span>
|
||||
</div>
|
||||
<span v-else>暂无场次信息</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'basicInfo',
|
||||
viewData: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(row) {
|
||||
this.activeTab = 'basicInfo'
|
||||
this.viewData = row
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,23 +1,55 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.mine-batch-toggle {
|
||||
padding: 0;
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mine-batch-empty {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.mine-batch-child-row {
|
||||
background: #fafcff;
|
||||
}
|
||||
|
||||
.mine-batch-child-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #7a8a9a;
|
||||
}
|
||||
|
||||
.mine-batch-child-label::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
background: #c8d3df;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号/场地">
|
||||
<el-input placeholder="请输入姓名/工号/场地" clearable v-model="pageForm.searchKeyword"
|
||||
<search-item label="姓名/工号">
|
||||
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="活动场地">
|
||||
<el-select v-model="pageForm.siteId" @change="doSearch" style="width: 100%" placeholder="请选择活动场地" filterable clearable>
|
||||
<el-select v-model="pageForm.siteId" @change="doSearch" style="width: 100%;" placeholder="请选择活动场地" filterable clearable>
|
||||
<el-option v-for="item in siteOptions" :value="item.id" :key="item.id" :label="item.name"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="场地类型">
|
||||
<el-select v-model="pageForm.siteType" @change="doSearch" style="width: 100%" placeholder="请选择场地类型" filterable clearable>
|
||||
<el-select v-model="pageForm.siteType" @change="doSearch" style="width: 100%;" placeholder="请选择场地类型" filterable clearable>
|
||||
<el-option v-for="item in typeOptions" :value="item.id" :key="item.id" :label="item.name"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
@@ -26,33 +58,46 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool label="申请列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :row-class-name="tableRowClassName" style="width: 100%;">
|
||||
<el-table-column label="" width="54" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-button
|
||||
v-if="row._hasFoldChildren"
|
||||
class="mine-batch-toggle"
|
||||
type="text"
|
||||
@click="toggleBatchGroup(row)">
|
||||
<i :class="row._expanded ? 'el-icon-caret-bottom' : 'el-icon-caret-right'"></i>
|
||||
</el-button>
|
||||
<span v-else class="mine-batch-empty"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" width="50" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
|
||||
<template v-slot="{ row }" v-if="column.prop === 'siteName'">
|
||||
<span v-if="row._isBatchChild" class="mine-batch-child-label">{{ row.siteName }}</span>
|
||||
<span v-else>{{ row.siteName }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'instanceState'">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveType'">
|
||||
<span>{{ row.reserveType === 'club' ? '协会预约' : '分工会预约' }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveTargetName'">
|
||||
<span>{{ row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-') }}</span>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'yearlyBatch'">
|
||||
<span>{{ normalizeBatchFlag(row) ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="180">
|
||||
<template v-slot="{row}">
|
||||
<template v-slot="{ row }">
|
||||
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.canCancel" @click="onDelete(row)" size="mini" type="danger">删除</el-button>
|
||||
<el-button v-if="row.canCancel && !row._isBatchChild" @click="onDelete(row)" size="mini" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -79,29 +124,85 @@ layout("/layouts/platform.html"){
|
||||
return {
|
||||
typeOptions: [],
|
||||
siteOptions: [],
|
||||
rawTableData: [],
|
||||
expandedBatchKeys: {},
|
||||
tableColumns: [
|
||||
{prop: 'siteName', label: '场地名称'},
|
||||
{prop: 'applyUserName', label: '预约人'},
|
||||
{prop: 'reserveType', label: '预约类型'},
|
||||
{prop: 'reserveTargetName', label: '预约单位'},
|
||||
{prop: 'applyUnitName', label: '所属单位'},
|
||||
{prop: 'reserveStartTime', label: '开始时间'},
|
||||
{prop: 'reserveEndTime', label: '结束时间'},
|
||||
{prop: 'applyMobile', label: '联系方式'},
|
||||
{prop: 'taskName', label: '当前节点'},
|
||||
{prop: 'instanceState', label: '流程状态'},
|
||||
{prop: 'siteName', label: '场地名称', sortable: 'custom'},
|
||||
{prop: 'applyUserName', label: '预约人', sortable: 'custom'},
|
||||
{prop: 'applyLoginName', label: '工号', sortable: 'custom'},
|
||||
{prop: 'yearlyBatch', label: '是否批量预约', sortable: 'custom', width: 130},
|
||||
{prop: 'reserveTargetName', label: '预约单位', sortable: 'custom'},
|
||||
{prop: 'reserveStartTime', label: '开始时间', sortable: 'custom'},
|
||||
{prop: 'reserveEndTime', label: '结束时间', sortable: 'custom'},
|
||||
{prop: 'applyMobile', label: '联系方式', sortable: 'custom'},
|
||||
{prop: 'taskName', label: '当前节点', sortable: 'custom'},
|
||||
{prop: 'instanceState', label: '流程状态', sortable: 'custom', width: 120},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeBatchFlag(row) {
|
||||
return row && (row.yearlyBatch === 1 || row.yearlyBatch === '1' || row.yearlyBatch === true)
|
||||
},
|
||||
getBatchGroupKey(row) {
|
||||
if (!this.normalizeBatchFlag(row)) {
|
||||
return 'single_' + row.id
|
||||
}
|
||||
const batchNo = row.backOption || row.id
|
||||
return 'batch_' + batchNo + '_' + (row.applyUserName || '') + '_' + (row.siteName || '')
|
||||
},
|
||||
buildTableData(rows) {
|
||||
const groups = new Map()
|
||||
;(rows || []).forEach(row => {
|
||||
const key = this.getBatchGroupKey(row)
|
||||
if (!groups.has(key)) {
|
||||
groups.set(key, [])
|
||||
}
|
||||
groups.get(key).push({...row})
|
||||
})
|
||||
const displayRows = []
|
||||
groups.forEach((groupRows, key) => {
|
||||
const sortedRows = groupRows.slice().sort((a, b) => {
|
||||
return (b.reserveStartTime || '').localeCompare(a.reserveStartTime || '')
|
||||
})
|
||||
const parent = {...sortedRows[0]}
|
||||
const children = sortedRows.slice(1).map(item => ({
|
||||
...item,
|
||||
_isBatchChild: true,
|
||||
_groupKey: key,
|
||||
_hasFoldChildren: false,
|
||||
}))
|
||||
parent._groupKey = key
|
||||
parent._isBatchChild = false
|
||||
parent._hasFoldChildren = children.length > 0
|
||||
parent._expanded = !!this.expandedBatchKeys[key]
|
||||
parent._batchCount = sortedRows.length
|
||||
parent._foldedRecords = children
|
||||
displayRows.push(parent)
|
||||
if (parent._expanded) {
|
||||
displayRows.push(...children)
|
||||
}
|
||||
})
|
||||
return displayRows
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return row && row._isBatchChild ? 'mine-batch-child-row' : ''
|
||||
},
|
||||
toggleBatchGroup(row) {
|
||||
if (!row || !row._groupKey || !row._hasFoldChildren) {
|
||||
return
|
||||
}
|
||||
this.$set(this.expandedBatchKeys, row._groupKey, !this.expandedBatchKeys[row._groupKey])
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
},
|
||||
onView(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
onDelete(row) {
|
||||
const message = row.yearlyBatch
|
||||
? ('该记录属于“预约本年”批量预约,确认后将一并删除同批次的 ' + (row.batchDeleteCount || 0) + ' 条预约记录,是否继续?')
|
||||
const message = this.normalizeBatchFlag(row)
|
||||
? ('该记录属于“预约本年”批量预约,确认后将一并删除同批次的 ' + (row.batchDeleteCount || row._batchCount || 0) + ' 条预约记录,是否继续?')
|
||||
: '您确定要删除吗?'
|
||||
this.$confirm(message, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
@@ -126,6 +227,15 @@ layout("/layouts/platform.html"){
|
||||
this.siteOptions = res.data
|
||||
})
|
||||
},
|
||||
pageData() {
|
||||
this.$axios.post(loc() + '/pageData', this.pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.rawTableData = Array.isArray(res.data.list) ? res.data.list : []
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.querySiteType()
|
||||
|
||||
@@ -1,64 +1,111 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.record-batch-toggle {
|
||||
padding: 0;
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.record-batch-empty {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.record-batch-child-row {
|
||||
background: #fafcff;
|
||||
}
|
||||
|
||||
.record-batch-child-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #7a8a9a;
|
||||
}
|
||||
|
||||
.record-batch-child-label::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
background: #c8d3df;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号/场地">
|
||||
<el-input placeholder="请输入姓名/工号/场地" clearable v-model="pageForm.searchKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.searchKeyword"
|
||||
placeholder="请输入姓名或工号"
|
||||
clearable
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="活动场地">
|
||||
<el-select v-model="pageForm.siteId" @change="doSearch" style="width: 100%"
|
||||
placeholder="请选择活动场地" filterable clearable>
|
||||
<el-option v-for="item in siteOptions"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
:label="item.name"></el-option>
|
||||
<el-select
|
||||
v-model="pageForm.siteId"
|
||||
@change="doSearch"
|
||||
style="width: 100%"
|
||||
placeholder="请选择活动场地"
|
||||
filterable
|
||||
clearable>
|
||||
<el-option
|
||||
v-for="item in siteOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="场地类型">
|
||||
<el-select v-model="pageForm.siteType" @change="doSearch" style="width: 100%"
|
||||
placeholder="请选择场地类型" filterable clearable>
|
||||
<el-option v-for="item in typeOptions"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
:label="item.name"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="预约类型">
|
||||
<el-select v-model="pageForm.reserveType" @change="doSearch" style="width: 100%"
|
||||
placeholder="请选择预约类型" clearable>
|
||||
<el-option label="分工会预约" value="union"></el-option>
|
||||
<el-option label="协会预约" value="club"></el-option>
|
||||
<el-select
|
||||
v-model="pageForm.siteType"
|
||||
@change="doSearch"
|
||||
style="width: 100%"
|
||||
placeholder="请选择场地类型"
|
||||
filterable
|
||||
clearable>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="预约单位">
|
||||
<el-input placeholder="请输入分工会或协会名称" clearable v-model="pageForm.reserveTargetKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
<el-input
|
||||
v-model="pageForm.reserveTargetKeyword"
|
||||
placeholder="请输入分工会或协会名称"
|
||||
clearable
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="预约开始时间">
|
||||
<el-date-picker v-model="pageForm.reserveTimeStart"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约开始时间"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="doSearch">
|
||||
<el-date-picker
|
||||
v-model="pageForm.reserveTimeStart"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约开始时间"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="doSearch">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="预约结束时间">
|
||||
<el-date-picker v-model="pageForm.reserveTimeEnd"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约结束时间"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="doSearch">
|
||||
<el-date-picker
|
||||
v-model="pageForm.reserveTimeEnd"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择预约结束时间"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@change="doSearch">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
</search>
|
||||
@@ -68,27 +115,47 @@ layout("/layouts/platform.html"){
|
||||
<table-tool label="申请列表">
|
||||
<el-button type="primary" size="small" @click="doExport">导出表格</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:row-class-name="tableRowClassName"
|
||||
@sort-change="pageOrder"
|
||||
style="width: 100%">
|
||||
<el-table-column label="" width="54" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-button
|
||||
v-if="row._hasFoldChildren"
|
||||
class="record-batch-toggle"
|
||||
type="text"
|
||||
@click="toggleBatchGroup(row)">
|
||||
<i :class="row._expanded ? 'el-icon-caret-bottom' : 'el-icon-caret-right'"></i>
|
||||
</el-button>
|
||||
<span v-else class="record-batch-empty"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" width="50" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns">
|
||||
<template v-slot="{ row }" v-if="column.prop === 'reserveType'">
|
||||
<span>{{ row.reserveType === 'club' ? '协会预约' : '分工会预约' }}</span>
|
||||
show-overflow-tooltip>
|
||||
<template v-slot="{ row }" v-if="column.prop === 'siteName'">
|
||||
<span v-if="row._isBatchChild" class="record-batch-child-label">{{ row.siteName }}</span>
|
||||
<span v-else>{{ row.siteName }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveTargetName'">
|
||||
<span>{{ row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-') }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'yearlyBatch'">
|
||||
<span>{{ normalizeBatchFlag(row) ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="180">
|
||||
<template v-slot="{row}">
|
||||
<template v-slot="{ row }">
|
||||
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
|
||||
<el-button @click="onDelete(row.id)" size="mini" type="danger">删除</el-button>
|
||||
</template>
|
||||
@@ -111,7 +178,7 @@ layout("/layouts/platform.html"){
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
'info': siteCugApplyInfo,
|
||||
info: siteCugApplyInfo,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -126,19 +193,75 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
typeOptions: [],
|
||||
siteOptions: [],
|
||||
rawTableData: [],
|
||||
expandedBatchKeys: {},
|
||||
tableColumns: [
|
||||
{prop: 'siteName', label: '场地名称'},
|
||||
{prop: 'applyUserName', label: '预约人'},
|
||||
{prop: 'reserveType', label: '预约类型'},
|
||||
{prop: 'reserveTargetName', label: '预约单位'},
|
||||
{prop: 'applyUnitName', label: '所属单位'},
|
||||
{prop: 'reserveStartTime', label: '开始时间'},
|
||||
{prop: 'reserveEndTime', label: '结束时间'},
|
||||
{prop: 'applyMobile', label: '联系方式'},
|
||||
{ prop: 'siteName', label: '场地名称', sortable: 'custom' },
|
||||
{ prop: 'applyUserName', label: '预约人', sortable: 'custom' },
|
||||
{ prop: 'applyLoginName', label: '工号', sortable: 'custom' },
|
||||
{ prop: 'yearlyBatch', label: '是否批量预约', sortable: 'custom', width: 130 },
|
||||
{ prop: 'reserveTargetName', label: '预约单位', sortable: 'custom' },
|
||||
{ prop: 'reserveStartTime', label: '开始时间', sortable: 'custom' },
|
||||
{ prop: 'reserveEndTime', label: '结束时间', sortable: 'custom' },
|
||||
{ prop: 'applyMobile', label: '联系方式', sortable: 'custom' },
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeBatchFlag(row) {
|
||||
return row && (row.yearlyBatch === 1 || row.yearlyBatch === '1' || row.yearlyBatch === true)
|
||||
},
|
||||
getBatchGroupKey(row) {
|
||||
if (!this.normalizeBatchFlag(row)) {
|
||||
return 'single_' + row.id
|
||||
}
|
||||
const batchNo = row.backOption || row.id
|
||||
return 'batch_' + batchNo + '_' + (row.applyUserName || '') + '_' + (row.siteName || '')
|
||||
},
|
||||
buildTableData(rows) {
|
||||
const groups = new Map()
|
||||
;(rows || []).forEach(row => {
|
||||
const key = this.getBatchGroupKey(row)
|
||||
if (!groups.has(key)) {
|
||||
groups.set(key, [])
|
||||
}
|
||||
groups.get(key).push({ ...row })
|
||||
})
|
||||
const displayRows = []
|
||||
groups.forEach((groupRows, key) => {
|
||||
const sortedRows = groupRows.slice().sort((a, b) => {
|
||||
return (b.reserveStartTime || '').localeCompare(a.reserveStartTime || '')
|
||||
})
|
||||
const parent = { ...sortedRows[0] }
|
||||
const children = sortedRows.slice(1).map(item => ({
|
||||
...item,
|
||||
_isBatchChild: true,
|
||||
_groupKey: key,
|
||||
_hasFoldChildren: false,
|
||||
}))
|
||||
parent._groupKey = key
|
||||
parent._isBatchChild = false
|
||||
parent._hasFoldChildren = children.length > 0
|
||||
parent._expanded = !!this.expandedBatchKeys[key]
|
||||
parent._batchCount = sortedRows.length
|
||||
parent._foldedRecords = children
|
||||
displayRows.push(parent)
|
||||
if (parent._expanded) {
|
||||
displayRows.push(...children)
|
||||
}
|
||||
})
|
||||
return displayRows
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return row && row._isBatchChild ? 'record-batch-child-row' : ''
|
||||
},
|
||||
toggleBatchGroup(row) {
|
||||
if (!row || !row._groupKey || !row._hasFoldChildren) {
|
||||
return
|
||||
}
|
||||
this.$set(this.expandedBatchKeys, row._groupKey, !this.expandedBatchKeys[row._groupKey])
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
},
|
||||
onView(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
@@ -148,7 +271,7 @@ layout("/layouts/platform.html"){
|
||||
this.$confirm('您确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.$axios.post('/platform/siteCug/record/delete', { id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
@@ -158,7 +281,6 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
})
|
||||
},
|
||||
// 导出内容和列表当前筛选条件保持一致,实现所见即所得
|
||||
doExport() {
|
||||
this.$downLoad('/platform/siteCug/record/doExport', this.pageForm)
|
||||
},
|
||||
@@ -172,13 +294,21 @@ layout("/layouts/platform.html"){
|
||||
this.siteOptions = res.data
|
||||
})
|
||||
},
|
||||
pageData() {
|
||||
this.$axios.post(loc() + '/pageData', this.pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.rawTableData = Array.isArray(res.data.list) ? res.data.list : []
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// 查询字段在 data 中一次性声明完整,避免 Vue 2 对后加属性渲染不稳定
|
||||
this.querySiteType()
|
||||
this.querySites()
|
||||
this.pageData()
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
+190
-59
@@ -1,35 +1,81 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.audit-batch-toggle {
|
||||
padding: 0;
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.audit-batch-empty {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.audit-batch-child-row {
|
||||
background: #fafcff;
|
||||
}
|
||||
|
||||
.audit-batch-child-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #7a8a9a;
|
||||
}
|
||||
|
||||
.audit-batch-child-label::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
background: #c8d3df;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号/场地">
|
||||
<el-input placeholder="请输入姓名/工号/场地" clearable v-model="pageForm.searchKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.searchKeyword"
|
||||
placeholder="请输入姓名或工号"
|
||||
clearable
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="活动场地">
|
||||
<el-select v-model="pageForm.siteId" @change="doSearch" style="width: 100%"
|
||||
placeholder="请选择活动场地" filterable clearable>
|
||||
<el-option v-for="item in siteOptions"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
></el-option>
|
||||
<el-select
|
||||
v-model="pageForm.siteId"
|
||||
@change="doSearch"
|
||||
style="width: 100%"
|
||||
placeholder="请选择活动场地"
|
||||
filterable
|
||||
clearable>
|
||||
<el-option
|
||||
v-for="item in siteOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="场地类型">
|
||||
<el-select v-model="pageForm.siteType" @change="doSearch" style="width: 100%"
|
||||
placeholder="请选择场地类型" filterable clearable>
|
||||
<el-option v-for="item in typeOptions"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
></el-option>
|
||||
<el-select
|
||||
v-model="pageForm.siteType"
|
||||
@change="doSearch"
|
||||
style="width: 100%"
|
||||
placeholder="请选择场地类型"
|
||||
filterable
|
||||
clearable>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
@@ -42,34 +88,53 @@ layout("/layouts/platform.html"){
|
||||
<el-radio-button :label="false">未审核</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:row-class-name="tableRowClassName"
|
||||
@sort-change="pageOrder"
|
||||
style="width: 100%">
|
||||
<el-table-column label="" width="54" align="center" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-button
|
||||
v-if="row._hasFoldChildren"
|
||||
class="audit-batch-toggle"
|
||||
type="text"
|
||||
@click="toggleBatchGroup(row)">
|
||||
<i :class="row._expanded ? 'el-icon-caret-bottom' : 'el-icon-caret-right'"></i>
|
||||
</el-button>
|
||||
<span v-else class="audit-batch-empty"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" width="50" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveType'">
|
||||
<span>{{ row.reserveType === 'club' ? '协会预约' : '分工会预约' }}</span>
|
||||
show-overflow-tooltip>
|
||||
<template v-slot="{ row }" v-if="column.prop === 'siteName'">
|
||||
<span v-if="row._isBatchChild" class="audit-batch-child-label">{{ row.siteName }}</span>
|
||||
<span v-else>{{ row.siteName }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'reserveTargetName'">
|
||||
<span>{{ row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-') }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'yearlyBatch'">
|
||||
<span>{{ normalizeBatchFlag(row) ? '是' : '否' }}</span>
|
||||
</template>
|
||||
<template v-slot="{ row }" v-else-if="column.prop === 'instanceState'">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300px">
|
||||
<template v-slot="{row}">
|
||||
<el-table-column label="操作" fixed="right" width="220">
|
||||
<template v-slot="{ row }">
|
||||
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
|
||||
<el-button v-if="canRevoke(row)" :loading="revokeLoading" :disabled="revokeLoading" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
|
||||
<el-button v-if="!row._isBatchChild && row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
|
||||
<el-button v-if="!row._isBatchChild && canRevoke(row)" :loading="revokeLoading" :disabled="revokeLoading" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -80,12 +145,13 @@ layout("/layouts/platform.html"){
|
||||
<info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">
|
||||
{{formData.taskName}}
|
||||
{{ formData.taskName }}
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
|
||||
class="flow-task-form">
|
||||
<el-form-item label="审批意见" prop="tf_opinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" class="flow-task-form">
|
||||
<el-form-item
|
||||
label="审批意见"
|
||||
prop="tf_opinion"
|
||||
:rules="[{ required: true, message: '必填', trigger: ['change', 'blur'] }]">
|
||||
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -105,28 +171,30 @@ layout("/layouts/platform.html"){
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/applyInfo.js'){}#-->
|
||||
new Vue({
|
||||
el: "#app",
|
||||
el: '#app',
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"info": siteCugApplyInfo,
|
||||
info: siteCugApplyInfo,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
approval: false
|
||||
approval: false,
|
||||
},
|
||||
rawTableData: [],
|
||||
expandedBatchKeys: {},
|
||||
tableColumns: [
|
||||
{prop: 'siteName', label: '场地名称'},
|
||||
{prop: 'applyUserName', label: '预约人'},
|
||||
{prop: 'reserveType', label: '预约类型'},
|
||||
{prop: 'reserveTargetName', label: '预约单位'},
|
||||
{prop: 'applyUnitName', label: '所属单位'},
|
||||
{prop: 'reserveStartTime', label: '开始时间'},
|
||||
{prop: 'reserveEndTime', label: '结束时间'},
|
||||
{prop: 'applyMobile', label: '联系方式'},
|
||||
{prop: 'curTaskName', label: '当前节点'},
|
||||
{prop: 'instanceState', label: '流程状态'},
|
||||
{ prop: 'siteName', label: '场地名称', sortable: 'custom' },
|
||||
{ prop: 'applyUserName', label: '预约人', sortable: 'custom' },
|
||||
{ prop: 'applyLoginName', label: '工号', sortable: 'custom' },
|
||||
{ prop: 'yearlyBatch', label: '是否批量预约', sortable: 'custom', width: 130 },
|
||||
{ prop: 'reserveTargetName', label: '预约单位', sortable: 'custom' },
|
||||
{ prop: 'reserveStartTime', label: '开始时间', sortable: 'custom' },
|
||||
{ prop: 'reserveEndTime', label: '结束时间', sortable: 'custom' },
|
||||
{ prop: 'applyMobile', label: '联系方式', sortable: 'custom' },
|
||||
{ prop: 'curTaskName', label: '当前节点', sortable: 'custom' },
|
||||
{ prop: 'instanceState', label: '流程状态', sortable: 'custom', width: 120 },
|
||||
],
|
||||
typeOptions: [],
|
||||
siteOptions: [],
|
||||
@@ -138,6 +206,60 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeBatchFlag(row) {
|
||||
return row && (row.yearlyBatch === 1 || row.yearlyBatch === '1' || row.yearlyBatch === true)
|
||||
},
|
||||
getBatchGroupKey(row) {
|
||||
if (!this.normalizeBatchFlag(row)) {
|
||||
return 'single_' + row.id
|
||||
}
|
||||
const batchNo = row.backOption || row.id
|
||||
return 'batch_' + batchNo + '_' + (row.applyUserName || '') + '_' + (row.siteName || '')
|
||||
},
|
||||
buildTableData(rows) {
|
||||
const groups = new Map()
|
||||
;(rows || []).forEach(row => {
|
||||
const key = this.getBatchGroupKey(row)
|
||||
if (!groups.has(key)) {
|
||||
groups.set(key, [])
|
||||
}
|
||||
groups.get(key).push({ ...row })
|
||||
})
|
||||
const displayRows = []
|
||||
groups.forEach((groupRows, key) => {
|
||||
const sortedRows = groupRows.slice().sort((a, b) => {
|
||||
return (b.reserveStartTime || '').localeCompare(a.reserveStartTime || '')
|
||||
})
|
||||
const parent = { ...sortedRows[0] }
|
||||
const children = sortedRows.slice(1).map(item => ({
|
||||
...item,
|
||||
_isBatchChild: true,
|
||||
_groupKey: key,
|
||||
_hasFoldChildren: false,
|
||||
}))
|
||||
parent._groupKey = key
|
||||
parent._isBatchChild = false
|
||||
parent._hasFoldChildren = children.length > 0
|
||||
parent._expanded = !!this.expandedBatchKeys[key]
|
||||
parent._batchCount = sortedRows.length
|
||||
parent._foldedRecords = children
|
||||
displayRows.push(parent)
|
||||
if (parent._expanded) {
|
||||
displayRows.push(...children)
|
||||
}
|
||||
})
|
||||
return displayRows
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return row && row._isBatchChild ? 'audit-batch-child-row' : ''
|
||||
},
|
||||
toggleBatchGroup(row) {
|
||||
if (!row || !row._groupKey || !row._hasFoldChildren) {
|
||||
return
|
||||
}
|
||||
this.$set(this.expandedBatchKeys, row._groupKey, !this.expandedBatchKeys[row._groupKey])
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
},
|
||||
canRevoke(row) {
|
||||
return Number(row.instanceState) === 20
|
||||
},
|
||||
@@ -156,7 +278,7 @@ layout("/layouts/platform.html"){
|
||||
taskName: row.curTaskName,
|
||||
tf_opinion: '',
|
||||
yearlyBatch: row.yearlyBatch,
|
||||
batchAuditCount: row.batchAuditCount,
|
||||
batchAuditCount: row.batchAuditCount || row._batchCount,
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
@@ -171,14 +293,14 @@ layout("/layouts/platform.html"){
|
||||
this.$confirm(message, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.auditLoading = true
|
||||
this.$axios.post('/platform/siteCug/schoolUnionAudit/executeTask', {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
submitType: val,
|
||||
}),
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.guava.index()
|
||||
@@ -194,13 +316,13 @@ layout("/layouts/platform.html"){
|
||||
if (!this.canRevoke(row) || this.revokeLoading) {
|
||||
return
|
||||
}
|
||||
const message = row.yearlyBatch
|
||||
? ('该记录属于“预约本年”批量预约,撤回后将一并撤回同批次的 ' + (row.batchAuditCount || 0) + ' 条审核任务,是否继续?')
|
||||
const message = this.normalizeBatchFlag(row)
|
||||
? ('该记录属于“预约本年”批量预约,撤回后将一并撤回同批次的 ' + (row.batchAuditCount || row._batchCount || 0) + ' 条审核任务,是否继续?')
|
||||
: '您确定要撤回吗?'
|
||||
this.$confirm(message, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
type: 'info',
|
||||
}).then(() => {
|
||||
this.revokeLoading = true
|
||||
this.$axios.post('/platform/siteCug/schoolUnionAudit/revokeTask', { taskId: row.taskId, applyId: row.id }).then((res) => {
|
||||
@@ -223,12 +345,21 @@ layout("/layouts/platform.html"){
|
||||
this.siteOptions = res.data
|
||||
})
|
||||
},
|
||||
pageData() {
|
||||
this.$axios.post(loc() + '/pageData', this.pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.rawTableData = Array.isArray(res.data.list) ? res.data.list : []
|
||||
this.tableData = this.buildTableData(this.rawTableData)
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
created() {
|
||||
this.querySiteType()
|
||||
this.querySites()
|
||||
this.pageData()
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
+123
-3
@@ -1,6 +1,45 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.query-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.query-row-title {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.query-row-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.query-row-content-tag {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-tag-item {
|
||||
margin-right: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.query-tag-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
@@ -40,6 +79,56 @@ layout("/layouts/platform.html"){
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-row type="flex" align="middle" class="query-row query-row-tag">
|
||||
<el-col class="query-row-title">人员分类:</el-col>
|
||||
<el-col class="query-row-content query-row-content-tag">
|
||||
<el-tag
|
||||
class="query-tag-item"
|
||||
v-for="item in aidFundMemberUserTypeOptions"
|
||||
:key="item.code"
|
||||
:effect="pageForm.aidFundMemberUserTypes.includes(item.code)?'dark':'plain'"
|
||||
@click="tagClick('aidFundMemberUserTypes',item.code)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
<span class="query-tag-actions">
|
||||
<el-link type="danger"
|
||||
v-if="aidFundMemberUserTypeOptions.length&&pageForm.aidFundMemberUserTypes.length"
|
||||
:underline="false"
|
||||
@click="pageForm.aidFundMemberUserTypes=[];doSearch()">清空
|
||||
</el-link>
|
||||
<el-link :underline="false"
|
||||
@click="pageForm.aidFundMemberUserTypes=aidFundMemberUserTypeOptions.map(p=>p.code);doSearch()"
|
||||
type="success">全部
|
||||
</el-link>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" align="middle" class="query-row query-row-tag">
|
||||
<el-col class="query-row-title">人员属性:</el-col>
|
||||
<el-col class="query-row-content query-row-content-tag">
|
||||
<el-tag class="query-tag-item"
|
||||
v-for="item in userAttributeOptions"
|
||||
:key="item.code"
|
||||
:effect="pageForm.userAttributes.includes(item.code)?'dark':'plain'"
|
||||
@click="tagClick('userAttributes',item.code)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
<span class="query-tag-actions">
|
||||
<el-link type="danger"
|
||||
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
|
||||
:underline="false"
|
||||
@click="pageForm.userAttributes=[];doSearch()">清空
|
||||
</el-link>
|
||||
<el-link :underline="false"
|
||||
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
|
||||
type="success">全部
|
||||
</el-link>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="会员分析"></table-tool>
|
||||
@@ -122,9 +211,13 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
unions: [],
|
||||
units: [],
|
||||
aidFundMemberUserTypeOptions: [],
|
||||
userAttributeOptions: [],
|
||||
pageForm: {
|
||||
currentYear: moment().format("YYYY"),
|
||||
queryType: "fgh"
|
||||
queryType: "fgh",
|
||||
userAttributes: [],
|
||||
aidFundMemberUserTypes: []
|
||||
},
|
||||
fullTableData: []
|
||||
},
|
||||
@@ -146,15 +239,42 @@ layout("/layouts/platform.html"){
|
||||
this.units = data
|
||||
})
|
||||
}
|
||||
this.$businessTool.getDictOptions("AIDFUND_MEMBER_USER_TYPE").then((data) => {
|
||||
this.aidFundMemberUserTypeOptions = data
|
||||
})
|
||||
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
|
||||
this.userAttributeOptions = data
|
||||
})
|
||||
},
|
||||
pageData() {
|
||||
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
|
||||
// 统计接口按 JSON 字符串接收多选条件,这里统一在提交前做转换,保证人员分类和人员属性都能正确过滤。
|
||||
const pageForm = clone(this.pageForm)
|
||||
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
|
||||
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
|
||||
this.$axios.post(loc() + "/pageData", pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
tagClick(key,code) {
|
||||
if (key === "userAttributes") {
|
||||
if (this.pageForm.userAttributes.includes(code)) {
|
||||
this.pageForm.userAttributes.splice(this.pageForm.userAttributes.indexOf(code), 1)
|
||||
} else {
|
||||
this.pageForm.userAttributes.push(code)
|
||||
}
|
||||
}
|
||||
if (key === "aidFundMemberUserTypes") {
|
||||
if (this.pageForm.aidFundMemberUserTypes.includes(code)) {
|
||||
this.pageForm.aidFundMemberUserTypes.splice(this.pageForm.aidFundMemberUserTypes.indexOf(code), 1)
|
||||
} else {
|
||||
this.pageForm.aidFundMemberUserTypes.push(code)
|
||||
}
|
||||
}
|
||||
this.doSearch()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
|
||||
+84
-28
@@ -3,24 +3,40 @@ layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style> .query-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.query-row-title {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.query-row-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.query-row-content-tag {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-tag-item {
|
||||
margin-right: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.query-tag-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
@@ -65,27 +81,51 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<el-row type="flex" align="middle" class="query-row query-row-tag">
|
||||
<el-col class="query-row-title">人员分类:</el-col>
|
||||
<el-col class="query-row-content query-row-content-tag">
|
||||
<el-tag
|
||||
class="query-tag-item"
|
||||
v-for="item in aidFundMemberUserTypeOptions"
|
||||
:key="item.code"
|
||||
:effect="pageForm.aidFundMemberUserTypes.includes(item.code)?'dark':'plain'"
|
||||
@click="tagClick('aidFundMemberUserTypes',item.code)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
<span class="query-tag-actions">
|
||||
<el-link type="danger"
|
||||
v-if="aidFundMemberUserTypeOptions.length&&pageForm.aidFundMemberUserTypes.length"
|
||||
:underline="false"
|
||||
@click="pageForm.aidFundMemberUserTypes=[];doSearch()">清空
|
||||
</el-link>
|
||||
<el-link :underline="false"
|
||||
@click="pageForm.aidFundMemberUserTypes=aidFundMemberUserTypeOptions.map(p=>p.code);doSearch()"
|
||||
type="success">全部
|
||||
</el-link>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" align="middle" class="query-row query-row-tag">
|
||||
<el-col class="query-row-title">人员属性:</el-col>
|
||||
<el-col class="query-row-content query-row-content-tag">
|
||||
<el-tag style="margin-right: 10px;cursor: pointer"
|
||||
<el-tag class="query-tag-item"
|
||||
v-for="item in userAttributeOptions"
|
||||
:key="item.code"
|
||||
:type="item.name"
|
||||
:effect="pageForm.userAttributes.includes(item.code)?'dark':'plain'"
|
||||
@click="tagClick(item.code)">
|
||||
@click="tagClick('userAttributes',item.code)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
|
||||
<el-link type="danger"
|
||||
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
|
||||
:underline="false"
|
||||
@click="pageForm.userAttributes=[];doSearch()">清空
|
||||
</el-link>
|
||||
<el-link :underline="false"
|
||||
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
|
||||
type="success">全部
|
||||
</el-link>
|
||||
<span class="query-tag-actions">
|
||||
<el-link type="danger"
|
||||
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
|
||||
:underline="false"
|
||||
@click="pageForm.userAttributes=[];doSearch()">清空
|
||||
</el-link>
|
||||
<el-link :underline="false"
|
||||
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
|
||||
type="success">全部
|
||||
</el-link>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
@@ -137,8 +177,10 @@ layout("/layouts/platform.html"){
|
||||
pageForm: {
|
||||
currentYear: moment().format("YYYY"),
|
||||
queryType: "fgh",
|
||||
userAttributes: []
|
||||
}
|
||||
userAttributes: [],
|
||||
aidFundMemberUserTypes: []
|
||||
},
|
||||
aidFundMemberUserTypeOptions: [],
|
||||
},
|
||||
methods: {
|
||||
doExport() {},
|
||||
@@ -158,20 +200,34 @@ layout("/layouts/platform.html"){
|
||||
this.units = data
|
||||
})
|
||||
}
|
||||
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
|
||||
this.userAttributeOptions = data
|
||||
})
|
||||
this.$businessTool.getDictOptions("AIDFUND_MEMBER_USER_TYPE").then((data) => {
|
||||
this.aidFundMemberUserTypeOptions = data
|
||||
})
|
||||
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
|
||||
this.userAttributeOptions = data
|
||||
})
|
||||
},
|
||||
tagClick(code) {
|
||||
if (this.pageForm.userAttributes.includes(code)) {
|
||||
this.pageForm.userAttributes.splice(this.pageForm.userAttributes.indexOf(code), 1)
|
||||
} else {
|
||||
this.pageForm.userAttributes.push(code)
|
||||
}
|
||||
tagClick(key,code) {
|
||||
if (key === "userAttributes") {
|
||||
if (this.pageForm.userAttributes.includes(code)) {
|
||||
this.pageForm.userAttributes.splice(this.pageForm.userAttributes.indexOf(code), 1)
|
||||
} else {
|
||||
this.pageForm.userAttributes.push(code)
|
||||
}
|
||||
}
|
||||
if (key === "aidFundMemberUserTypes") {
|
||||
if (this.pageForm.aidFundMemberUserTypes.includes(code)) {
|
||||
this.pageForm.aidFundMemberUserTypes.splice(this.pageForm.aidFundMemberUserTypes.indexOf(code), 1)
|
||||
} else {
|
||||
this.pageForm.aidFundMemberUserTypes.push(code)
|
||||
}
|
||||
}
|
||||
this.doSearch()
|
||||
},
|
||||
pageData() {
|
||||
const pageForm = clone(this.pageForm)
|
||||
// 统计接口按 JSON 字符串接收多选条件,这里把人员分类和人员属性统一成同一提交口径。
|
||||
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
|
||||
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
|
||||
this.$axios.post(loc() + "/pageData", pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
|
||||
@@ -61,6 +61,14 @@ layout("/layouts/platform.html"){
|
||||
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitOptions"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="人员分类">
|
||||
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
|
||||
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
|
||||
</search-item>
|
||||
<search-item label="人员属性">
|
||||
<dict-select v-model="pageForm.userAttribute" placeholder="请选择人员属性" @change="doSearch"
|
||||
code="USER_ATTRIBUTE"></dict-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@ layout("/layouts/platform.html"){
|
||||
v-model="pageForm.userStates"
|
||||
></dict-select>
|
||||
</search-item>
|
||||
<search-item label="人员分类">
|
||||
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
|
||||
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
|
||||
</search-item>
|
||||
<search-item label="人员属性">
|
||||
<dict-select v-model="pageForm.userAttribute" placeholder="请选择人员属性" @change="doSearch"
|
||||
code="USER_ATTRIBUTE"></dict-select>
|
||||
</search-item>
|
||||
|
||||
<!-- <search-item label="所选福利:">-->
|
||||
<!-- <el-select clearable placeholder="请选择所选福利" style="width: 100%" v-model="pageForm.optionId">-->
|
||||
|
||||
+671
-579
File diff suppressed because it is too large
Load Diff
+490
-74
@@ -4,9 +4,304 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding-bottom: 84px;
|
||||
background: #f7f8fa;
|
||||
padding: 12px 12px 96px;
|
||||
background: #f5f7fb;
|
||||
min-height: calc(100vh - 46px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
border-radius: 22px;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
0 2px 6px rgba(15, 23, 42, 0.04),
|
||||
0 12px 28px rgba(15, 23, 42, 0.08),
|
||||
0 0 0 1px rgba(226, 232, 240, 0.95);
|
||||
}
|
||||
|
||||
.notice-card__header {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.notice-card__icon {
|
||||
color: #64748b;
|
||||
font-size: 16px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.notice-card__icon--expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.notice-card__body {
|
||||
padding: 0 16px 14px;
|
||||
border-top: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.notice-item__label {
|
||||
color: #334155;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.notice-item__value {
|
||||
margin-top: 4px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.disabled-time-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.disabled-time-item {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-tip {
|
||||
padding: 0 16px 14px;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.selection-summary {
|
||||
margin: 0 16px 14px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.batch-panel {
|
||||
padding: 0 16px 14px;
|
||||
}
|
||||
|
||||
.batch-summary {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.availability-popup {
|
||||
height: 82vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.availability-popup__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.availability-popup__title {
|
||||
color: #0f172a;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.availability-popup__subtitle {
|
||||
margin-top: 4px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.availability-popup__close {
|
||||
color: #94a3b8;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.availability-popup__tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 16px 0;
|
||||
overflow-x: auto;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.availability-tab {
|
||||
min-width: 72px;
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.availability-tab--active {
|
||||
border-color: #2563eb;
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.22);
|
||||
}
|
||||
|
||||
.availability-popup__legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 12px 16px 0;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.availability-legend__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.availability-legend__dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.availability-legend__dot--available {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
.availability-legend__dot--reserved {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.availability-legend__dot--selected {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.availability-legend__dot--closed {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.availability-popup__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.slot-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.slot-block {
|
||||
min-height: 48px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.slot-block--available {
|
||||
border-color: #86efac;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.slot-block--reserved {
|
||||
background: #e5e7eb;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.slot-block--closed {
|
||||
background: #f8fafc;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.slot-block--selected {
|
||||
border-color: #2563eb;
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 10px 20px rgba(37, 99, 235, 0.24);
|
||||
}
|
||||
|
||||
.availability-popup__footer {
|
||||
padding: 12px 16px calc(env(safe-area-inset-bottom) + 12px);
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.availability-popup__summary {
|
||||
margin-bottom: 12px;
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.availability-popup__actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.availability-popup__actions .van-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
@@ -17,17 +312,39 @@ layout("/layouts/platform_h5.html"){
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 10px 12px calc(env(safe-area-inset-bottom) + 10px);
|
||||
background: #ffffff;
|
||||
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.footer-actions .van-button {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.disabled-time-item {
|
||||
line-height: 20px;
|
||||
margin-bottom: 4px;
|
||||
/deep/ .panel-card .van-cell-group {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/ .panel-card .van-cell-group__title {
|
||||
padding: 16px 16px 8px;
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/deep/ .panel-card .van-cell {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
/deep/ .panel-card .van-field__label,
|
||||
/deep/ .panel-card .van-cell__title {
|
||||
color: #334155;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/deep/ .direction-column-cell .van-cell__value {
|
||||
@@ -40,46 +357,43 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-nav-bar title="场馆申请" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="page-container" v-if="siteLoaded">
|
||||
<van-cell-group title="场馆信息">
|
||||
<van-cell title="场馆名称" :value="row.name || '-' "></van-cell>
|
||||
<van-cell title="场地地址" class="direction-column-cell">
|
||||
<template #default>
|
||||
{{ row.address || '-' }}
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell title="联系人" :value="row.contactName || '-' "></van-cell>
|
||||
<van-cell title="联系电话" :value="row.contactPhone || '-' "></van-cell>
|
||||
<van-cell title="场地类型" :value="row.typeName || '-' "></van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group title="预约须知">
|
||||
<van-cell title="可预约日期" value="仅限工作日"></van-cell>
|
||||
<van-cell title="节假日限制"
|
||||
:value="timeLimitConfig.filterHolidays ? '节假日不可预约' : '不排除节假日'"></van-cell>
|
||||
<van-cell title="禁用时段" class="direction-column-cell">
|
||||
<template #default>
|
||||
<div v-if="timeLimitConfig.notApplyTimeList.length">
|
||||
<div
|
||||
class="disabled-time-item"
|
||||
v-for="(item, index) in timeLimitConfig.notApplyTimeList"
|
||||
:key="item.date + item.startTime + item.endTime + index">
|
||||
{{ item.date }} {{ item.startTime }} - {{ item.endTime }}
|
||||
<div class="panel-card">
|
||||
<button type="button" class="notice-card__header" @click="noticeCollapsed = !noticeCollapsed">
|
||||
<span>预约须知</span>
|
||||
<van-icon :class="['notice-card__icon', noticeCollapsed ? '' : 'notice-card__icon--expanded']" name="arrow-down"></van-icon>
|
||||
</button>
|
||||
<div v-if="!noticeCollapsed" class="notice-card__body">
|
||||
<div class="notice-list">
|
||||
<div class="notice-item">
|
||||
<div class="notice-item__label">可预约日期</div>
|
||||
<div class="notice-item__value">仅限工作日</div>
|
||||
</div>
|
||||
<div class="notice-item">
|
||||
<div class="notice-item__label">节假日限制</div>
|
||||
<div class="notice-item__value">{{ timeLimitConfig.filterHolidays ? '节假日不可预约' : '不排除节假日' }}</div>
|
||||
</div>
|
||||
<div class="notice-item">
|
||||
<div class="notice-item__label">禁用时段</div>
|
||||
<div class="notice-item__value">
|
||||
<div v-if="timeLimitConfig.notApplyTimeList.length" class="disabled-time-list">
|
||||
<div
|
||||
class="disabled-time-item"
|
||||
v-for="(item, index) in timeLimitConfig.notApplyTimeList"
|
||||
:key="item.date + item.startTime + item.endTime + index">
|
||||
{{ item.date }} {{ item.startTime }} - {{ item.endTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>暂无禁用时段</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>暂无禁用时段</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-form ref="formRef" class="form-container" :show-error-message="false">
|
||||
<van-cell-group title="申请信息">
|
||||
<van-field v-model="formData.applyUserName" label="预约人" name="applyUserName" readonly
|
||||
:rules="[{ required: true, message: '请确认预约人' }]"></van-field>
|
||||
<van-field v-model="formData.applyLoginName" label="工号" name="applyLoginName" readonly
|
||||
:rules="[{ required: true, message: '请确认工号' }]"></van-field>
|
||||
<van-field v-model="formData.applyUnitName" label="所属单位" name="applyUnitName" readonly
|
||||
:rules="[{ required: true, message: '请确认所属单位' }]"></van-field>
|
||||
<van-field
|
||||
<div class="panel-card">
|
||||
<van-cell-group title="申请信息">
|
||||
<van-field
|
||||
:value="reserveTypeText"
|
||||
label="预约类型"
|
||||
name="reserveType"
|
||||
@@ -88,18 +402,24 @@ layout("/layouts/platform_h5.html"){
|
||||
is-link
|
||||
required
|
||||
placeholder="请选择预约类型"
|
||||
@click="showReserveTypePicker = true"
|
||||
@click="openReserveTypePicker"
|
||||
:rules="[{ required: true, message: '请选择预约类型' }]">
|
||||
</van-field>
|
||||
<van-field
|
||||
</van-field>
|
||||
<van-field v-model="formData.applyUserName" label="预约人" name="applyUserName" readonly
|
||||
:rules="[{ required: true, message: '请确认预约人' }]"></van-field>
|
||||
<van-field v-model="formData.applyLoginName" label="工号" name="applyLoginName" readonly
|
||||
:rules="[{ required: true, message: '请确认工号' }]"></van-field>
|
||||
<van-field v-model="formData.applyUnitName" label="所属单位" name="applyUnitName" readonly
|
||||
:rules="[{ required: true, message: '请确认所属单位' }]"></van-field>
|
||||
<van-field
|
||||
v-if="formData.reserveType === 'union'"
|
||||
v-model="formData.applyUnionName"
|
||||
label="分工会"
|
||||
name="applyUnionName"
|
||||
readonly
|
||||
placeholder="自动读取当前登录人的分工会">
|
||||
</van-field>
|
||||
<van-field
|
||||
</van-field>
|
||||
<van-field
|
||||
v-if="formData.reserveType === 'club'"
|
||||
v-model="formData.clubName"
|
||||
label="协会"
|
||||
@@ -111,21 +431,58 @@ layout("/layouts/platform_h5.html"){
|
||||
placeholder="请选择您管理的协会"
|
||||
@click="openClubPicker"
|
||||
:rules="[{ required: true, message: '请选择您管理的协会' }]">
|
||||
</van-field>
|
||||
<van-field v-model="formData.applyMobile" label="联系电话" name="applyMobile" type="tel" maxlength="11"
|
||||
required placeholder="请输入联系电话"
|
||||
:rules="[{ required: true, message: '请输入联系电话' }]"></van-field>
|
||||
<van-field v-model="formData.reserveStartTime" label="开始时间" name="reserveStartTime" readonly
|
||||
clickable is-link required placeholder="请选择预约开始时间"
|
||||
@click="openTimePicker('reserveStartTime')"
|
||||
:rules="[{ required: true, message: '请选择预约开始时间' }]"></van-field>
|
||||
<van-field v-model="formData.reserveEndTime" label="结束时间" name="reserveEndTime" readonly clickable
|
||||
is-link required placeholder="请选择预约结束时间" @click="openTimePicker('reserveEndTime')"
|
||||
:rules="[{ required: true, message: '请选择预约结束时间' }]"></van-field>
|
||||
<van-field v-model="formData.applyCause" label="预约事由" name="applyCause" required rows="4" autosize
|
||||
type="textarea" maxlength="1000" show-word-limit placeholder="请输入预约事由"
|
||||
:rules="[{ required: true, message: '请输入预约事由' }]"></van-field>
|
||||
</van-cell-group>
|
||||
</van-field>
|
||||
<van-field v-model="formData.applyMobile" label="联系电话" name="applyMobile" type="tel" maxlength="11"
|
||||
required placeholder="请输入联系电话"
|
||||
:rules="[{ required: true, message: '请输入联系电话' }]"></van-field>
|
||||
<van-field
|
||||
:value="scheduleDate"
|
||||
label="预约日期"
|
||||
name="scheduleDate"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
required
|
||||
placeholder="请选择预约日期"
|
||||
@click="openScheduleDatePicker">
|
||||
</van-field>
|
||||
<van-field
|
||||
:value="selectionSummary || ''"
|
||||
label="预约时段"
|
||||
name="timeSelection"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
required
|
||||
:placeholder="scheduleDate ? '请选择预约时段' : '请先选择预约日期'"
|
||||
@click="openAvailabilityPopup">
|
||||
</van-field>
|
||||
<div v-if="selectionSummary" class="selection-summary">当前已选:{{ selectionSummary }}</div>
|
||||
<van-cell title="批量预约">
|
||||
<template #default>
|
||||
<van-switch v-model="yearlyReserve" size="22px"></van-switch>
|
||||
</template>
|
||||
</van-cell>
|
||||
<div v-if="yearlyReserve" class="batch-panel">
|
||||
<div class="section-tip">将自动预约截止日期内每周同一时段,默认到本月底。</div>
|
||||
<van-field
|
||||
:value="formData.yearlyReserveEndDate"
|
||||
label="截至日期"
|
||||
name="yearlyReserveEndDate"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
required
|
||||
placeholder="请选择批量预约截止日期"
|
||||
@click="openYearlyEndDatePicker">
|
||||
</van-field>
|
||||
<div v-if="batchReserveSummary" class="batch-summary">{{ batchReserveSummary }}</div>
|
||||
</div>
|
||||
<van-field v-model="formData.applyCause" label="预约事由" name="applyCause" required rows="4" autosize
|
||||
type="textarea" maxlength="1000" show-word-limit placeholder="请输入预约事由"
|
||||
:rules="[{ required: true, message: '请输入预约事由' }]"></van-field>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
|
||||
@@ -144,17 +501,76 @@ layout("/layouts/platform_h5.html"){
|
||||
@cancel="showClubPicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="showTimePicker" position="bottom" round>
|
||||
<van-picker
|
||||
ref="timePickerRef"
|
||||
show-toolbar
|
||||
value-key="text"
|
||||
:title="timePickerTitle"
|
||||
:columns="timePickerColumns"
|
||||
@change="onTimePickerChange"
|
||||
@confirm="onTimeConfirm"
|
||||
@cancel="timePickerSyncing = false; showTimePicker = false">
|
||||
</van-picker>
|
||||
<van-popup v-model="showScheduleDatePicker" position="bottom" round>
|
||||
<van-datetime-picker
|
||||
v-model="schedulePickerDate"
|
||||
type="date"
|
||||
title="选择预约日期"
|
||||
:min-date="scheduleMinDate"
|
||||
:max-date="scheduleMaxDate"
|
||||
@confirm="onScheduleDateConfirm"
|
||||
@cancel="showScheduleDatePicker = false">
|
||||
</van-datetime-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="showAvailabilityPopup" position="bottom" round class="availability-popup">
|
||||
<div class="availability-popup__header">
|
||||
<div>
|
||||
<div class="availability-popup__title">选择预约时段</div>
|
||||
<div class="availability-popup__subtitle">{{ scheduleDate || '请选择预约日期' }} {{ reserveModeText }}</div>
|
||||
</div>
|
||||
<van-icon class="availability-popup__close" name="cross" @click="showAvailabilityPopup = false"></van-icon>
|
||||
</div>
|
||||
<div v-if="timeGroupTabs.length" class="availability-popup__tabs">
|
||||
<button
|
||||
v-for="tab in timeGroupTabs"
|
||||
:key="tab.key"
|
||||
type="button"
|
||||
:class="['availability-tab', activeTimeGroupKey === tab.key ? 'availability-tab--active' : '']"
|
||||
@click="activeTimeGroupKey = tab.key">
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="availability-popup__legend">
|
||||
<span class="availability-legend__item"><i class="availability-legend__dot availability-legend__dot--available"></i>可预约</span>
|
||||
<span class="availability-legend__item"><i class="availability-legend__dot availability-legend__dot--reserved"></i>已预约</span>
|
||||
<span class="availability-legend__item"><i class="availability-legend__dot availability-legend__dot--selected"></i>已选择</span>
|
||||
<span class="availability-legend__item"><i class="availability-legend__dot availability-legend__dot--closed"></i>不可预约</span>
|
||||
</div>
|
||||
<div class="availability-popup__body">
|
||||
<van-loading v-if="availabilityLoading" vertical>时段加载中</van-loading>
|
||||
<van-empty v-else-if="!availabilityBlocks.length" description="当前日期暂无可选时段"></van-empty>
|
||||
<van-empty v-else-if="!displayedAvailabilityBlocks.length" description="当前标签暂无可选时段"></van-empty>
|
||||
<div v-else class="slot-grid">
|
||||
<button
|
||||
v-for="block in displayedAvailabilityBlocks"
|
||||
:key="block.key"
|
||||
type="button"
|
||||
:class="getBlockClass(block)"
|
||||
@click="onAvailabilityBlockClick(block)">
|
||||
<span>{{ block.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="availability-popup__footer">
|
||||
<div class="availability-popup__summary">{{ selectionSummary || '请选择一个或多个连续时段,不可跨越间隔选择。' }}</div>
|
||||
<div class="availability-popup__actions">
|
||||
<van-button plain round type="default" @click="clearSelection(true)">清空</van-button>
|
||||
<van-button round type="primary" color="#246fb4" @click="confirmAvailabilitySelection">确定</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="showYearlyEndDatePicker" position="bottom" round>
|
||||
<van-datetime-picker
|
||||
v-model="yearlyReservePickerDate"
|
||||
type="date"
|
||||
title="选择批量预约截止日期"
|
||||
:min-date="yearlyReserveMinDate"
|
||||
:max-date="yearlyReserveMaxDate"
|
||||
@confirm="onYearlyReserveEndDateConfirm"
|
||||
@cancel="showYearlyEndDatePicker = false">
|
||||
</van-datetime-picker>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,13 +3,311 @@ layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.site-page {
|
||||
background: #f5f7fb;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.toolbar-wrap {
|
||||
padding: 10px 10px 8px;
|
||||
background: #f5f7fb;
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.search-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.search-panel /deep/ .van-search {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.search-panel /deep/ .van-search__content {
|
||||
height: 42px;
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.toolbar-btn,
|
||||
.toolbar-icon-btn {
|
||||
height: 42px;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #2563eb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
min-width: 62px;
|
||||
padding: 0 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toolbar-icon-btn {
|
||||
width: 42px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.selected-type-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
color: #2563eb;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.selected-type-text {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-right: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selected-type-clear {
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: #2563eb;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.type-popup {
|
||||
width: calc(100vw - 48px);
|
||||
max-width: 320px;
|
||||
padding: 16px 16px 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.type-popup__header {
|
||||
color: #111827;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.type-popup__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.type-popup__item {
|
||||
min-width: calc(50% - 5px);
|
||||
min-height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #dbe4f0;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.type-popup__item--active {
|
||||
border-color: #2563eb;
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
color: #2563eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.site-card-header {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.site-thumb {
|
||||
width: 92px;
|
||||
height: 92px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, #e5eefb 0%, #f5f9ff 100%);
|
||||
box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.06);
|
||||
}
|
||||
|
||||
.site-thumb img,
|
||||
.site-thumb .van-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.site-thumb-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.site-head-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
color: #111827;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.site-address {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.site-address i {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.site-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.site-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #93c5fd;
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.site-tag--solid {
|
||||
border-color: #2563eb;
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.site-tag--muted {
|
||||
border-color: #cbd5e1;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.site-timeline {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.timeline-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(48, minmax(0, 1fr));
|
||||
gap: 1px;
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.timeline-bar__segment {
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.timeline-bar__segment--available {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
.timeline-bar__segment--reserved {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.timeline-scale {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
padding: 0 2px;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container {
|
||||
margin-top: 8px;
|
||||
padding: 0 8px 12px;
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item {
|
||||
margin-bottom: 12px;
|
||||
padding: 14px;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
border-radius: 22px;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
0 2px 6px rgba(15, 23, 42, 0.05),
|
||||
0 10px 24px rgba(15, 23, 42, 0.08),
|
||||
0 0 0 1px rgba(226, 232, 240, 0.9);
|
||||
}
|
||||
|
||||
/deep/ .table-list-container .table-list-item .item-actions {
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #eef2f7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<div id="app" class="site-page">
|
||||
<van-nav-bar title="场馆预约" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px">
|
||||
<div class="toolbar-wrap">
|
||||
<div class="search-toolbar">
|
||||
<div class="search-panel">
|
||||
<van-search
|
||||
v-model="pageForm.searchKeyword"
|
||||
:show-action="false"
|
||||
@@ -17,18 +315,54 @@ layout("/layouts/platform_h5.html"){
|
||||
input-align="left"
|
||||
placeholder="请输入名称或地址搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
></van-search>
|
||||
</div>
|
||||
<button type="button" class="toolbar-btn" @click="doSearch">搜索</button>
|
||||
<button type="button" class="toolbar-icon-btn" @click="typePopupVisible = true">
|
||||
<van-icon name="apps-o"></van-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="selectedTypeText" class="selected-type-bar">
|
||||
<div class="selected-type-text">已选场地类型:{{ selectedTypeText }}</div>
|
||||
<button type="button" class="selected-type-clear" @click="selectType(null)">清除</button>
|
||||
</div>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/siteCug/apply/pageData" :page_form.sync="pageForm" ref="tableListRef" title="name" @ready="onReady">
|
||||
<table-list api="/platform/siteCug/apply/pageData" :page_form.sync="pageForm" ref="tableListRef" @ready="onReady">
|
||||
<template #header="{ row }">
|
||||
<div class="site-card-header">
|
||||
<div class="site-thumb" @click="previewSitePhoto(row)">
|
||||
<van-image v-if="row.sitePhoto" :src="row.sitePhoto" fit="cover"></van-image>
|
||||
<div v-else class="site-thumb-empty">暂无场地照片</div>
|
||||
</div>
|
||||
<div class="site-head-main">
|
||||
<div class="site-name">{{ row.name || '-' }}</div>
|
||||
<div class="site-address">
|
||||
<i class="fa fa-map-marker"></i>
|
||||
<span>{{ row.address || '-' }}</span>
|
||||
</div>
|
||||
<div class="site-tags">
|
||||
<span class="site-tag site-tag--solid">{{ row.typeName || '活动场地' }}</span>
|
||||
<span class="site-tag">容{{ row.maxNum || '-' }}</span>
|
||||
<span :class="['site-tag', row.state ? '' : 'site-tag--muted']">{{ row.state ? '已开启' : '未开启' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<table-column label="场地地址">{{ row.address }}</table-column>
|
||||
<table-column label="联系人">{{ row.contactName }}</table-column>
|
||||
<table-column label="联系方式">{{ row.contactPhone }}</table-column>
|
||||
<table-column label="场地类型">{{ row.typeName }}</table-column>
|
||||
<div class="site-timeline">
|
||||
<div class="timeline-bar">
|
||||
<span
|
||||
v-for="(segment, index) in row.timelineSegments || []"
|
||||
:key="row.id + '-segment-' + index"
|
||||
:class="['timeline-bar__segment', 'timeline-bar__segment--' + (segment.status || 'closed')]">
|
||||
</span>
|
||||
</div>
|
||||
<div class="timeline-scale">
|
||||
<span v-for="mark in timeMarks" :key="row.id + '-mark-' + mark">{{ mark }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
@@ -42,6 +376,21 @@ layout("/layouts/platform_h5.html"){
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<van-popup v-model="typePopupVisible" round class="type-popup">
|
||||
<div class="type-popup__header">选择场地类型</div>
|
||||
<div class="type-popup__list">
|
||||
<button
|
||||
v-for="item in typeFilterOptions"
|
||||
:key="String(item.value)"
|
||||
type="button"
|
||||
:class="['type-popup__item', pageForm.type === item.value ? 'type-popup__item--active' : '']"
|
||||
@click="selectType(item.value)">
|
||||
<span>{{ item.text }}</span>
|
||||
<van-icon v-if="pageForm.type === item.value" name="success"></van-icon>
|
||||
</button>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<info ref="infoRef"></info>
|
||||
</div>
|
||||
|
||||
@@ -62,11 +411,25 @@ layout("/layouts/platform_h5.html"){
|
||||
searchKeyword: '',
|
||||
type: null,
|
||||
},
|
||||
typeOptions: [],
|
||||
typeFilterOptions: [],
|
||||
typePopupVisible: false,
|
||||
timeMarks: ['0', '2', '4', '6', '8', '10', '12', '14', '16', '18', '20', '22'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedTypeText() {
|
||||
const current = this.typeFilterOptions.find((item) => item.value === this.pageForm.type)
|
||||
return current && current.value !== null ? current.text : ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
historyBack,
|
||||
previewSitePhoto(row) {
|
||||
if (!row || !row.sitePhoto) {
|
||||
return
|
||||
}
|
||||
this.vant.ImagePreview([row.sitePhoto])
|
||||
},
|
||||
onView(row) {
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
@@ -75,14 +438,14 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
async onReady() {
|
||||
const typeList = await this.querySiteType()
|
||||
this.typeOptions = [
|
||||
this.typeFilterOptions = [
|
||||
{
|
||||
text: '全部类型',
|
||||
value: null,
|
||||
}
|
||||
].concat(typeList.map((item) => ({ text: item.name, value: item.id })))
|
||||
if (this.typeOptions.length > 0) {
|
||||
this.pageForm.type = this.typeOptions[0].value
|
||||
if (this.typeFilterOptions.length > 0) {
|
||||
this.pageForm.type = this.typeFilterOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
},
|
||||
@@ -90,6 +453,11 @@ layout("/layouts/platform_h5.html"){
|
||||
const res = await this.$axios.post('/platform/siteCug/function/type/queryFunctionType')
|
||||
return Array.isArray(res.data) ? res.data : []
|
||||
},
|
||||
selectType(value) {
|
||||
this.pageForm.type = value
|
||||
this.typePopupVisible = false
|
||||
this.doSearch()
|
||||
},
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
|
||||
@@ -18,14 +18,14 @@ const siteCugApplyInfoH5 = {
|
||||
</van-cell-group>
|
||||
<template v-for="task in doneTasks">
|
||||
<van-cell-group :title="task.displayName" v-if="task.ext.isFirstTaskNode" :key="task.id + '-first'">
|
||||
<van-cell title="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</van-cell>
|
||||
<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 :key="task.id + '-done'">
|
||||
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</van-cell>
|
||||
<van-cell title="办理人">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</van-cell>
|
||||
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
|
||||
<van-cell title="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
|
||||
@@ -77,29 +77,66 @@ const siteInfo = {
|
||||
},
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/deep/ .van-action-sheet {
|
||||
border-top-left-radius: 24px;
|
||||
border-top-right-radius: 24px;
|
||||
overflow: hidden;
|
||||
background: #f8fafc;
|
||||
}
|
||||
/deep/ .van-action-sheet__header {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
background: #ffffff;
|
||||
}
|
||||
/deep/ .detail-container {
|
||||
padding: 10px 12px 18px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
/deep/ .detail-container .van-cell-group {
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
/deep/ .detail-container .van-cell-group__title {
|
||||
padding: 12px 16px 8px;
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
/deep/ .detail-container .van-cell {
|
||||
min-height: 52px;
|
||||
}
|
||||
/deep/ .direction-column-cell .van-cell__value {
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
/deep/ .table-class {
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
line-height: 1.5rem;
|
||||
line-height: 1.8rem;
|
||||
font-size: 13px;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
background: #ffffff;
|
||||
}
|
||||
/deep/ .table-class th {
|
||||
background-color: #f2f2f2;
|
||||
border: 1px solid #dddddd;
|
||||
padding: 10px 8px;
|
||||
background-color: #f1f5f9;
|
||||
border: 1px solid #dbe4ee;
|
||||
color: #334155;
|
||||
font-weight: 600;
|
||||
}
|
||||
/deep/ .table-class tr {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
border-bottom: 1px solid #dbe4ee;
|
||||
}
|
||||
/deep/ .table-class td {
|
||||
border: 1px solid #dddddd;
|
||||
padding: 10px 8px;
|
||||
border: 1px solid #dbe4ee;
|
||||
color: #475569;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -18,10 +18,8 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<table-list api="/platform/siteCug/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" title="siteName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="场地名称">{{row.siteName}}</table-column>
|
||||
<table-column label="预约人">{{row.applyUserName || '-'}}</table-column>
|
||||
<table-column label="预约类型">{{row.reserveType === 'club' ? '协会预约' : '分工会预约'}}</table-column>
|
||||
<table-column label="预约主体">{{row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-')}}</table-column>
|
||||
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
|
||||
<table-column label="开始时间">{{row.reserveStartTime}}</table-column>
|
||||
<table-column label="结束时间">{{row.reserveEndTime}}</table-column>
|
||||
<table-column label="当前节点">{{row.taskName || '-'}}</table-column>
|
||||
@@ -73,7 +71,6 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
historyBack,
|
||||
async onReady() {
|
||||
await this.querySiteType()
|
||||
await this.querySites()
|
||||
@@ -148,4 +145,4 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
#-->
|
||||
Reference in New Issue
Block a user