Quellcode durchsuchen

PDA拍照功能,ERP销售出货

sunxianglong vor 1 Monat
Ursprung
Commit
cc54bf4b47

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 6696
db/mysql/mes_cloud-2023-6-10.sql


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 11650
mes_cloud_x04c.sql


+ 15 - 0
src/main/java/com/jeesite/modules/mes/dao/MesPhotoDao.java

@@ -0,0 +1,15 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesPhoto;
+
+/**
+ * 照片IdDAO接口
+ * @author sxl
+ * @version 2026-09-09
+ */
+@MyBatisDao
+public interface MesPhotoDao extends CrudDao<MesPhoto> {
+	
+}

+ 32 - 0
src/main/java/com/jeesite/modules/mes/entity/MesPhoto.java

@@ -0,0 +1,32 @@
+package com.jeesite.modules.mes.entity;
+
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+
+/**
+ * 照片IdEntity
+ * @author sxl
+ * @version 2026-09-09
+ */
+@Table(name="mes_photo", alias="a", label="照片Id信息", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="create_by", attrName="createBy", label="创建人", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建时间", isUpdate=false, isUpdateForce=true),
+	}, orderBy="a.create_date DESC"
+)
+public class MesPhoto extends DataEntity<MesPhoto> {
+	
+	private static final long serialVersionUID = 1L;
+
+	public MesPhoto() {
+		this(null);
+	}
+	
+	public MesPhoto(String id){
+		super(id);
+	}
+	
+}

+ 84 - 0
src/main/java/com/jeesite/modules/mes/service/MesPhotoService.java

@@ -0,0 +1,84 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.file.utils.FileUploadUtils;
+import com.jeesite.modules.mes.entity.MesPhoto;
+import com.jeesite.modules.mes.dao.MesPhotoDao;
+
+/**
+ * 照片IdService
+ * @author sxl
+ * @version 2026-09-09
+ */
+@Service
+public class MesPhotoService extends CrudService<MesPhotoDao, MesPhoto> {
+	
+	/**
+	 * 获取单条数据
+	 * @param mesPhoto
+	 * @return
+	 */
+	@Override
+	public MesPhoto get(MesPhoto mesPhoto) {
+		return super.get(mesPhoto);
+	}
+	
+	/**
+	 * 查询分页数据
+	 * @param mesPhoto 查询条件
+	 * @param mesPhoto.page 分页对象
+	 * @return
+	 */
+	@Override
+	public Page<MesPhoto> findPage(MesPhoto mesPhoto) {
+		return super.findPage(mesPhoto);
+	}
+	
+	/**
+	 * 查询列表数据
+	 * @param mesPhoto
+	 * @return
+	 */
+	@Override
+	public List<MesPhoto> findList(MesPhoto mesPhoto) {
+		return super.findList(mesPhoto);
+	}
+	
+	/**
+	 * 保存数据(插入或更新)
+	 * @param mesPhoto
+	 */
+	@Override
+	@Transactional
+	public void save(MesPhoto mesPhoto) {
+		super.save(mesPhoto);
+		// 以主键ID为bizKey存储图片
+		FileUploadUtils.saveFileUpload(mesPhoto, mesPhoto.getId(), "mesPhoto_image");
+	}
+	
+	/**
+	 * 更新状态
+	 * @param mesPhoto
+	 */
+	@Override
+	@Transactional
+	public void updateStatus(MesPhoto mesPhoto) {
+		super.updateStatus(mesPhoto);
+	}
+	
+	/**
+	 * 删除数据
+	 * @param mesPhoto
+	 */
+	@Override
+	@Transactional
+	public void delete(MesPhoto mesPhoto) {
+		super.delete(mesPhoto);
+	}
+	
+}

+ 27 - 0
src/main/java/com/jeesite/modules/mes/util/U9cAuthUtil.java

@@ -36,6 +36,8 @@ public class U9cAuthUtil {
 
 	private static final String CREATE_RTN_ISSUE_DOC_URL = "http://10.0.10.11/u9c/webapi/CustLJRMESOpenAPI/CreateRtnIssueDoc";
 
+	private static final String UPDATE_SHIP_LINE_URL = "http://10.0.10.11/u9c/webapi/CustLJRMESOpenAPI/UpdateShipLine";
+
 	private U9cAuthUtil() {
 	}
 
@@ -158,6 +160,31 @@ public class U9cAuthUtil {
 	}
 
 	/**
+	 * 调用 UpdateShipLine 接口,自动获取 token 并写入请求头
+	 *
+	 * @param requestBody JSON 请求体
+	 * @return 接口响应,失败返回 null
+	 */
+	public static JSONObject updateShipLine(String requestBody) {
+		String token = getToken();
+		if (StringUtils.isEmpty(token)) {
+			log.warn("UpdateShipLine 请求失败: 无法获取 token");
+			return null;
+		}
+		return postWithToken(UPDATE_SHIP_LINE_URL, requestBody, token);
+	}
+
+	/**
+	 * 调用 UpdateShipLine 接口,自动获取 token 并写入请求头
+	 *
+	 * @param requestBody JSON 请求体
+	 * @return 接口响应,失败返回 null
+	 */
+	public static JSONObject updateShipLine(JSONObject requestBody) {
+		return updateShipLine(requestBody == null ? "{}" : requestBody.toJSONString());
+	}
+
+	/**
 	 * U9C 响应 Data 可能是 JSONArray,也可能是单个 JSONObject
 	 */
 	public static JSONArray getErpDataArray(JSONObject response) {

+ 1 - 1
src/main/java/com/jeesite/modules/mes/web/MesOrderSnZpController.java

@@ -138,7 +138,7 @@ public class MesOrderSnZpController extends BaseController {
 		}
 		String ret = mesProductRecordController.checkQualityCommon("OP400", sn, lineSn, userCode);
 		String checkret = ret.substring(3, 5);
-		if (!"UD".equals(checkret) && !"OK".equalsIgnoreCase(checkret) && !"NG".equalsIgnoreCase(checkret)) {
+		if (!"OK".equalsIgnoreCase(checkret) && !"NG".equalsIgnoreCase(checkret)) {
 			String lx = ret.substring(0, 3);
 			return renderResult(Global.FALSE, text(ErrorMsg.getErrorMsg(checkret, lx)));
 		}

+ 191 - 0
src/main/java/com/jeesite/modules/mes/web/MesPhotoController.java

@@ -0,0 +1,191 @@
+package com.jeesite.modules.mes.web;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.jeesite.common.collect.ListUtils;
+import com.jeesite.common.collect.MapUtils;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.file.entity.FileUpload;
+import com.jeesite.modules.file.service.FileUploadService;
+import com.jeesite.modules.mes.entity.MesPhoto;
+import com.jeesite.modules.mes.service.MesPhotoService;
+
+/**
+ * 照片IdController
+ * @author sxl
+ * @version 2026-09-09
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesPhoto")
+public class MesPhotoController extends BaseController {
+
+	@Autowired
+	private MesPhotoService mesPhotoService;
+
+	@Autowired
+	private FileUploadService fileUploadService;
+	
+	/**
+	 * 获取数据
+	 */
+	@ModelAttribute
+	public MesPhoto get(String id, boolean isNewRecord) {
+		return mesPhotoService.get(id, isNewRecord);
+	}
+	
+	/**
+	 * 查询列表
+	 */
+	@RequiresPermissions("mes:mesPhoto:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesPhoto mesPhoto, Model model) {
+		model.addAttribute("mesPhoto", mesPhoto);
+		return "modules/mes/mesPhotoList";
+	}
+	
+	/**
+	 * 查询列表数据
+	 */
+	@RequiresPermissions("mes:mesPhoto:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesPhoto> listData(MesPhoto mesPhoto, HttpServletRequest request, HttpServletResponse response) {
+		mesPhoto.setPage(new Page<>(request, response));
+		Page<MesPhoto> page = mesPhotoService.findPage(mesPhoto);
+		return page;
+	}
+
+	/**
+	 * 查看编辑表单
+	 */
+	@RequiresPermissions("mes:mesPhoto:view")
+	@RequestMapping(value = "form")
+	public String form(MesPhoto mesPhoto, Model model) {
+		model.addAttribute("mesPhoto", mesPhoto);
+		return "modules/mes/mesPhotoForm";
+	}
+
+	/**
+	 * 保存数据
+	 */
+	@RequiresPermissions("mes:mesPhoto:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesPhoto mesPhoto) {
+		mesPhotoService.save(mesPhoto);
+		return renderResult(Global.TRUE, text("保存照片Id成功!"));
+	}
+	
+	/**
+	 * 删除数据
+	 */
+	@RequiresPermissions("mes:mesPhoto:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesPhoto mesPhoto) {
+		mesPhotoService.delete(mesPhoto);
+		return renderResult(Global.TRUE, text("删除照片Id成功!"));
+	}
+
+	/**
+	 * 接收创建时间和图片,写入 mes_photo,并以主键ID为bizKey存储图片
+	 * 图片传参方式与项目其他模块一致(bizType = mesPhoto_image)
+	 */
+	@PostMapping(value = "upload")
+	@ResponseBody
+	public String upload(MesPhoto mesPhoto) {
+		if (mesPhoto == null) {
+			return renderResult(Global.FALSE, text("参数不能为空!"));
+		}
+		if (mesPhoto.getCreateDate() == null) {
+			return renderResult(Global.FALSE, text("创建时间不能为空!"));
+		}
+		try {
+			mesPhoto.setIsNewRecord(true);
+			mesPhoto.setId(null);
+			mesPhotoService.save(mesPhoto);
+			return renderResult(Global.TRUE, text("保存成功!"), mesPhoto);
+		} catch (Exception e) {
+			logger.error("照片上传保存失败", e);
+			return renderResult(Global.FALSE, text("保存失败!"));
+		}
+	}
+
+	/**
+	 * 按年、月筛选创建时间匹配的照片并返回图片列表
+	 */
+	@RequiresPermissions("mes:mesPhoto:view")
+	@RequestMapping(value = "listByMonth")
+	@ResponseBody
+	public List<Map<String, Object>> listByMonth(Integer year, Integer month, HttpServletRequest request) {
+		List<Map<String, Object>> result = new ArrayList<>();
+		if (year == null || month == null || month < 1 || month > 12) {
+			return result;
+		}
+
+		Calendar cal = Calendar.getInstance();
+		cal.clear();
+		cal.set(year, month - 1, 1, 0, 0, 0);
+		Date start = cal.getTime();
+		cal.add(Calendar.MONTH, 1);
+		cal.add(Calendar.SECOND, -1);
+		Date end = cal.getTime();
+
+		MesPhoto query = new MesPhoto();
+		query.getSqlMap().getWhere()
+				.and("a.create_date", QueryType.GTE, start)
+				.and("a.create_date", QueryType.LTE, end);
+		List<MesPhoto> photoList = mesPhotoService.findList(query);
+		if (ListUtils.isEmpty(photoList)) {
+			return result;
+		}
+
+		String host = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/js";
+		for (MesPhoto photo : photoList) {
+			FileUpload fu = new FileUpload();
+			fu.setBizKey(photo.getId());
+			fu.setBizType("mesPhoto_image");
+			List<FileUpload> files = fileUploadService.findList(fu);
+			if (ListUtils.isEmpty(files)) {
+				continue;
+			}
+			String createDateText = photo.getCreateDate() == null
+					? ""
+					: DateUtils.formatDate(photo.getCreateDate(), "yyyy-MM-dd HH:mm:ss");
+			for (FileUpload fi : files) {
+				Map<String, Object> map = MapUtils.newHashMap();
+				map.put("photoId", photo.getId());
+				map.put("createDate", createDateText);
+				map.put("name", fi.getFileName());
+				map.put("url", host + fi.getFileUrl());
+				if (fi.getFileEntity() != null) {
+					map.put("id", fi.getFileEntity().getFileId());
+					map.put("size", fi.getFileEntity().getFileSize());
+				}
+				result.add(map);
+			}
+		}
+		return result;
+	}
+	
+}

+ 61 - 2
src/main/java/com/jeesite/modules/mes/web/MesProductSaleController.java

@@ -15,8 +15,6 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.jeesite.common.config.Global;
 import com.jeesite.common.entity.Page;
@@ -24,6 +22,7 @@ import com.jeesite.common.lang.StringUtils;
 import com.jeesite.common.web.BaseController;
 import com.jeesite.modules.mes.entity.MesProductSale;
 import com.jeesite.modules.mes.service.MesProductSaleService;
+import com.jeesite.modules.mes.util.U9cAuthUtil;
 
 /**
  * 销售出货单表Controller
@@ -159,6 +158,66 @@ public class MesProductSaleController extends BaseController {
 		return renderResult(Global.TRUE, text("保存成功!"));
 	}
 
+	/**
+	 * 查询审核后(shState=1)的销售出货单数据
+	 */
+	@RequestMapping(value = "listByShState")
+	@ResponseBody
+	public List<MesProductSale> listByShState() {
+		MesProductSale query = new MesProductSale();
+		query.setShState("1");
+		return mesProductSaleService.findList(query);
+	}
 
+	/**
+	 * 回写ERP销售出货行(UpdateShipLine)
+	 * 入参示例:
+	 * {"OtherID":"","ID":0,"DocNo":"100SM2607310002","DocLineNo":"10","PrivateDescSeg1":"mes已完成扫码出库"}
+	 */
+	@PostMapping(value = "updateShipLine")
+	@ResponseBody
+	public String updateShipLine(@RequestBody JSONObject body) {
+		if (body == null) {
+			return renderResult(Global.FALSE, text("参数不能为空!"));
+		}
+		String docNo = body.getString("DocNo");
+		if (StringUtils.isBlank(docNo)) {
+			return renderResult(Global.FALSE, text("单据号不能为空!"));
+		}
+		String docLineNo = body.getString("DocLineNo");
+		if (StringUtils.isBlank(docLineNo)) {
+			return renderResult(Global.FALSE, text("行号不能为空!"));
+		}
+
+		JSONObject requestJson = new JSONObject(true);
+		requestJson.put("OtherID", StringUtils.defaultString(body.getString("OtherID")));
+		Integer id = body.getInteger("ID");
+		requestJson.put("ID", id == null ? 0 : id);
+		requestJson.put("DocNo", docNo);
+		requestJson.put("DocLineNo", docLineNo);
+		requestJson.put("PrivateDescSeg1", StringUtils.defaultString(body.getString("PrivateDescSeg1")));
+
+		JSONObject response = U9cAuthUtil.updateShipLine(requestJson.toJSONString());
+		if (response == null) {
+			return renderResult(Global.FALSE, text("同步ERP失败!"));
+		}
+		if (!Boolean.TRUE.equals(response.getBoolean("Success"))) {
+			String msg = U9cAuthUtil.getErpErrorMessage(response);
+			return renderResult(Global.FALSE, text(StringUtils.isNotBlank(msg) ? msg : "同步ERP失败!"));
+		}
+
+		// 同步成功后,将对应销售出货单 mesState 更新为 2=已扫码
+		MesProductSale query = new MesProductSale();
+		query.setSaleOrder(docNo);
+		query.setDocLineNo(docLineNo);
+		List<MesProductSale> list = mesProductSaleService.findList(query);
+		if (list != null && !list.isEmpty()) {
+			for (MesProductSale exist : list) {
+				exist.setMesState("2");
+				mesProductSaleService.save(exist);
+			}
+		}
+		return renderResult(Global.TRUE, text("同步ERP成功!"));
+	}
 
 }

+ 3 - 3
src/main/resources/config/application.yml

@@ -53,8 +53,8 @@ jdbc:
   # Mysql 数据库配置
   type: mysql
   driver: com.mysql.cj.jdbc.Driver
-#  url: jdbc:mysql://127.0.0.1:3306/mes_cloud_ljr?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
-  url: jdbc:mysql://10.0.20.10:3306/mes_cloud_ljr?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+  url: jdbc:mysql://127.0.0.1:3306/mes_cloud_ljr?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+#  url: jdbc:mysql://10.0.20.10:3306/mes_cloud_ljr?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
 #  url: jdbc:mysql://192.168.22.99:3306/mes_cloud?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
 #  username: mes_cloud
 #  password: YD57bSRabiyWBwkZ
@@ -767,7 +767,7 @@ shiro:
     ${adminPath}/mes/mesProductRecord/shiftCapacityData= anon
     ${adminPath}/mes/mesProductRecord/prevShiftCapacityData= anon
     ${adminPath}/mes/mesProduct/hourlyUpDownOkData= anon
-    
+    ${adminPath}/mes/mesProductVoltage/add= anon
     
   
     

+ 15 - 0
src/main/resources/mappings/modules/mes/MesPhotoDao.xml

@@ -0,0 +1,15 @@
+<?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="com.jeesite.modules.mes.dao.MesPhotoDao">
+	
+	<!-- 查询数据
+	<select id="findList" resultType="MesPhoto">
+		SELECT ${sqlMap.column.toSql()}
+		FROM ${sqlMap.table.toSql()}
+		<where>
+			${sqlMap.where.toSql()}
+		</where>
+		ORDER BY ${sqlMap.order.toSql()}
+	</select> -->
+	
+</mapper>

+ 56 - 0
src/main/resources/views/modules/mes/mesPhotoForm.html

@@ -0,0 +1,56 @@
+<% layout('/layouts/default.html', {title: '照片Id管理', libs: ['validate','fileupload']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text(mesPhoto.isNewRecord ? '新增照片Id' : '编辑照片Id')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesPhoto}" action="${ctx}/mes/mesPhoto/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2">
+								<span class="required hide">*</span> ${text('图片上传')}:</label>
+							<div class="col-sm-10">
+								<#form:fileupload id="uploadImage" bizKey="${mesPhoto.id}" bizType="mesPhoto_image"
+									uploadType="image" class="" readonly="false" preview="true"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesPhoto:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 174 - 0
src/main/resources/views/modules/mes/mesPhotoList.html

@@ -0,0 +1,174 @@
+<% layout('/layouts/default.html', {title: '照片Id管理', libs: []}){ %>
+<style>
+	.photo-folder-wrap { padding: 20px; }
+	.photo-folder-nav { margin-bottom: 16px; min-height: 28px; }
+	.photo-folder-nav .crumb { color: #666; margin-right: 8px; }
+	.photo-folder-nav .crumb-link { cursor: pointer; color: #3c8dbc; }
+	.photo-folder-nav .crumb-link:hover { text-decoration: underline; }
+	.photo-folder-btns { display: flex; flex-wrap: wrap; gap: 16px; }
+	.photo-folder-btn {
+		min-width: 120px;
+		height: 80px;
+		font-size: 20px;
+		font-weight: 600;
+		border-radius: 6px;
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+		gap: 8px;
+	}
+	.photo-image-grid {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 16px;
+		padding-top: 8px;
+	}
+	.photo-image-item {
+		width: 220px;
+		text-align: center;
+	}
+	.photo-image-item img {
+		width: 220px;
+		height: 160px;
+		object-fit: cover;
+		border: 1px solid #ddd;
+		border-radius: 4px;
+		padding: 3px;
+		background: #fff;
+		cursor: pointer;
+	}
+	.photo-image-item .meta {
+		margin-top: 6px;
+		font-size: 12px;
+		color: #666;
+		word-break: break-all;
+	}
+	.photo-empty {
+		padding: 30px 10px;
+		color: #999;
+	}
+</style>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('照片Id管理')}
+			</div>
+		</div>
+		<div class="box-body">
+			<div class="photo-folder-wrap">
+				<div class="photo-folder-nav" id="photoFolderNav"></div>
+				<div class="photo-folder-btns" id="photoFolderBtns"></div>
+				<div class="photo-image-grid" id="photoImageGrid" style="display:none;"></div>
+			</div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+(function(){
+	var level = 'year'; // year | month | image
+	var currentYear = '2026';
+	var currentMonth = null;
+	var months = [1,2,3,4,5,6,7,8,9,10,11,12];
+
+	function renderNav(){
+		var html = '';
+		if(level === 'year'){
+			html = '<span class="crumb">${text("全部")}</span>';
+		}else if(level === 'month'){
+			html = '<span class="crumb crumb-link" id="btnBackYear">${text("全部")}</span>';
+			html += '<span class="crumb">/</span>';
+			html += '<span class="crumb">' + currentYear + '</span>';
+		}else{
+			html = '<span class="crumb crumb-link" id="btnBackYear">${text("全部")}</span>';
+			html += '<span class="crumb">/</span>';
+			html += '<span class="crumb crumb-link" id="btnBackMonth">' + currentYear + '</span>';
+			html += '<span class="crumb">/</span>';
+			html += '<span class="crumb">' + currentMonth + '月</span>';
+		}
+		$('#photoFolderNav').html(html);
+		$('#btnBackYear').off('click').on('click', function(){
+			level = 'year';
+			currentMonth = null;
+			render();
+		});
+		$('#btnBackMonth').off('click').on('click', function(){
+			level = 'month';
+			currentMonth = null;
+			render();
+		});
+	}
+
+	function renderBtns(){
+		var html = '';
+		if(level === 'year'){
+			html += '<button type="button" class="btn btn-primary photo-folder-btn" data-year="' + currentYear + '">';
+			html += '<i class="fa fa-folder-open"></i> ' + currentYear;
+			html += '</button>';
+		}else if(level === 'month'){
+			for(var i = 0; i < months.length; i++){
+				var m = months[i];
+				html += '<button type="button" class="btn btn-info photo-folder-btn" data-month="' + m + '">';
+				html += '<i class="fa fa-folder"></i> ' + m + '月';
+				html += '</button>';
+			}
+		}
+		$('#photoFolderBtns').html(html).toggle(level !== 'image');
+
+		$('#photoFolderBtns [data-year]').off('click').on('click', function(){
+			currentYear = $(this).data('year') + '';
+			level = 'month';
+			render();
+		});
+
+		$('#photoFolderBtns [data-month]').off('click').on('click', function(){
+			currentMonth = parseInt($(this).data('month'), 10);
+			level = 'image';
+			render();
+			loadImages();
+		});
+	}
+
+	function renderImages(list){
+		if(!list || list.length === 0){
+			$('#photoImageGrid').html('<div class="photo-empty">${text("该月份暂无图片")}</div>').show();
+			return;
+		}
+		var html = '';
+		for(var i = 0; i < list.length; i++){
+			var item = list[i];
+			html += '<div class="photo-image-item">';
+			html += '<a href="' + item.url + '" target="_blank">';
+			html += '<img src="' + item.url + '" alt="' + (item.name || '') + '" />';
+			html += '</a>';
+			html += '<div class="meta">' + (item.createDate || '') + '</div>';
+			html += '<div class="meta">' + (item.name || '') + '</div>';
+			html += '</div>';
+		}
+		$('#photoImageGrid').html(html).show();
+	}
+
+	function loadImages(){
+		$('#photoImageGrid').html('<div class="photo-empty">${text("加载中...")}</div>').show();
+		$.get('${ctx}/mes/mesPhoto/listByMonth', {
+			year: currentYear,
+			month: currentMonth
+		}, function(list){
+			renderImages(list);
+		}).fail(function(){
+			$('#photoImageGrid').html('<div class="photo-empty">${text("加载失败")}</div>').show();
+		});
+	}
+
+	function render(){
+		renderNav();
+		renderBtns();
+		if(level !== 'image'){
+			$('#photoImageGrid').hide().empty();
+		}
+	}
+
+	render();
+})();
+</script>