【新增】PS考点设置后端
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
package pc.exam.pp.module.exam.controller.admin.exception;
|
||||||
|
|
||||||
|
public class PsException extends RuntimeException {
|
||||||
|
|
||||||
|
public PsException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
package pc.exam.pp.module.exam.dal.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@TableName(value = "exam_ps_keyword", autoResultMap = true)
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ExamPsKeyword {
|
||||||
|
/**
|
||||||
|
* 主键id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 试题id
|
||||||
|
*/
|
||||||
|
private String quId;
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
private String parentId;
|
||||||
|
/**
|
||||||
|
* 键
|
||||||
|
*/
|
||||||
|
private String keyName;
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
private String keyValue;
|
||||||
|
/**
|
||||||
|
* 权值
|
||||||
|
*/
|
||||||
|
private String rate;
|
||||||
|
/**
|
||||||
|
* 类型(学生考点:1,结果考点:2)
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.admin.Ps;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import pc.exam.pp.module.judgement.controller.service.mysql.IMysqlServerice;
|
||||||
|
import pc.exam.pp.module.judgement.controller.service.ps.IPsService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/tool/Ps")
|
||||||
|
public class PsController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPsService psService;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import pc.exam.pp.framework.common.pojo.CommonResult;
|
import pc.exam.pp.framework.common.pojo.CommonResult;
|
||||||
import pc.exam.pp.framework.tenant.core.aop.TenantIgnore;
|
import pc.exam.pp.framework.tenant.core.aop.TenantIgnore;
|
||||||
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo;
|
||||||
import pc.exam.pp.module.judgement.controller.service.getpoints.ExamGetPointsService;
|
import pc.exam.pp.module.judgement.controller.service.getpoints.ExamGetPointsService;
|
||||||
@@ -76,4 +77,41 @@ public class GetPointsController {
|
|||||||
public CommonResult get_browser_point(@RequestBody Points points) {
|
public CommonResult get_browser_point(@RequestBody Points points) {
|
||||||
return CommonResult.success(examGetPointsService.get_browser_point(points));
|
return CommonResult.success(examGetPointsService.get_browser_point(points));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件得出PS操作考点
|
||||||
|
* @return 得分
|
||||||
|
*/
|
||||||
|
@Operation(summary = "文件得出PS操作考点")
|
||||||
|
@PostMapping("/get_ps_point")
|
||||||
|
@TenantIgnore
|
||||||
|
public CommonResult get_ps_point(@RequestBody PointsVo pointsVo) throws IOException {
|
||||||
|
try {
|
||||||
|
return CommonResult.success(examGetPointsService.get_ps_point(pointsVo));
|
||||||
|
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return CommonResult.error(987312, e.getMessage());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return CommonResult.error(987312, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置PS操作考点
|
||||||
|
* @return 得分
|
||||||
|
*/
|
||||||
|
@Operation(summary = "文件设置PS操作考点")
|
||||||
|
@PostMapping("/set_ps_point")
|
||||||
|
@TenantIgnore
|
||||||
|
public void set_ps_point(@RequestBody PsAnswerSubmitDTO psAnswerSubmitDTO) {
|
||||||
|
examGetPointsService.set_ps_point(psAnswerSubmitDTO);
|
||||||
|
}
|
||||||
|
@Operation(summary = "获取PS操作考点")
|
||||||
|
@GetMapping("/getPsPointById/{quId}")
|
||||||
|
@TenantIgnore
|
||||||
|
public CommonResult getPsPointById(@PathVariable("quId") String quId) {
|
||||||
|
return CommonResult.success(examGetPointsService.getPsPointById(quId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.admin.getpoints.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PsAnswerNode {
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
private Integer rate;
|
||||||
|
|
||||||
|
private List<PsAnswerNode> children;
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.admin.getpoints.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PsAnswerSubmitDTO {
|
||||||
|
private String quId;
|
||||||
|
private List<PsAnswerNode> questionAnswerList;
|
||||||
|
private String type;
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,17 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.admin.getpoints.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PsViewDto {
|
||||||
|
private List<PsVo> pointList;
|
||||||
|
|
||||||
|
private List<PsVo> answerList;
|
||||||
|
}
|
@@ -8,7 +8,9 @@ import lombok.NoArgsConstructor;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class PointsVo {
|
public class PointsVo {
|
||||||
|
//原始文件路径
|
||||||
private String shucaiPath;
|
private String shucaiPath;
|
||||||
|
//答案文件路径
|
||||||
private String answerPath;
|
private String answerPath;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.admin.getpoints.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PsVo {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
private List<PsVo> children;
|
||||||
|
}
|
@@ -1,6 +1,9 @@
|
|||||||
package pc.exam.pp.module.judgement.controller.service.getpoints;
|
package pc.exam.pp.module.judgement.controller.service.getpoints;
|
||||||
|
|
||||||
|
import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword;
|
||||||
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsViewDto;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FilePointsVo;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FilePointsVo;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo;
|
||||||
@@ -23,4 +26,10 @@ public interface ExamGetPointsService {
|
|||||||
boolean update_mysql_point(Points points);
|
boolean update_mysql_point(Points points);
|
||||||
|
|
||||||
List<ExamQuestionAnswer> getPointById(String quId);
|
List<ExamQuestionAnswer> getPointById(String quId);
|
||||||
|
|
||||||
|
PsViewDto get_ps_point(PointsVo pointsVo) throws RuntimeException, IOException, InterruptedException;
|
||||||
|
|
||||||
|
void set_ps_point(PsAnswerSubmitDTO psAnswerSubmitDTO);
|
||||||
|
|
||||||
|
PsViewDto getPsPointById(String quId);
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,17 @@
|
|||||||
package pc.exam.pp.module.judgement.controller.service.getpoints;
|
package pc.exam.pp.module.judgement.controller.service.getpoints;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.xingyuv.jushauth.utils.UuidUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.ibatis.executor.BatchResult;
|
import org.apache.ibatis.executor.BatchResult;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import pc.exam.pp.module.exam.controller.admin.exception.PsException;
|
||||||
import pc.exam.pp.module.exam.dal.dataobject.ExamMysqlKeyword;
|
import pc.exam.pp.module.exam.dal.dataobject.ExamMysqlKeyword;
|
||||||
|
import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword;
|
||||||
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
|
||||||
import pc.exam.pp.module.exam.dal.mysql.mysqlkeyword.MysqlKeywordMapper;
|
import pc.exam.pp.module.exam.dal.mysql.mysqlkeyword.MysqlKeywordMapper;
|
||||||
import pc.exam.pp.module.exam.dal.mysql.question.ExamQuestionAnswerMapper;
|
import pc.exam.pp.module.exam.dal.mysql.question.ExamQuestionAnswerMapper;
|
||||||
@@ -15,17 +20,22 @@ import pc.exam.pp.module.exam.utils.file.GetDifferencesBetweenFolders;
|
|||||||
import pc.exam.pp.module.exam.utils.uuid.IdUtils;
|
import pc.exam.pp.module.exam.utils.uuid.IdUtils;
|
||||||
import pc.exam.pp.module.infra.dal.dataobject.config.ConfigDO;
|
import pc.exam.pp.module.infra.dal.dataobject.config.ConfigDO;
|
||||||
import pc.exam.pp.module.infra.service.config.ConfigService;
|
import pc.exam.pp.module.infra.service.config.ConfigService;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FileNode;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerNode;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.FilePointsVo;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsAnswerSubmitDTO;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.Points;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.dto.PsViewDto;
|
||||||
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PointsVo;
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.*;
|
||||||
|
import pc.exam.pp.module.judgement.controller.service.ps.IPsService;
|
||||||
|
import pc.exam.pp.module.judgement.controller.utils.ps.PsUtil;
|
||||||
import pc.exam.pp.module.judgement.controller.utils.zip.ZipUtil;
|
import pc.exam.pp.module.judgement.controller.utils.zip.ZipUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.MalformedInputException;
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -43,7 +53,8 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{
|
|||||||
MysqlKeywordMapper mysqlKeywordMapper;
|
MysqlKeywordMapper mysqlKeywordMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ExamQuestionAnswerMapper examQuestionAnswerMapper;
|
private ExamQuestionAnswerMapper examQuestionAnswerMapper;
|
||||||
|
@Resource
|
||||||
|
private IPsService psService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilePointsVo get_file_point(PointsVo pointsVo) throws IOException {
|
public FilePointsVo get_file_point(PointsVo pointsVo) throws IOException {
|
||||||
@@ -375,5 +386,322 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{
|
|||||||
return examQuestionAnswers;
|
return examQuestionAnswers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PsViewDto get_ps_point(PointsVo pointsVo) throws RuntimeException {
|
||||||
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||||
|
String baseDir = "ps\\" + timestamp;
|
||||||
|
|
||||||
|
// 下载学生文件和答案文件
|
||||||
|
String sthPath = downloadStudentFile(pointsVo.getShucaiPath(), baseDir);
|
||||||
|
String answerPath = downloadStudentFile(pointsVo.getAnswerPath(), baseDir);
|
||||||
|
|
||||||
|
System.out.println("学生PSD路径: " + sthPath);
|
||||||
|
System.out.println("答案PSD路径: " + answerPath);
|
||||||
|
|
||||||
|
String sthJsonPath = sthPath.replaceAll("(?i)\\.psd$", ".json");
|
||||||
|
String answerJsonPath = answerPath.replaceAll("(?i)\\.psd$", ".json");
|
||||||
|
|
||||||
|
String photoshopExe = PsUtil.findPhotoshopExe();
|
||||||
|
if (photoshopExe == null) {
|
||||||
|
throw new PsException("请先在本机安装PS软件!");
|
||||||
|
}
|
||||||
|
|
||||||
|
String homeDir = System.getProperty("user.dir");
|
||||||
|
String jsxTemplatePath = homeDir + "\\ps\\checkPSD.jsx"; // 模板路径
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 执行学生和答案PSD文件
|
||||||
|
runTwoPsdsInOneScript(sthPath, answerPath, jsxTemplatePath, photoshopExe);
|
||||||
|
System.out.println("Photoshop脚本执行完毕");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("执行 Photoshop 脚本失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取两个 JSON 文件
|
||||||
|
try {
|
||||||
|
String sthJsonStr = Files.readString(Paths.get(sthJsonPath), StandardCharsets.UTF_8);
|
||||||
|
String answerJsonStr = Files.readString(Paths.get(answerJsonPath), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
Map<String,Object> jsonStuMap = mapper.readValue(sthJsonStr, new TypeReference<Map<String,Object>>() {});
|
||||||
|
List<PsVo> stulist = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : jsonStuMap.entrySet()) {
|
||||||
|
stulist.add(mapToPsVo(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
Map<String,Object> jsonAnswerMap = mapper.readValue(answerJsonStr, new TypeReference<Map<String,Object>>() {});
|
||||||
|
List<PsVo> answerlist = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : jsonAnswerMap.entrySet()) {
|
||||||
|
answerlist.add(mapToPsVo(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println("✅ 学生PSD数据: " + stulist);
|
||||||
|
System.out.println("✅ 答案PSD数据: " + answerlist);
|
||||||
|
PsViewDto psViewDto=new PsViewDto();
|
||||||
|
|
||||||
|
List<PsVo> pointList = findExtraOrDifferentInAnswer(stulist, answerlist);
|
||||||
|
System.out.println("答案相对考生多出来的节点集合: " + pointList);
|
||||||
|
|
||||||
|
psViewDto.setPointList(pointList);
|
||||||
|
psViewDto.setAnswerList(answerlist);
|
||||||
|
|
||||||
|
return psViewDto;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace(); // 打印异常堆栈
|
||||||
|
throw new PsException("读取 JSON 文件失败!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set_ps_point(PsAnswerSubmitDTO psAnswerSubmitDTO) {
|
||||||
|
List<ExamPsKeyword> stuList = new ArrayList<>();
|
||||||
|
String quId = String.valueOf(psAnswerSubmitDTO.getQuId());
|
||||||
|
|
||||||
|
// 开始递归转换 学生考点
|
||||||
|
for (PsAnswerNode node : psAnswerSubmitDTO.getQuestionAnswerList()) {
|
||||||
|
convertNodeToKeyword(node, "0", quId, stuList);
|
||||||
|
}
|
||||||
|
//给List统一设置type
|
||||||
|
String type = psAnswerSubmitDTO.getType();
|
||||||
|
for (ExamPsKeyword keyword : stuList) {
|
||||||
|
keyword.setType(type);
|
||||||
|
}
|
||||||
|
stuList.forEach(System.out::println);
|
||||||
|
//先根据删除试题id删除
|
||||||
|
psService.deletedKeywordById(quId);
|
||||||
|
//再更新
|
||||||
|
psService.insertPsKeywordList(stuList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PsViewDto getPsPointById(String quId) {
|
||||||
|
PsViewDto psViewDto=new PsViewDto();
|
||||||
|
List<PsVo> pointList=psService.selectPsPointByType(quId,"1");
|
||||||
|
List<PsVo> answerList=psService.selectPsPointByType(quId,"2");
|
||||||
|
psViewDto.setAnswerList(answerList);
|
||||||
|
psViewDto.setPointList(pointList);
|
||||||
|
return psViewDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 递归转换方法
|
||||||
|
private void convertNodeToKeyword(PsAnswerNode node, String parentId, String quId, List<ExamPsKeyword> resultList) {
|
||||||
|
// 生成唯一 ID(用 UUID 或其它方式)
|
||||||
|
String id = IdUtils.simpleUUID();
|
||||||
|
|
||||||
|
ExamPsKeyword keyword = new ExamPsKeyword();
|
||||||
|
keyword.setId(id);
|
||||||
|
keyword.setQuId(quId);
|
||||||
|
keyword.setParentId(parentId);
|
||||||
|
keyword.setKeyName(node.getKey());
|
||||||
|
keyword.setKeyValue(node.getValue());
|
||||||
|
keyword.setRate(node.getRate() == null ? null : node.getRate().toString());
|
||||||
|
resultList.add(keyword);
|
||||||
|
|
||||||
|
// 递归子节点
|
||||||
|
if (node.getChildren() != null) {
|
||||||
|
for (PsAnswerNode child : node.getChildren()) {
|
||||||
|
convertNodeToKeyword(child, id, quId, resultList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public List<PsVo> findExtraOrDifferentInAnswer(List<PsVo> stuList, List<PsVo> answerList) {
|
||||||
|
if (answerList == null) return Collections.emptyList();
|
||||||
|
if (stuList == null) stuList = Collections.emptyList();
|
||||||
|
|
||||||
|
// 把学生列表转为 key->PsVo 映射
|
||||||
|
Map<String, PsVo> stuMap = stuList.stream()
|
||||||
|
.collect(Collectors.toMap(PsVo::getKey, p -> p, (a, b) -> a));
|
||||||
|
|
||||||
|
List<PsVo> extraList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PsVo ansNode : answerList) {
|
||||||
|
PsVo stuNode = stuMap.get(ansNode.getKey());
|
||||||
|
|
||||||
|
if (stuNode == null) {
|
||||||
|
// 学生没有该键,整项加入
|
||||||
|
extraList.add(ansNode);
|
||||||
|
} else {
|
||||||
|
boolean valueDifferent = !Objects.equals(ansNode.getValue(), stuNode.getValue());
|
||||||
|
|
||||||
|
// 递归对比子节点
|
||||||
|
List<PsVo> ansChildren = ansNode.getChildren() != null ? ansNode.getChildren() : Collections.emptyList();
|
||||||
|
List<PsVo> stuChildren = stuNode.getChildren() != null ? stuNode.getChildren() : Collections.emptyList();
|
||||||
|
List<PsVo> childDiffs = findExtraOrDifferentInAnswer(stuChildren, ansChildren);
|
||||||
|
|
||||||
|
if (valueDifferent || !childDiffs.isEmpty()) {
|
||||||
|
PsVo diffNode = new PsVo();
|
||||||
|
diffNode.setKey(ansNode.getKey());
|
||||||
|
diffNode.setValue(valueDifferent ? ansNode.getValue() : null);
|
||||||
|
diffNode.setChildren(childDiffs.isEmpty() ? null : childDiffs);
|
||||||
|
extraList.add(diffNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extraList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PsVo mapToPsVo(String key, Object value) {
|
||||||
|
PsVo vo = new PsVo();
|
||||||
|
vo.setKey(key);
|
||||||
|
|
||||||
|
if (value instanceof Map) {
|
||||||
|
Map<String, Object> map = (Map<String, Object>) value;
|
||||||
|
List<PsVo> children = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
|
children.add(mapToPsVo(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
vo.setChildren(children);
|
||||||
|
vo.setValue(null);
|
||||||
|
|
||||||
|
} else if (value instanceof List) {
|
||||||
|
List<?> list = (List<?>) value;
|
||||||
|
List<PsVo> children = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Object item : list) {
|
||||||
|
if (item instanceof Map) {
|
||||||
|
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||||
|
// 获取图层名作为 key
|
||||||
|
String layerName = (String) mapItem.get("图层名");
|
||||||
|
if (layerName == null) {
|
||||||
|
// 如果没有图层名,直接用空字符串
|
||||||
|
layerName = "";
|
||||||
|
}
|
||||||
|
PsVo childVo = new PsVo();
|
||||||
|
childVo.setKey(layerName);
|
||||||
|
|
||||||
|
// 除了图层名外的字段作为 children
|
||||||
|
List<PsVo> grandChildren = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> e : mapItem.entrySet()) {
|
||||||
|
if (!"图层名".equals(e.getKey())) {
|
||||||
|
grandChildren.add(mapToPsVo(e.getKey(), e.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childVo.setChildren(grandChildren);
|
||||||
|
childVo.setValue(null);
|
||||||
|
|
||||||
|
children.add(childVo);
|
||||||
|
} else {
|
||||||
|
// 如果不是 Map 类型,按原逻辑处理
|
||||||
|
children.add(mapToPsVo(null, item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setChildren(children);
|
||||||
|
vo.setValue(null);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 基础类型
|
||||||
|
vo.setValue(value == null ? null : value.toString());
|
||||||
|
vo.setChildren(null);
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTwoPsdsInOneScript(String psdPath1, String psdPath2, String jsxTemplatePath, String photoshopExe)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
File psdFile1 = new File(psdPath1);
|
||||||
|
String baseDir = psdFile1.getParent();
|
||||||
|
String jsxTargetPath = baseDir + File.separator + "run_both_" + System.currentTimeMillis() + ".jsx";
|
||||||
|
|
||||||
|
String jsxTemplate = Files.readString(Paths.get(jsxTemplatePath), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String safePath1 = psdPath1.replace("\\", "\\\\").replace("'", "\\'");
|
||||||
|
String safePath2 = psdPath2.replace("\\", "\\\\").replace("'", "\\'");
|
||||||
|
String jsxContent = jsxTemplate
|
||||||
|
.replace("${inputPath1}", safePath1)
|
||||||
|
.replace("${inputPath2}", safePath2);
|
||||||
|
|
||||||
|
Files.writeString(Paths.get(jsxTargetPath), jsxContent, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String command = String.format("\"%s\" -r \"%s\"", photoshopExe, jsxTargetPath);
|
||||||
|
System.out.println("运行 Photoshop 脚本: " + command);
|
||||||
|
|
||||||
|
Process process = Runtime.getRuntime().exec(command);
|
||||||
|
|
||||||
|
// 异步打印输出日志
|
||||||
|
new Thread(() -> {
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
System.out.println("[PS OUT] " + line);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
System.err.println("[PS ERR] " + line);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
// 不使用 waitFor 阻塞,改成轮询判断两个 JSON 文件是否生成
|
||||||
|
String jsonPath1 = psdPath1.replaceAll("(?i)\\.psd$", ".json");
|
||||||
|
String jsonPath2 = psdPath2.replaceAll("(?i)\\.psd$", ".json");
|
||||||
|
|
||||||
|
int maxWaitSeconds = 60; // 最多等待 60秒,根据实际调整
|
||||||
|
int waited = 0;
|
||||||
|
while (!(Files.exists(Paths.get(jsonPath1)) && Files.exists(Paths.get(jsonPath2)))) {
|
||||||
|
Thread.sleep(1000); // 每秒检查一次
|
||||||
|
waited++;
|
||||||
|
if (waited > maxWaitSeconds) {
|
||||||
|
// 超时,结束进程并抛异常
|
||||||
|
process.destroyForcibly();
|
||||||
|
throw new RuntimeException("等待 Photoshop 生成 JSON 文件超时");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("检测到 JSON 文件生成,关闭 Photoshop 进程...");
|
||||||
|
|
||||||
|
// 手动杀死 Photoshop 进程,确保释放资源
|
||||||
|
Process killProcess = Runtime.getRuntime().exec("taskkill /IM Photoshop.exe /F");
|
||||||
|
killProcess.waitFor();
|
||||||
|
|
||||||
|
System.out.println("Photoshop 进程已关闭");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public String downloadStudentFile(String fileUrl, String filePath) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(fileUrl);
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
|
String fileName = new File(url.getPath()).getName();
|
||||||
|
File dir = new File(filePath);
|
||||||
|
if (!dir.exists()) dir.mkdirs();
|
||||||
|
|
||||||
|
File saveFile = new File(dir, fileName);
|
||||||
|
|
||||||
|
try (InputStream in = connection.getInputStream();
|
||||||
|
FileOutputStream out = new FileOutputStream(saveFile)) {
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("✅ 下载成功: " + saveFile.getAbsolutePath());
|
||||||
|
return saveFile.getAbsolutePath();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("❌ 下载失败: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.service.ps;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IPsService {
|
||||||
|
void deletedKeywordById(String quId);
|
||||||
|
|
||||||
|
void insertPsKeywordList(List<ExamPsKeyword> resultList);
|
||||||
|
|
||||||
|
|
||||||
|
List<PsVo> selectPsPointByType(String quId, String type);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.service.ps;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo;
|
||||||
|
import pc.exam.pp.module.judgement.dal.mysql.ps.PsMapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PsServiceImpl implements IPsService {
|
||||||
|
@Resource
|
||||||
|
PsMapper psMapper;
|
||||||
|
@Override
|
||||||
|
public void deletedKeywordById(String quId) {
|
||||||
|
psMapper.deleteByQuId(quId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertPsKeywordList(List<ExamPsKeyword> resultList) {
|
||||||
|
psMapper.insertBatch(resultList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PsVo> selectPsPointByType(String quId, String type) {
|
||||||
|
return psMapper.selectPsPointByType(quId,type);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,53 @@
|
|||||||
|
package pc.exam.pp.module.judgement.controller.utils.ps;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
public class PsUtil {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 查询注册表路径,返回command字符串(即exe路径+参数)
|
||||||
|
private static String queryReg(String regPath) throws IOException, InterruptedException {
|
||||||
|
Process process = Runtime.getRuntime().exec("reg query \"" + regPath + "\" /ve");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK")); // 注册表输出一般GBK编码
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.startsWith("(默认)")) {
|
||||||
|
String[] parts = line.split(" ");
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
return parts[parts.length - 1].replace("\"", "").trim(); // 去掉双引号
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.waitFor();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试读取多个注册表路径获取 Photoshop 路径
|
||||||
|
public static String findPhotoshopExe() {
|
||||||
|
String[] regPaths = {
|
||||||
|
"HKLM\\SOFTWARE\\Classes\\Applications\\Photoshop.exe\\shell\\edit\\command",
|
||||||
|
"HKLM\\SOFTWARE\\WOW6432Node\\Classes\\Applications\\Photoshop.exe\\shell\\edit\\command"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String path : regPaths) {
|
||||||
|
try {
|
||||||
|
String exePath = queryReg(path);
|
||||||
|
if (exePath != null && !exePath.isEmpty()) {
|
||||||
|
// 通常 exe 路径后面会跟参数 %1,只取 exe 路径部分
|
||||||
|
int idx = exePath.toLowerCase().indexOf("photoshop.exe");
|
||||||
|
if (idx != -1) {
|
||||||
|
exePath = exePath.substring(0, idx + "photoshop.exe".length());
|
||||||
|
}
|
||||||
|
return exePath;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略异常,继续尝试其他路径
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // 没找到
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
package pc.exam.pp.module.judgement.dal.mysql.ps;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import pc.exam.pp.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import pc.exam.pp.module.exam.dal.dataobject.ExamPsKeyword;
|
||||||
|
import pc.exam.pp.module.judgement.controller.admin.getpoints.vo.PsVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PsMapper extends BaseMapperX<ExamPsKeyword> {
|
||||||
|
|
||||||
|
void deleteByQuId(String quId);
|
||||||
|
|
||||||
|
|
||||||
|
List<PsVo> selectPsPointByType(@Param("quId") String quId
|
||||||
|
,@Param("type") String type);
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="pc.exam.pp.module.judgement.dal.mysql.ps.PsMapper">
|
||||||
|
|
||||||
|
|
||||||
|
<resultMap type="ExamPsKeyword" id="ExamPsKeywordResult">
|
||||||
|
<id property="id" column="id" jdbcType="VARCHAR"/>
|
||||||
|
<result property="quId" column="qu_id" jdbcType="VARCHAR"/>
|
||||||
|
<result property="parentId" column="parent_id" jdbcType="VARCHAR"/>
|
||||||
|
<result property="keyName" column="key_name" jdbcType="VARCHAR"/>
|
||||||
|
<result property="keyValue" column="key_value" jdbcType="VARCHAR"/>
|
||||||
|
<result property="rate" column="rate" jdbcType="VARCHAR"/>
|
||||||
|
<result property="type" column="type" jdbcType="VARCHAR"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<delete id="deleteByQuId">
|
||||||
|
delete from exam_ps_keyword where qu_id =#{quId}
|
||||||
|
</delete>
|
||||||
|
<select id="selectPsPointByType" resultMap="ExamPsKeywordResult">
|
||||||
|
select id,qu_id,parent_id,key_name,key_value,rate,type from exam_ps_keyword where qu_id=#{quId}
|
||||||
|
and type=#{type}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
@@ -352,6 +352,7 @@ exam:
|
|||||||
- education_paper_session
|
- education_paper_session
|
||||||
- exam_mysql_keyword
|
- exam_mysql_keyword
|
||||||
- education_question_examine
|
- education_question_examine
|
||||||
|
- exam_ps_keyword
|
||||||
ignore-caches:
|
ignore-caches:
|
||||||
- user_role_ids
|
- user_role_ids
|
||||||
- permission_menu_ids
|
- permission_menu_ids
|
||||||
|
693
ps/checkPSD.jsx
Normal file
693
ps/checkPSD.jsx
Normal file
@@ -0,0 +1,693 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// 简单 JSON.stringify 兼容
|
||||||
|
if (typeof JSON === "undefined") {
|
||||||
|
JSON = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputPath1 = '${inputPath1}';
|
||||||
|
var inputPath2 = '${inputPath2}';
|
||||||
|
// 载入XMP扩展支持(全局调用一次即可)
|
||||||
|
if (!ExternalObject.AdobeXMPScript) {
|
||||||
|
ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
|
||||||
|
}
|
||||||
|
JSON.stringify = function (obj, replacer, space) {
|
||||||
|
if (typeof space === "number") {
|
||||||
|
space = new Array(space + 1).join(" ");
|
||||||
|
}
|
||||||
|
function format(value, indent) {
|
||||||
|
var type = typeof value;
|
||||||
|
if (value === null) return "null";
|
||||||
|
if (type === "string") return '"' + value + '"';
|
||||||
|
if (type === "number" || type === "boolean") return String(value);
|
||||||
|
if (type === "object") {
|
||||||
|
var isArray = (value instanceof Array);
|
||||||
|
var items = [];
|
||||||
|
var pad = indent + (space || "");
|
||||||
|
for (var key in value) {
|
||||||
|
if (value.hasOwnProperty(key)) {
|
||||||
|
var val = format(value[key], pad);
|
||||||
|
if (!isArray) {
|
||||||
|
val = '"' + key + '": ' + val;
|
||||||
|
}
|
||||||
|
items.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isArray) {
|
||||||
|
return "[\n" + pad + items.join(",\n" + pad) + "\n" + indent + "]";
|
||||||
|
} else {
|
||||||
|
return "{\n" + pad + items.join(",\n" + pad) + "\n" + indent + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
return format(obj, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
function processFile(path) {
|
||||||
|
var fileRef = new File(path);
|
||||||
|
if (!fileRef.exists) {
|
||||||
|
// alert("PSD文件不存在: " + path);
|
||||||
|
throw new Error("PSD文件不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var doc = app.open(fileRef);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var result = {};
|
||||||
|
|
||||||
|
// 文档属性
|
||||||
|
result["颜色模式"] = getColorModeName(doc.mode);
|
||||||
|
result["颜色深度"] = getBitsPerChannelLabel(doc.bitsPerChannel);
|
||||||
|
result["图像宽度"] = doc.width.as('px') + " 像素";
|
||||||
|
result["图像高度"] = doc.height.as('px') + " 像素";
|
||||||
|
result["画布宽度"]= doc.width.as('px') + " 像素";
|
||||||
|
result["画布高度"]= doc.height.as('px') + " 像素";
|
||||||
|
result["分辨率"] = doc.resolution + " dpi";
|
||||||
|
|
||||||
|
// 图层数组
|
||||||
|
result["图层数"] = doc.artLayers.length;
|
||||||
|
result["智能对象图层数"] = countSmartObjects(doc);
|
||||||
|
|
||||||
|
// 图层详细信息
|
||||||
|
result["图层信息"] = [];
|
||||||
|
for (var i = 0; i < doc.artLayers.length; i++) {
|
||||||
|
var layer = doc.artLayers[i];
|
||||||
|
doc.activeLayer = layer; // 激活当前图层
|
||||||
|
|
||||||
|
|
||||||
|
var info = {};
|
||||||
|
info["图层名"] = layer.name;
|
||||||
|
info["类型"] = getLayerKindName(layer.kind);
|
||||||
|
info["可见"] = layer.visible;
|
||||||
|
info["锁定"] = layer.allLocked;
|
||||||
|
|
||||||
|
try {
|
||||||
|
info["图层蒙版密度"] = layer.layerMaskDensity !== undefined ? layer.layerMaskDensity : null;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
|
||||||
|
// 文字图层特殊信息
|
||||||
|
if (layer.kind === LayerKind.TEXT) {
|
||||||
|
var t = layer.textItem;
|
||||||
|
try {
|
||||||
|
info["文字内容"] = t.contents;
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info["字体"] = fontMap[t.font] || t.font;} // 如果找不到对应中文名,则保留英文名
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info["字号"] = t.size.toString(); }// 修正这里
|
||||||
|
catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
info["颜色"] = getSolidColorHex(t.color);}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info["字距"] = t.tracking;}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info["仿斜体"] = t.fauxItalic;}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info["仿粗体"] = t.fauxBold;
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var transformProps = getTextTransformProps();
|
||||||
|
if (transformProps) {
|
||||||
|
info["变形"] = transformProps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 详细读取样式(如果你需要更详细数据的话)
|
||||||
|
var layerStyles = getLayerStyles();
|
||||||
|
if (layerStyles) {
|
||||||
|
info["图层样式"] = layerStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测滤镜信息,放到info里
|
||||||
|
var filterInfo = detectFilters(layer.name);
|
||||||
|
if (filterInfo) {
|
||||||
|
info["滤镜信息"] = filterInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
result["图层信息"].push(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var jsonFilePath = path.replace(/\.psd$/i, ".json");
|
||||||
|
var jsonFile = new File(jsonFilePath);
|
||||||
|
|
||||||
|
if (jsonFile.open("w")) {
|
||||||
|
jsonFile.encoding = "UTF8";
|
||||||
|
jsonFile.write(JSON.stringify(result, null, 2));
|
||||||
|
jsonFile.close();
|
||||||
|
// alert("JSON 文件已生成: " + jsonFilePath);
|
||||||
|
} else {
|
||||||
|
// alert("无法打开文件进行写入: " + jsonFilePath);
|
||||||
|
throw new Error("无法打开文件进行写入");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
// 运行两个 PSD 的处理
|
||||||
|
processFile(inputPath1);
|
||||||
|
processFile(inputPath2);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
// 辅助函数 - 颜色模式
|
||||||
|
function getColorModeName(mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case DocumentMode.RGB: return "RGB";
|
||||||
|
case DocumentMode.CMYK: return "CMYK";
|
||||||
|
case DocumentMode.GRAYSCALE: return "灰度";
|
||||||
|
case DocumentMode.BITMAP: return "位图";
|
||||||
|
case DocumentMode.INDEXEDCOLOR: return "索引颜色";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getBitsPerChannelLabel(bits) {
|
||||||
|
switch (bits) {
|
||||||
|
case BitsPerChannelType.ONE: return "1 位/通道";
|
||||||
|
case BitsPerChannelType.EIGHT: return "8 位/通道";
|
||||||
|
case BitsPerChannelType.SIXTEEN: return "16 位/通道";
|
||||||
|
case BitsPerChannelType.THIRTYTWO: return "32 位/通道";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数 - 图层类型名
|
||||||
|
function getLayerKindName(kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case LayerKind.NORMAL: return "普通图层";
|
||||||
|
case LayerKind.TEXT: return "文字图层";
|
||||||
|
case LayerKind.SMARTOBJECT: return "智能对象图层";
|
||||||
|
case LayerKind.SOLIDFILL: return "纯色填充图层";
|
||||||
|
case LayerKind.GRADIENTFILL: return "渐变填充图层";
|
||||||
|
case LayerKind.PATTERNFILL: return "图案填充图层";
|
||||||
|
case LayerKind.BRIGHTNESSCONTRAST: return "亮度/对比度调整层";
|
||||||
|
case LayerKind.LEVELS: return "色阶调整层";
|
||||||
|
case LayerKind.CURVES: return "曲线调整层";
|
||||||
|
case LayerKind.EXPOSURE: return "曝光度调整层";
|
||||||
|
case LayerKind.VIBRANCE: return "自然饱和度调整层";
|
||||||
|
case LayerKind.HUESATURATION: return "色相/饱和度调整层";
|
||||||
|
case LayerKind.COLORBALANCE: return "色彩平衡调整层";
|
||||||
|
case LayerKind.BLACKANDWHITE: return "黑白调整层";
|
||||||
|
case LayerKind.PHOTOFILTER: return "照片滤镜调整层";
|
||||||
|
case LayerKind.CHANNELMIXER: return "通道混合器调整层";
|
||||||
|
case LayerKind.COLORLOOKUP: return "颜色查找调整层";
|
||||||
|
case LayerKind.INVERSION: return "反相调整层";
|
||||||
|
case LayerKind.POSTERIZE: return "色调分离调整层";
|
||||||
|
case LayerKind.THRESHOLD: return "阈值调整层";
|
||||||
|
case LayerKind.GRADIENTMAP: return "渐变映射调整层";
|
||||||
|
case LayerKind.SELECTIVECOLOR: return "可选颜色调整层";
|
||||||
|
default: return "未知类型";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取颜色为HEX
|
||||||
|
function getSolidColorHex(color) {
|
||||||
|
if (!color) return null;
|
||||||
|
if (color.typename === "SolidColor") {
|
||||||
|
if (color.rgb) {
|
||||||
|
var r = Math.round(color.rgb.red);
|
||||||
|
var g = Math.round(color.rgb.green);
|
||||||
|
var b = Math.round(color.rgb.blue);
|
||||||
|
return "#" + toHex(r) + toHex(g) + toHex(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function toHex(c) {
|
||||||
|
var h = c.toString(16);
|
||||||
|
return h.length == 1 ? "0" + h : h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计智能对象图层数量
|
||||||
|
function countSmartObjects(doc) {
|
||||||
|
var count = 0;
|
||||||
|
for (var i = 0; i < doc.artLayers.length; i++) {
|
||||||
|
if (doc.artLayers[i].kind === LayerKind.SMARTOBJECT) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文字图层的变形属性
|
||||||
|
function getTextTransformProps() {
|
||||||
|
try {
|
||||||
|
var ref = new ActionReference();
|
||||||
|
ref.putEnumerated(stringIDToTypeID("layer"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
||||||
|
var desc = executeActionGet(ref);
|
||||||
|
if (desc.hasKey(stringIDToTypeID("textKey"))) {
|
||||||
|
var textDesc = desc.getObjectValue(stringIDToTypeID("textKey"));
|
||||||
|
if (textDesc.hasKey(stringIDToTypeID("transform"))) {
|
||||||
|
var transformDesc = textDesc.getObjectValue(stringIDToTypeID("transform"));
|
||||||
|
var horizontal = transformDesc.getDouble(stringIDToTypeID("horizontalShear"));
|
||||||
|
var vertical = transformDesc.getDouble(stringIDToTypeID("verticalShear"));
|
||||||
|
var scaleX = transformDesc.getDouble(stringIDToTypeID("horizontalScale"));
|
||||||
|
var scaleY = transformDesc.getDouble(stringIDToTypeID("verticalScale"));
|
||||||
|
return {
|
||||||
|
"水平扭曲": horizontal.toFixed(2) + "%",
|
||||||
|
"垂直扭曲": vertical.toFixed(2) + "%",
|
||||||
|
"水平缩放": scaleX.toFixed(2) + "%",
|
||||||
|
"垂直缩放": scaleY.toFixed(2) + "%"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLayerStyles() {
|
||||||
|
try {
|
||||||
|
var ref = new ActionReference();
|
||||||
|
ref.putEnumerated(stringIDToTypeID("layer"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
||||||
|
var desc = executeActionGet(ref);
|
||||||
|
|
||||||
|
if (!desc.hasKey(stringIDToTypeID("layerEffects"))) {
|
||||||
|
$.writeln("图层无layerEffects");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effects = desc.getObjectValue(stringIDToTypeID("layerEffects"));
|
||||||
|
var styles = {};
|
||||||
|
|
||||||
|
// ============ 描边 ===============
|
||||||
|
if (effects.hasKey(stringIDToTypeID("frameFX"))) {
|
||||||
|
var frameFX = effects.getObjectValue(stringIDToTypeID("frameFX"));
|
||||||
|
styles["描边_启用"] = frameFX.getBoolean(stringIDToTypeID("enabled"));
|
||||||
|
if (styles["描边_启用"]) {
|
||||||
|
if (frameFX.hasKey(stringIDToTypeID("size"))) {
|
||||||
|
styles["描边_大小"] = frameFX.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素";
|
||||||
|
}
|
||||||
|
if (frameFX.hasKey(stringIDToTypeID("color"))) {
|
||||||
|
var colorDesc = frameFX.getObjectValue(stringIDToTypeID("color"));
|
||||||
|
styles["描边_颜色"] = colorDescToHex(colorDesc);
|
||||||
|
}
|
||||||
|
if (frameFX.hasKey(stringIDToTypeID("position"))) {
|
||||||
|
var pos = frameFX.getEnumerationValue(stringIDToTypeID("position"));
|
||||||
|
styles["描边_位置"] = getStrokePositionName(pos);
|
||||||
|
}
|
||||||
|
if (frameFX.hasKey(stringIDToTypeID("opacity"))) {
|
||||||
|
styles["描边_不透明度"] = Math.round(frameFX.getUnitDoubleValue(stringIDToTypeID("opacity"))) + "%";
|
||||||
|
}
|
||||||
|
if (frameFX.hasKey(stringIDToTypeID("blendMode"))) {
|
||||||
|
var blendMode = frameFX.getEnumerationValue(stringIDToTypeID("blendMode"));
|
||||||
|
styles["描边_混合模式"] = getBlendModeName(blendMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 内发光 ===============
|
||||||
|
if (effects.hasKey(stringIDToTypeID("innerGlow"))) {
|
||||||
|
var innerGlow = effects.getObjectValue(stringIDToTypeID("innerGlow"));
|
||||||
|
styles["内发光_启用"] = innerGlow.getBoolean(stringIDToTypeID("enabled"));
|
||||||
|
if (styles["内发光_启用"]) {
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("opacity"))) {
|
||||||
|
styles["内发光_不透明度"] = Math.round(innerGlow.getUnitDoubleValue(stringIDToTypeID("opacity")) ) + "%";
|
||||||
|
}
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("color"))) {
|
||||||
|
styles["内发光_颜色"] = colorDescToHex(innerGlow.getObjectValue(stringIDToTypeID("color")));
|
||||||
|
}
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("glowTechnique"))) {
|
||||||
|
var tech = innerGlow.getEnumerationValue(stringIDToTypeID("glowTechnique"));
|
||||||
|
styles["内发光_技巧"] = getInnerGlowTechniqueName(tech);
|
||||||
|
}
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("source"))) {
|
||||||
|
var source = innerGlow.getEnumerationValue(stringIDToTypeID("source"));
|
||||||
|
styles["内发光_来源"] = getInnerGlowSourceName(source);
|
||||||
|
}
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("chokeMatte"))) {
|
||||||
|
styles["内发光_阻塞"] = innerGlow.getUnitDoubleValue(stringIDToTypeID("chokeMatte")) + " 像素";
|
||||||
|
}
|
||||||
|
if (innerGlow.hasKey(stringIDToTypeID("blur"))) {
|
||||||
|
styles["内发光_模糊"] = innerGlow.getUnitDoubleValue(stringIDToTypeID("blur")) + " 像素";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 斜面和浮雕 ===============
|
||||||
|
if (effects.hasKey(stringIDToTypeID("bevelEmboss"))) {
|
||||||
|
var bevel = effects.getObjectValue(stringIDToTypeID("bevelEmboss"));
|
||||||
|
styles["斜面和浮雕_启用"] = bevel.getBoolean(stringIDToTypeID("enabled"));
|
||||||
|
if (styles["斜面和浮雕_启用"]) {
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("style"))) {
|
||||||
|
styles["斜面和浮雕_样式"] = getBevelStyleName(bevel.getEnumerationValue(stringIDToTypeID("style")));
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("technique"))) {
|
||||||
|
styles["斜面和浮雕_方法"] = getBevelTechniqueName(bevel.getEnumerationValue(stringIDToTypeID("technique")));
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("depth"))) {
|
||||||
|
styles["斜面和浮雕_深度"] = bevel.getInteger(stringIDToTypeID("depth")) + "%";
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("direction"))) {
|
||||||
|
styles["斜面和浮雕_方向"] = getBevelDirectionName(bevel.getEnumerationValue(stringIDToTypeID("direction")));
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("size"))) {
|
||||||
|
styles["斜面和浮雕_大小"] = bevel.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素";
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("soften"))) {
|
||||||
|
styles["斜面和浮雕_软化"] = bevel.getUnitDoubleValue(stringIDToTypeID("soften")) + " 像素";
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("useGlobalAngle"))) {
|
||||||
|
styles["斜面和浮雕_全局光"] = bevel.getBoolean(stringIDToTypeID("useGlobalAngle"));
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("angle"))) {
|
||||||
|
styles["斜面和浮雕_角度"] = bevel.getDouble(stringIDToTypeID("angle")).toFixed(1) + "°";
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("highlightMode"))) {
|
||||||
|
var highlight = bevel.getObjectValue(stringIDToTypeID("highlightMode"));
|
||||||
|
if (highlight.hasKey(stringIDToTypeID("mode"))) {
|
||||||
|
styles["斜面和浮雕_高光模式"] = getBlendModeName(highlight.getEnumerationValue(stringIDToTypeID("mode")));
|
||||||
|
}
|
||||||
|
if (highlight.hasKey(stringIDToTypeID("color"))) {
|
||||||
|
styles["斜面和浮雕_高光颜色"] = colorDescToHex(highlight.getObjectValue(stringIDToTypeID("color")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bevel.hasKey(stringIDToTypeID("shadowMode"))) {
|
||||||
|
var shadow = bevel.getObjectValue(stringIDToTypeID("shadowMode"));
|
||||||
|
if (shadow.hasKey(stringIDToTypeID("mode"))) {
|
||||||
|
styles["斜面和浮雕_阴影模式"] = getBlendModeName(shadow.getEnumerationValue(stringIDToTypeID("mode")));
|
||||||
|
}
|
||||||
|
if (shadow.hasKey(stringIDToTypeID("color"))) {
|
||||||
|
styles["斜面和浮雕_阴影颜色"] = colorDescToHex(shadow.getObjectValue(stringIDToTypeID("color")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 光泽 ===============
|
||||||
|
if (effects.hasKey(stringIDToTypeID("glossContour"))) {
|
||||||
|
styles["光泽_启用"] = true; // 简化处理
|
||||||
|
|
||||||
|
var gloss = effects.getObjectValue(stringIDToTypeID("glossContour"));
|
||||||
|
if (gloss.hasKey(stringIDToTypeID("range"))) {
|
||||||
|
styles["光泽_范围"] = (gloss.getDouble(stringIDToTypeID("range")) * 100).toFixed(1) + "%";
|
||||||
|
}
|
||||||
|
if (gloss.hasKey(stringIDToTypeID("highlightSize"))) {
|
||||||
|
styles["光泽_高光大小"] = gloss.getUnitDoubleValue(stringIDToTypeID("highlightSize")) + " 像素";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 外发光 ===============
|
||||||
|
if (effects.hasKey(stringIDToTypeID("outerGlow"))) {
|
||||||
|
var outerGlow = effects.getObjectValue(stringIDToTypeID("outerGlow"));
|
||||||
|
styles["外发光_启用"] = outerGlow.getBoolean(stringIDToTypeID("enabled"));
|
||||||
|
if (styles["外发光_启用"]) {
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("opacity"))) {
|
||||||
|
styles["外发光_不透明度"] = Math.round(outerGlow.getUnitDoubleValue(stringIDToTypeID("opacity")) ) + "%";
|
||||||
|
}
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("color"))) {
|
||||||
|
styles["外发光_颜色"] = colorDescToHex(outerGlow.getObjectValue(stringIDToTypeID("color")));
|
||||||
|
}
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("technique"))) {
|
||||||
|
styles["外发光_图素方法"] = getOuterGlowTechniqueName(outerGlow.getEnumerationValue(stringIDToTypeID("technique")));
|
||||||
|
}
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("size"))) {
|
||||||
|
styles["外发光_大小"] = outerGlow.getUnitDoubleValue(stringIDToTypeID("size")) + " 像素";
|
||||||
|
}
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("spread"))) {
|
||||||
|
styles["外发光_扩展"] = outerGlow.getUnitDoubleValue(stringIDToTypeID("spread")) + " 像素";
|
||||||
|
}
|
||||||
|
if (outerGlow.hasKey(stringIDToTypeID("contour"))) {
|
||||||
|
var contour = outerGlow.getObjectValue(stringIDToTypeID("contour"));
|
||||||
|
styles["外发光_等高线"] = getContourName(contour.getEnumerationValue(stringIDToTypeID("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasOwnProperties(styles) ? styles : null;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
$.writeln("读取图层样式异常: " + e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 辅助函数:描边位置
|
||||||
|
function getStrokePositionName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("inside"): return "内侧";
|
||||||
|
case stringIDToTypeID("center"): return "居中";
|
||||||
|
case stringIDToTypeID("outside"): return "外侧";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 斜面和浮雕 样式名称
|
||||||
|
function getBevelStyleName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("outerBevel"): return "外斜面";
|
||||||
|
case stringIDToTypeID("innerBevel"): return "内斜面";
|
||||||
|
case stringIDToTypeID("emboss"): return "浮雕";
|
||||||
|
case stringIDToTypeID("pillowEmboss"): return "枕头状浮雕";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 斜面和浮雕 方法
|
||||||
|
function getBevelTechniqueName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("smooth"): return "平滑";
|
||||||
|
case stringIDToTypeID("chiselHard"): return "雕刻清晰";
|
||||||
|
case stringIDToTypeID("chiselSoft"): return "雕刻柔和";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 斜面和浮雕 方向
|
||||||
|
function getBevelDirectionName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("up"): return "上";
|
||||||
|
case stringIDToTypeID("down"): return "下";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 图层混合模式
|
||||||
|
function getBlendModeName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("normal"): return "正常";
|
||||||
|
case stringIDToTypeID("multiply"): return "正片叠底";
|
||||||
|
case stringIDToTypeID("screen"): return "滤色";
|
||||||
|
case stringIDToTypeID("overlay"): return "叠加";
|
||||||
|
case stringIDToTypeID("softLight"): return "柔光";
|
||||||
|
case stringIDToTypeID("hardLight"): return "强光";
|
||||||
|
case stringIDToTypeID("difference"): return "差值";
|
||||||
|
case stringIDToTypeID("colorBurn"): return "颜色加深";
|
||||||
|
case stringIDToTypeID("colorDodge"): return "颜色减淡";
|
||||||
|
case stringIDToTypeID("lighten"): return "变亮";
|
||||||
|
case stringIDToTypeID("darken"): return "变暗";
|
||||||
|
default: return "其他";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 外发光 图素方法
|
||||||
|
function getOuterGlowTechniqueName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("precise"): return "精确";
|
||||||
|
case stringIDToTypeID("softer"): return "柔和";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 等高线名称(外发光等)
|
||||||
|
function getContourName(value) {
|
||||||
|
switch (value) {
|
||||||
|
case stringIDToTypeID("linear"): return "线性";
|
||||||
|
case stringIDToTypeID("ring"): return "环形";
|
||||||
|
case stringIDToTypeID("cone"): return "锥形";
|
||||||
|
case stringIDToTypeID("cove"): return "凹形";
|
||||||
|
case stringIDToTypeID("stairs"): return "阶梯";
|
||||||
|
case stringIDToTypeID("custom"): return "自定义";
|
||||||
|
case stringIDToTypeID("zigZag"): return "锯齿1";
|
||||||
|
case stringIDToTypeID("roundedSteps"): return "圆角阶梯";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function colorDescToHex(colorDesc) {
|
||||||
|
try {
|
||||||
|
var red = Math.round(colorDesc.getDouble(stringIDToTypeID('red')));
|
||||||
|
var green = Math.round(colorDesc.getDouble(stringIDToTypeID('green')));
|
||||||
|
var blue = Math.round(colorDesc.getDouble(stringIDToTypeID('blue')));
|
||||||
|
return rgbToHex(red, green, blue);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getInnerGlowTechniqueName(value) {
|
||||||
|
switch(value) {
|
||||||
|
case stringIDToTypeID("precise"): return "精确";
|
||||||
|
case stringIDToTypeID("softer"): return "柔和";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getInnerGlowSourceName(value) {
|
||||||
|
switch(value) {
|
||||||
|
case stringIDToTypeID("center"): return "中心";
|
||||||
|
case stringIDToTypeID("edge"): return "边缘";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return "#" + [r, g, b].map(function(x) {
|
||||||
|
var hex = x.toString(16);
|
||||||
|
return hex.length === 1 ? "0" + hex : hex;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滤镜检测示例(只检测图层名包含的关键词)
|
||||||
|
// 修改后的detectFilters函数,优先用XMP元数据读取镜头光晕参数
|
||||||
|
function detectFilters(layerName) {
|
||||||
|
var filters = {};
|
||||||
|
if (!layerName || typeof layerName !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var name = layerName.toLowerCase();
|
||||||
|
|
||||||
|
// 先尝试读取XMP中镜头光晕参数
|
||||||
|
|
||||||
|
var lensFlareMeta = readHistoryFromXMP();
|
||||||
|
if (lensFlareMeta) {
|
||||||
|
return lensFlareMeta; // 直接返回读取的元数据
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// // 没读到元数据,返回默认提示
|
||||||
|
// filters["滤镜类型"] = "镜头光晕";
|
||||||
|
// filters["亮度"] = "⚠️ 无法读取,建议人工确认";
|
||||||
|
// filters["镜头类型"] = "⚠️ 无法读取,建议人工确认";
|
||||||
|
// return filters;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 结合你的读取函数用法举例
|
||||||
|
function readHistoryFromXMP() {
|
||||||
|
try {
|
||||||
|
if (typeof ExternalObject.AdobeXMPScript === "undefined") {
|
||||||
|
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmp = new XMPMeta(app.activeDocument.xmpMetadata.rawData);
|
||||||
|
var nsMM = "http://ns.adobe.com/xap/1.0/mm/";
|
||||||
|
var nsEvt = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#";
|
||||||
|
|
||||||
|
XMPMeta.registerNamespace(nsMM, "xmpMM");
|
||||||
|
XMPMeta.registerNamespace(nsEvt, "stEvt");
|
||||||
|
|
||||||
|
var count = xmp.countArrayItems(nsMM, "History");
|
||||||
|
if (count === 0) {
|
||||||
|
$.writeln("没有找到历史记录");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 1; i <= count; i++) {
|
||||||
|
var itemXMP = xmp.getArrayItem(nsMM, "History", i);
|
||||||
|
var historyLine = itemXMP.toString();
|
||||||
|
|
||||||
|
$.writeln("历史记录第" + i + "条: " + historyLine);
|
||||||
|
|
||||||
|
if (historyLine.indexOf("镜头光晕") !== -1 || historyLine.toLowerCase().indexOf("lens flare") !== -1) {
|
||||||
|
var brightnessMatch = historyLine.match(/亮度[::]?\s*(\d+%?)/);
|
||||||
|
var typeMatch = historyLine.match(/类型[::]?\s*([^\s]+)/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"历史":historyLine,
|
||||||
|
"滤镜类型": "镜头光晕",
|
||||||
|
"亮度": brightnessMatch ? brightnessMatch[1] : "未知",
|
||||||
|
"镜头类型": typeMatch ? typeMatch[1] : "未知"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
"读取XMP历史记录失败:": e.message
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var fontMap = {
|
||||||
|
"SimSun": "宋体",
|
||||||
|
"SimHei": "黑体",
|
||||||
|
"FangSong_GB2312": "仿宋",
|
||||||
|
"KaiTi_GB2312": "楷体",
|
||||||
|
"MicrosoftYaHei": "微软雅黑",
|
||||||
|
"MicrosoftJhengHei": "微软正黑体",
|
||||||
|
"NSimSun": "新宋体",
|
||||||
|
"PMingLiU": "新细明体",
|
||||||
|
"MingLiU": "细明体",
|
||||||
|
"DFKai-SB": "标楷体",
|
||||||
|
"STKaiti": "华文楷体",
|
||||||
|
"STSong": "华文宋体",
|
||||||
|
"STHeiti": "华文黑体",
|
||||||
|
"STFangsong": "华文仿宋",
|
||||||
|
"STXihei": "华文细黑",
|
||||||
|
"STZhongsong": "华文中宋",
|
||||||
|
"STXinwei": "华文新魏",
|
||||||
|
"STLiti": "华文隶书",
|
||||||
|
"STHupo": "华文琥珀",
|
||||||
|
"STCaiyun": "华文彩云",
|
||||||
|
"STXingkai": "华文行楷",
|
||||||
|
"STHupo": "华文琥珀",
|
||||||
|
"STFangsong": "华文仿宋",
|
||||||
|
"Arial": "Arial(英文字体)",
|
||||||
|
"TimesNewRomanPSMT": "Times New Roman",
|
||||||
|
"CourierNewPSMT": "Courier New",
|
||||||
|
"Georgia": "Georgia",
|
||||||
|
"Tahoma": "Tahoma",
|
||||||
|
"Verdana": "Verdana"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断对象是否含有自有属性(兼容无Object.keys的环境)
|
||||||
|
function hasOwnProperties(obj) {
|
||||||
|
for (var key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user