Ver Fonte

修改获取订单列表

qmj há 1 mês atrás
pai
commit
1eed708981

+ 1 - 0
AGENTS.md

@@ -0,0 +1 @@
+CLAUDE.md

+ 899 - 0
docs/superpowers/plans/2026-05-19-order-log.md

@@ -0,0 +1,899 @@
+# 订单操作日志 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 新建 pos_order_log 表,在所有订单状态变更处自动记录日志,并在平台管理端提供查询/导出页面。
+
+**Architecture:** 跟随 SysOperLog 的模式——Entity + Mapper(XML) + Service + Controller。日志写入封装为一个工具类 `OrderLogHelper`,供各 Controller 和定时任务调用。前端新增独立的日志查询页面挂到"订单管理"菜单下。
+
+**Tech Stack:** Java (Spring Boot, MyBatis XML), Vue.js (Element UI), MySQL
+
+---
+
+## File Structure
+
+| 操作 | 文件 | 职责 |
+|------|------|------|
+| Create | `ruoyi-system/.../domain/PosOrderLog.java` | 实体类 |
+| Create | `ruoyi-system/.../mapper/PosOrderLogMapper.java` | Mapper 接口 |
+| Create | `ruoyi-system/.../mapper/PosOrderLogMapper.xml` (resources/mapper/system/) | XML mapper |
+| Create | `ruoyi-system/.../service/IPosOrderLogService.java` | Service 接口 |
+| Create | `ruoyi-system/.../service/impl/PosOrderLogServiceImpl.java` | Service 实现 |
+| Create | `ruoyi-admin/.../controller/system/PosOrderLogController.java` | 后台管理接口 |
+| Create | `ruoyi-system/.../utils/OrderLogHelper.java` | 日志写入工具类 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderShOprateController.java` | 商家操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderQsOprateController.java` | 骑手操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/UserOrderController.java` | 用户操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderController.java` | 平台操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/TestTask.java` | 系统定时任务插入日志 |
+| Modify | `ruoyi-admin/.../app/pay/PayController.java` | 支付回调插入日志 |
+| Create | `foodie-admin-vue/src/api/system/orderLog.js` | 前端 API |
+| Create | `foodie-admin-vue/src/views/system/order/log.vue` | 前端页面 |
+
+---
+
+### Task 1: 创建 PosOrderLog 实体类
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java`
+
+- [ ] **Step 1: 创建实体类**
+
+```java
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 订单操作日志表 pos_order_log
+ */
+public class PosOrderLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @Excel(name = "主键ID", cellType = ColumnType.NUMERIC)
+    private Long id;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String ddId;
+
+    /** 操作人类型:0系统,1平台管理员,2商家,3骑手,4用户 */
+    @Excel(name = "操作人类型", readConverterExp = "0=系统,1=平台管理员,2=商家,3=骑手,4=用户")
+    private Integer operatorType;
+
+    /** 操作人ID */
+    @Excel(name = "操作人ID", cellType = ColumnType.NUMERIC)
+    private Long operatorId;
+
+    /** 操作人名称 */
+    @Excel(name = "操作人名称")
+    private String operatorName;
+
+    /** 操作内容 */
+    @Excel(name = "操作内容")
+    private String content;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date logTime;
+
+    public Long getId() { return id; }
+    public void setId(Long id) { this.id = id; }
+    public String getDdId() { return ddId; }
+    public void setDdId(String ddId) { this.ddId = ddId; }
+    public Integer getOperatorType() { return operatorType; }
+    public void setOperatorType(Integer operatorType) { this.operatorType = operatorType; }
+    public Long getOperatorId() { return operatorId; }
+    public void setOperatorId(Long operatorId) { this.operatorId = operatorId; }
+    public String getOperatorName() { return operatorName; }
+    public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Date getLogTime() { return logTime; }
+    public void setLogTime(Date logTime) { this.logTime = logTime; }
+}
+```
+
+注意:`logTime` 使用 `logTime` 而非 `createTime`,避免与 BaseEntity 的 `createTime` 冲突。BaseEntity 提供 `params` map 用于查询参数传递。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java
+git commit -m "feat(order-log): add PosOrderLog entity"
+```
+
+---
+
+### Task 2: 创建 MyBatis Mapper 接口和 XML
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java`
+- Create: `ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml`
+
+- [ ] **Step 1: 创建 Mapper 接口**
+
+```java
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+/**
+ * 订单操作日志 数据层
+ */
+public interface PosOrderLogMapper
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}
+```
+
+- [ ] **Step 2: 创建 XML Mapper**
+
+```xml
+<?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.ruoyi.system.mapper.PosOrderLogMapper">
+
+    <resultMap type="PosOrderLog" id="PosOrderLogResult">
+        <id     property="id"            column="id"             />
+        <result property="ddId"          column="dd_id"          />
+        <result property="operatorType"  column="operator_type"  />
+        <result property="operatorId"    column="operator_id"    />
+        <result property="operatorName"  column="operator_name"  />
+        <result property="content"       column="content"        />
+        <result property="logTime"       column="create_time"    />
+    </resultMap>
+
+    <sql id="selectOrderLogVo">
+        select id, dd_id, operator_type, operator_id, operator_name, content, create_time
+        from pos_order_log
+    </sql>
+
+    <insert id="insertOrderLog" parameterType="PosOrderLog">
+        insert into pos_order_log(dd_id, operator_type, operator_id, operator_name, content, create_time)
+        values (#{ddId}, #{operatorType}, #{operatorId}, #{operatorName}, #{content}, sysdate())
+    </insert>
+
+    <select id="selectOrderLogList" parameterType="PosOrderLog" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        <where>
+            <if test="ddId != null and ddId != ''">
+                AND dd_id like concat('%', #{ddId}, '%')
+            </if>
+            <if test="operatorId != null">
+                AND operator_id = #{operatorId}
+            </if>
+            <if test="operatorName != null and operatorName != ''">
+                AND operator_name like concat('%', #{operatorName}, '%')
+            </if>
+            <if test="operatorType != null">
+                AND operator_type = #{operatorType}
+            </if>
+            <if test="params.beginTime != null and params.beginTime != ''">
+                AND create_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params.endTime != null and params.endTime != ''">
+                AND create_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectOrderLogById" parameterType="Long" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        where id = #{id}
+    </select>
+
+</mapper>
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml
+git commit -m "feat(order-log): add PosOrderLog mapper interface and XML"
+```
+
+---
+
+### Task 3: 创建 Service 层
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java`
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java`
+
+- [ ] **Step 1: 创建 Service 接口**
+
+```java
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+/**
+ * 订单操作日志 服务层
+ */
+public interface IPosOrderLogService
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}
+```
+
+- [ ] **Step 2: 创建 Service 实现**
+
+```java
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.mapper.PosOrderLogMapper;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+/**
+ * 订单操作日志 服务层处理
+ */
+@Service
+public class PosOrderLogServiceImpl implements IPosOrderLogService
+{
+    @Autowired
+    private PosOrderLogMapper orderLogMapper;
+
+    @Override
+    public void insertOrderLog(PosOrderLog orderLog)
+    {
+        orderLogMapper.insertOrderLog(orderLog);
+    }
+
+    @Override
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog)
+    {
+        return orderLogMapper.selectOrderLogList(orderLog);
+    }
+
+    @Override
+    public PosOrderLog selectOrderLogById(Long id)
+    {
+        return orderLogMapper.selectOrderLogById(id);
+    }
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java
+git commit -m "feat(order-log): add service interface and implementation"
+```
+
+---
+
+### Task 4: 创建 OrderLogHelper 工具类
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java`
+
+这个工具类封装日志构建逻辑,各调用方只需传入 ddId、operatorType、operatorId、operatorName、content 即可。
+
+- [ ] **Step 1: 创建工具类**
+
+```java
+package com.ruoyi.system.utils;
+
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.TimerTask;
+
+/**
+ * 订单操作日志工具类
+ */
+@Component
+public class OrderLogHelper {
+
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    /**
+     * 异步写入订单日志
+     */
+    public void log(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog log = new PosOrderLog();
+        log.setDdId(ddId);
+        log.setOperatorType(operatorType);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setContent(content);
+        AsyncManager.me().execute(new TimerTask() {
+            @Override
+            public void run() {
+                orderLogService.insertOrderLog(log);
+            }
+        });
+    }
+
+    /**
+     * 同步写入订单日志(用于定时任务等异步上下文)
+     */
+    public void logSync(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog log = new PosOrderLog();
+        log.setDdId(ddId);
+        log.setOperatorType(operatorType);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setContent(content);
+        orderLogService.insertOrderLog(log);
+    }
+}
+```
+
+使用 `AsyncManager` 异步写入,避免影响主流程性能。`logSync` 给定时任务使用(定时任务已在异步线程中)。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java
+git commit -m "feat(order-log): add OrderLogHelper utility for async log writing"
+```
+
+---
+
+### Task 5: 创建后台管理 Controller
+
+**Files:**
+- Create: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java`
+
+- [ ] **Step 1: 创建 Controller**
+
+```java
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+/**
+ * 订单操作日志
+ */
+@RestController
+@RequestMapping("/system/orderLog")
+public class PosOrderLogController extends BaseController
+{
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(PosOrderLog orderLog)
+    {
+        startPage();
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id)
+    {
+        return success(orderLogService.selectOrderLogById(id));
+    }
+
+    @Log(title = "订单操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:orderLog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, PosOrderLog orderLog)
+    {
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        ExcelUtil<PosOrderLog> util = new ExcelUtil<PosOrderLog>(PosOrderLog.class);
+        util.exportExcel(response, list, "订单操作日志");
+    }
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java
+git commit -m "feat(order-log): add admin controller with list/detail/export"
+```
+
+---
+
+### Task 6: 商家端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java`
+
+- [ ] **Step 1: 注入 OrderLogHelper**
+
+在 `PosOrderShOprateController` 的成员变量区域添加:
+
+```java
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+添加 import:
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+```
+
+- [ ] **Step 2: acceptOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已接单");
+```
+
+- [ ] **Step 3: dispatchOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后、`chuCan(order, update);` 之前添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已出餐");
+```
+
+- [ ] **Step 4: completeOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已完成订单");
+```
+
+- [ ] **Step 5: cancelOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "取消订单");
+```
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java
+git commit -m "feat(order-log): add order log to merchant operations"
+```
+
+---
+
+### Task 7: 骑手端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java`
+
+- [ ] **Step 1: 注入 OrderLogHelper**
+
+在成员变量区域添加:
+
+```java
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+添加 import:
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+```
+
+- [ ] **Step 2: acceptOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "接单成功");
+```
+
+- [ ] **Step 3: pickupOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已取餐");
+```
+
+- [ ] **Step 4: deliverOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已送达");
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java
+git commit -m "feat(order-log): add order log to rider operations"
+```
+
+---
+
+### Task 8: 用户端和平台端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java`
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java`
+
+- [ ] **Step 1: UserOrderController 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 2: UserOrderController.createOrderChild 添加日志**
+
+在订单创建成功后(`posOrderService.saveOrUpdate()` 之后)添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(posOrder.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "创建订单");
+```
+
+注意:`userId` 在 createOrderChild 方法中从 token 获取,变量名需确认是 `userId` 还是从 `jwtUtil.getusid(token)` 获取。根据实际方法中已有的变量名调整。
+
+- [ ] **Step 3: UserOrderController.confirmPickup 添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(new JwtUtil().getusid(token)), uName, "用户" + uName + "确认取餐");
+```
+
+- [ ] **Step 4: UserOrderController.cancelOrder 添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(new JwtUtil().getusid(token)), uName, "用户" + uName + "取消订单");
+```
+
+- [ ] **Step 5: PosOrderController.setorderuzt 添加日志**
+
+在 `PosOrderController` 中注入 OrderLogHelper。
+
+在 `setorderuzt` 方法中,通用的 `posOrderService.saveOrUpdate(posOrder)` 成功分支添加:
+
+```java
+// 获取平台管理员信息
+SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+String adminName = adminUser != null ? adminUser.getNickName() : "";
+PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+String ddId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+orderLogHelper.log(ddId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+    "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+```
+
+注意:`PosOrderController.setorderuzt` 是平台管理端调用的接口(使用 Spring Security 认证),通过 `SecurityUtils.getLoginUser()` 获取当前管理员信息。需添加 import `com.ruoyi.common.utils.SecurityUtils` 和 `com.ruoyi.common.core.domain.entity.SysUser`。
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java
+git commit -m "feat(order-log): add order log to user and platform operations"
+```
+
+---
+
+### Task 9: 定时任务和支付回调插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java`
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java`
+
+- [ ] **Step 1: TestTask 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 2: TestTask.testTiming 添加日志**
+
+在 `posOrderService.saveOrUpdate(posOrder);` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动取消超时未接单订单");
+```
+
+使用 `logSync` 而非 `log`,因为定时任务已在异步上下文中。
+
+- [ ] **Step 3: TestTask.zidwancheng 添加日志**
+
+在 `posOrderService.saveOrUpdate(pos);` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动完成超时订单");
+```
+
+- [ ] **Step 4: TestTask.refundProcessing 添加日志**
+
+在退款成功后(`posOrderService.saveOrUpdate(posOrder);` 之后)添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(list.get(i).getDdId()), 0, null, "系统", "系统处理退款完成");
+```
+
+- [ ] **Step 5: PayController 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 6: PayController 支付回调添加日志**
+
+在支付成功设置 `payStatus=1` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(order.getDdId()), 0, null, "系统", "系统收到支付成功回调");
+```
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java
+git commit -m "feat(order-log): add order log to scheduled tasks and payment callback"
+```
+
+---
+
+### Task 10: 前端 - API 文件
+
+**Files:**
+- Create: `E:\QtwCode\foodie\foodie-admin-vue\src\api\system\orderLog.js`
+
+- [ ] **Step 1: 创建 API 文件**
+
+```javascript
+import request from '@/utils/request'
+
+// 查询订单操作日志列表
+export function listOrderLog(query) {
+  return request({
+    url: '/system/orderLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询订单操作日志详细
+export function getOrderLog(id) {
+  return request({
+    url: '/system/orderLog/' + id,
+    method: 'get'
+  })
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add "E:/QtwCode/foodie/foodie-admin-vue/src/api/system/orderLog.js"
+git commit -m "feat(order-log): add frontend API file"
+```
+
+---
+
+### Task 11: 前端 - 日志查询页面
+
+**Files:**
+- Create: `E:\QtwCode\foodie\foodie-admin-vue\src\views\system\order\log.vue`
+
+这是核心前端页面,参照截图中的 UI 设计。页面使用 Element UI 组件,包含搜索栏、数据表格、分页。
+
+- [ ] **Step 1: 创建日志页面**
+
+创建 `log.vue`,包含以下核心结构:
+
+**搜索栏**:订单号输入框、操作人ID输入框、操作人名称输入框、搜索按钮、重置按钮、导出按钮
+
+**数据表格列**:主键ID、订单号、操作人类型(格式化为中文)、操作人ID、操作人名称、操作内容、时间、操作(查看链接)
+
+**操作人类型格式化方法**:
+
+```javascript
+operatorTypeFormat(type) {
+  const map = { 0: '系统', 1: '平台', 2: '商家', 3: '骑手', 4: '用户' }
+  return map[type] || type
+}
+```
+
+**核心 data 属性**:
+
+```javascript
+data() {
+  return {
+    orderLogList: [],
+    total: 0,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      ddId: undefined,
+      operatorId: undefined,
+      operatorName: undefined
+    }
+  }
+}
+```
+
+**核心方法**:
+
+```javascript
+methods: {
+  getList() {
+    listOrderLog(this.queryParams).then(response => {
+      this.orderLogList = response.rows
+      this.total = response.total
+    })
+  },
+  handleQuery() {
+    this.queryParams.pageNum = 1
+    this.getList()
+  },
+  resetQuery() {
+    this.queryParams = { pageNum: 1, pageSize: 10, ddId: undefined, operatorId: undefined, operatorName: undefined }
+    this.getList()
+  },
+  handleExport() {
+    this.download('system/orderLog/export', { ...this.queryParams }, '订单操作日志.xlsx')
+  }
+}
+```
+
+查看按钮点击后弹窗显示该条日志详情(使用 el-dialog)。
+
+由于前端文件使用 CRLF 换行,编写时使用 Python 脚本方式创建文件。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add "E:/QtwCode/foodie/foodie-admin-vue/src/views/system/order/log.vue"
+git commit -m "feat(order-log): add order log query page for admin"
+```
+
+---
+
+### Task 12: 前端 - 路由和菜单配置
+
+**Files:**
+- Modify: `E:\QtwCode\foodie\foodie-admin-vue\src\router\index.js`
+
+订单日志页面需要添加到路由配置中。在系统管理或订单管理路由组下添加。
+
+由于本项目使用动态路由(菜单由后台数据库配置),路由代码不需要手动添加。需要在数据库的 `sys_menu` 表中插入菜单记录。
+
+- [ ] **Step 1: 添加 SQL 到 updatesql/sql.md**
+
+在 `updatesql/sql.md` 末尾追加菜单插入 SQL:
+
+```sql
+-- 2026-05-19 新增订单操作日志菜单
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+SELECT '订单日志', menu_id, 6, 'orderLog', 'system/order/log', 'C', '0', '0', 'system:orderLog:list', 'log', 'admin', NOW(), '订单操作日志菜单'
+FROM sys_menu WHERE menu_name = '订单管理' AND parent_id = 0 LIMIT 1;
+
+-- 日志查询按钮权限
+SET @logMenuId = (SELECT menu_id FROM sys_menu WHERE perms = 'system:orderLog:list' LIMIT 1);
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志查询', @logMenuId, 1, '#', '', 'F', '0', '0', 'system:orderLog:query', '#', 'admin', NOW());
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志导出', @logMenuId, 2, '#', '', 'F', '0', '0', 'system:orderLog:export', '#', 'admin', NOW());
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add updatesql/sql.md
+git commit -m "feat(order-log): add menu SQL for order log page"
+```
+
+---
+
+### Task 13: 执行数据库建表 SQL
+
+- [ ] **Step 1: 在数据库执行建表语句**
+
+从 `updatesql/sql.md` 中复制 `CREATE TABLE pos_order_log` 和菜单插入 SQL,在 MySQL 中执行。
+
+- [ ] **Step 2: 验证**
+
+- 启动后端服务
+- 使用商家端操作一个订单(接单→出餐→完成)
+- 打开平台管理端,进入"订单管理 > 订单日志"页面
+- 确认能看到操作日志记录
+- 测试搜索和导出功能
+
+---
+
+## Self-Review Checklist
+
+### 1. Spec Coverage
+| Spec 要求 | 对应 Task |
+|-----------|----------|
+| pos_order_log 表 | Task 1-3 (Entity/Mapper/Service), SQL in updatesql/sql.md |
+| 5 种操作人类型 | Task 1 (Entity), Task 4 (Helper), Task 6-9 (各调用方) |
+| 日志写入时机(商家4种) | Task 6 |
+| 日志写入时机(骑手3种) | Task 7 |
+| 日志写入时机(用户3种) | Task 8 |
+| 日志写入时机(平台1种) | Task 8 |
+| 日志写入时机(系统3种) | Task 9 |
+| 平台管理端查询页面 | Task 10-12 |
+| 搜索条件(订单号/操作人ID/名称) | Task 2 (XML), Task 11 (前端) |
+| 导出功能 | Task 5 (Controller), Task 11 (前端) |
+| 菜单配置 | Task 12 |
+
+### 2. Placeholder Scan
+- 无 TBD/TODO/实现稍后
+- 所有代码步骤都有完整代码
+
+### 3. Type Consistency
+- PosOrderLog 字段在 Entity/Mapper XML/Service/Controller/Helper 中保持一致
+- `ddId` 类型 String, `operatorType` 类型 Integer, `operatorId` 类型 Long
+- `logTime` 映射到数据库 `create_time` 列

+ 49 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java

@@ -0,0 +1,49 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+@RestController
+@RequestMapping("/system/orderLog")
+public class PosOrderLogController extends BaseController
+{
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(PosOrderLog orderLog)
+    {
+        startPage();
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id)
+    {
+        return success(orderLogService.selectOrderLogById(id));
+    }
+
+    @Log(title = "订单操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:orderLog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, PosOrderLog orderLog)
+    {
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        ExcelUtil<PosOrderLog> util = new ExcelUtil<PosOrderLog>(PosOrderLog.class);
+        util.exportExcel(response, list, "订单操作日志");
+    }
+}

+ 49 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java

@@ -0,0 +1,49 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+public class PosOrderLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "主键ID", cellType = ColumnType.NUMERIC)
+    private Long id;
+
+    @Excel(name = "订单号")
+    private String ddId;
+
+    @Excel(name = "操作人类型", readConverterExp = "0=系统,1=平台管理员,2=商家,3=骑手,4=用户")
+    private Integer operatorType;
+
+    @Excel(name = "操作人ID", cellType = ColumnType.NUMERIC)
+    private Long operatorId;
+
+    @Excel(name = "操作人名称")
+    private String operatorName;
+
+    @Excel(name = "操作内容")
+    private String content;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date logTime;
+
+    public Long getId() { return id; }
+    public void setId(Long id) { this.id = id; }
+    public String getDdId() { return ddId; }
+    public void setDdId(String ddId) { this.ddId = ddId; }
+    public Integer getOperatorType() { return operatorType; }
+    public void setOperatorType(Integer operatorType) { this.operatorType = operatorType; }
+    public Long getOperatorId() { return operatorId; }
+    public void setOperatorId(Long operatorId) { this.operatorId = operatorId; }
+    public String getOperatorName() { return operatorName; }
+    public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Date getLogTime() { return logTime; }
+    public void setLogTime(Date logTime) { this.logTime = logTime; }
+}

+ 13 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java

@@ -0,0 +1,13 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+public interface PosOrderLogMapper
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}

+ 13 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java

@@ -0,0 +1,13 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+public interface IPosOrderLogService
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}

+ 33 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java

@@ -0,0 +1,33 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.mapper.PosOrderLogMapper;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+@Service
+public class PosOrderLogServiceImpl implements IPosOrderLogService
+{
+    @Autowired
+    private PosOrderLogMapper orderLogMapper;
+
+    @Override
+    public void insertOrderLog(PosOrderLog orderLog)
+    {
+        orderLogMapper.insertOrderLog(orderLog);
+    }
+
+    @Override
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog)
+    {
+        return orderLogMapper.selectOrderLogList(orderLog);
+    }
+
+    @Override
+    public PosOrderLog selectOrderLogById(Long id)
+    {
+        return orderLogMapper.selectOrderLogById(id);
+    }
+}

+ 45 - 0
ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java

@@ -0,0 +1,45 @@
+package com.ruoyi.system.utils;
+
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class OrderLogHelper {
+
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    /**
+     * 异步写入订单日志
+     */
+    public void log(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog orderLog = new PosOrderLog();
+        orderLog.setDdId(ddId);
+        orderLog.setOperatorType(operatorType);
+        orderLog.setOperatorId(operatorId);
+        orderLog.setOperatorName(operatorName);
+        orderLog.setContent(content);
+        ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+        executor.schedule(() -> SpringUtils.getBean(IPosOrderLogService.class).insertOrderLog(orderLog),
+                10, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 同步写入订单日志(用于定时任务等异步上下文)
+     */
+    public void logSync(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog orderLog = new PosOrderLog();
+        orderLog.setDdId(ddId);
+        orderLog.setOperatorType(operatorType);
+        orderLog.setOperatorId(operatorId);
+        orderLog.setOperatorName(operatorName);
+        orderLog.setContent(content);
+        orderLogService.insertOrderLog(orderLog);
+    }
+}

+ 57 - 0
ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml

@@ -0,0 +1,57 @@
+<?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.ruoyi.system.mapper.PosOrderLogMapper">
+
+    <resultMap type="PosOrderLog" id="PosOrderLogResult">
+        <id     property="id"            column="id"             />
+        <result property="ddId"          column="dd_id"          />
+        <result property="operatorType"  column="operator_type"  />
+        <result property="operatorId"    column="operator_id"    />
+        <result property="operatorName"  column="operator_name"  />
+        <result property="content"       column="content"        />
+        <result property="logTime"       column="create_time"    />
+    </resultMap>
+
+    <sql id="selectOrderLogVo">
+        select id, dd_id, operator_type, operator_id, operator_name, content, create_time
+        from pos_order_log
+    </sql>
+
+    <insert id="insertOrderLog" parameterType="PosOrderLog">
+        insert into pos_order_log(dd_id, operator_type, operator_id, operator_name, content, create_time)
+        values (#{ddId}, #{operatorType}, #{operatorId}, #{operatorName}, #{content}, sysdate())
+    </insert>
+
+    <select id="selectOrderLogList" parameterType="PosOrderLog" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        <where>
+            <if test="ddId != null and ddId != ''">
+                AND dd_id like concat('%', #{ddId}, '%')
+            </if>
+            <if test="operatorId != null">
+                AND operator_id = #{operatorId}
+            </if>
+            <if test="operatorName != null and operatorName != ''">
+                AND operator_name like concat('%', #{operatorName}, '%')
+            </if>
+            <if test="operatorType != null">
+                AND operator_type = #{operatorType}
+            </if>
+            <if test="params.beginTime != null and params.beginTime != ''">
+                AND create_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params.endTime != null and params.endTime != ''">
+                AND create_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectOrderLogById" parameterType="Long" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        where id = #{id}
+    </select>
+
+</mapper>

+ 408 - 0
specs/006-orderstate/test-guide.md

@@ -0,0 +1,408 @@
+# 订单状态流转 — 测试指南
+
+> 本文档面向测试人员和项目经理,描述订单系统在状态重构后的全部业务行为。
+> 如有疑问请对照 `specs/006-orderstate/spec.md` 原始需求文档。
+
+---
+
+## 一、背景
+
+系统支持三种订单类型,每种类型有不同的参与角色和完成方式:
+
+| 订单类型 | 谁送餐 | 谁付钱 | 谁完成订单 |
+|---------|--------|--------|-----------|
+| **外送** (type=0) | 骑手配送 | 在线支付 / 到付 | 骑手点"已送达" |
+| **自取** (type=1) | 用户到店取 | 现金(线下收款) | 商家点"完成" 或 用户确认取餐 |
+| **堂食** (type=2) | 商家端上 | 现金(线下收款) | 商家点"完成" |
+
+---
+
+## 二、状态字段说明
+
+订单有 4 个独立的状态字段,各自跟踪不同的关注点:
+
+### 2.1 订单状态 (state) — 订单的生命周期
+
+| 值 | 状态名 | 含义 |
+|----|--------|------|
+| 0 | 待处理 | 订单刚创建,等待商家接单 |
+| 1 | 已接单 | 商家已接单,正在备餐 |
+| 2 | 已出餐 | 商家备餐完成 |
+| 3 | 已完成 | 订单结束(外送=骑手送达,自取=用户取餐,堂食=备餐完成) |
+| 4 | 已取消 | 订单被取消 |
+
+### 2.2 支付状态 (payStatus)
+
+| 值 | 状态名 | 含义 |
+|----|--------|------|
+| 0 | 未支付 | 等待付款 |
+| 1 | 已支付 | 付款完成 |
+| 2 | 已退款 | 退款完成 |
+
+**关键规则:**
+- 外送到付订单:下单时直接标记为"已支付"(1),骑手送货时收现金
+- 自取/堂食现金订单:下单时为"未支付"(0),商家收款+点完成时才改为"已支付"(1)
+- 在线支付订单:下单时为"未支付"(0),支付成功后改为"已支付"(1)
+
+### 2.3 配送状态 (deliveryStatus) — 仅外送订单
+
+| 值 | 状态名 | 含义 |
+|----|--------|------|
+| null | 不适用 | 自取/堂食订单没有配送状态 |
+| 0 | 待接单 | 等待骑手接单 |
+| 1 | 骑手已接单 | 骑手已接单,还没取餐 |
+| 2 | 配送中 | 骑手已取餐,正在送 |
+| 3 | 已送达 | 骑手已送到 |
+
+### 2.4 售后状态 (afterSaleStatus)
+
+| 值 | 状态名 | 含义 |
+|----|--------|------|
+| 0 | 无售后 | 正常订单 |
+| 1 | 申请中 | 用户申请退款,等商家处理 |
+| 2 | 退款中 | 商家同意,退款正在处理 |
+| 3 | 已退款 | 退款完成 |
+| 4 | 退款拒绝 | 商家拒绝退款 |
+| 5 | 客服介入 | 客服正在处理 |
+| 6 | 售后完成 | 售后流程结束 |
+
+> **本次不实现售后/退款流程**。afterSaleStatus 字段已预留,但退款相关操作(申请退款、同意退款、拒绝退款、客服介入等)暂未开发。
+
+---
+
+## 三、状态流转场景
+
+### 3.1 外送 — 在线支付
+
+```
+用户下单 → 用户付款 → 商家接单 → 商家出餐 → 骑手接单 → 骑手取餐 → 骑手送达
+  state:    0         0          1          2         2         2         3
+  pay:      0    →    1          1          1         1         1         1
+  delivery:  null     null       null       0         1         2         3
+```
+
+| 步骤 | 操作者 | 操作 | state | payStatus | deliveryStatus |
+|------|--------|------|-------|-----------|----------------|
+| 1 | 系统 | 创建订单 | 0 | 0 | null |
+| 2 | 用户 | 在线支付 | 0 | **0→1** | null |
+| 3 | 商家 | 接单 | **0→1** | 1 | null |
+| 4 | 商家 | 出餐 | **1→2** | 1 | **0** |
+| 5 | 骑手 | 接单 | 2 | 1 | **0→1** |
+| 6 | 骑手 | 取餐出发 | 2 | 1 | **1→2** |
+| 7 | 骑手 | 送达 | **2→3** | 1 | **2→3** |
+
+### 3.2 外送 — 到付
+
+```
+与在线支付基本相同,唯一区别:下单时 payStatus 直接设为 1(已支付)
+```
+
+| 步骤 | 操作者 | 操作 | state | payStatus | deliveryStatus |
+|------|--------|------|-------|-----------|----------------|
+| 1 | 系统 | 创建订单 | 0 | **1**(直接已支付) | null |
+| 2 | 商家 | 接单 | **0→1** | 1 | null |
+| 3 | 商家 | 出餐 | **1→2** | 1 | **0** |
+| 4 | 骑手 | 接单→取餐→送达 | 2→2→3 | 1 | 0→1→2→3 |
+
+### 3.3 自取 — 现金
+
+```
+用户下单 → 商家接单 → 商家出餐 → 商家收款+完成(或用户确认取餐)
+  state:    0         1          2         3
+  pay:      0         0          0         1(商家收款时设为1)
+  delivery: 无        无         无        无
+```
+
+| 步骤 | 操作者 | 操作 | state | payStatus |
+|------|--------|------|-------|-----------|
+| 1 | 系统 | 创建订单 | 0 | 0 |
+| 2 | 商家 | 接单 | **0→1** | 0 |
+| 3 | 商家 | 出餐 | **1→2** | 0 |
+| 4a | 商家 | 完成(收款+完成) | **2→3** | **0→1** |
+| 4b | 用户 | 确认取餐(仅已支付时可用) | **2→3** | 1(不变) |
+
+> **重要规则**:用户"确认取餐"仅对已支付(payStatus=1)的自取订单有效。现金订单(payStatus=0)的用户无法自己确认取餐,只能等商家点"完成"。
+
+### 3.4 堂食 — 现金
+
+与自取流程完全一致,只是 type=2。
+
+---
+
+## 四、取消订单场景
+
+### 4.1 用户取消
+
+| 条件 | state | 结果 |
+|------|-------|------|
+| 商家未接单 | state=0 | state→4(已取消) |
+| 商家已接单 | state=1 或更高 | **不允许取消**(需走退款流程) |
+
+**测试步骤:**
+1. 用户下单(state=0)
+2. 用户点击"取消订单"
+3. 预期:订单变为"已取消"(state=4)
+
+### 4.2 商家取消
+
+| 条件 | state | 结果 |
+|------|-------|------|
+| 待处理或已接单 | state=0 或 1 | state→4(已取消) |
+| 已出餐或之后 | state=2 或更高 | **不允许取消** |
+
+### 4.3 系统自动取消
+
+| 场景 | 条件 | 结果 |
+|------|------|------|
+| 超时未接单 | 商家在规定时间内未接单 | state→4 |
+| 超时未完成 | 商家出餐后长时间未完成 | state→3(自动完成) |
+
+---
+
+## 五、各端订单列表测试
+
+### 5.1 用户端
+
+接口:`GET /system/userOrder/orderList`
+
+| Tab 名称 | tab 参数 | 应出现的订单 | 不应出现的订单 |
+|----------|---------|-------------|---------------|
+| 待付款 | `unpaid` | 外送未支付订单(payStatus=0, type=0) | 自取/堂食的未支付订单、已支付的外送订单 |
+| 进行中 | `active` | 所有正在进行的订单 | 已完成、已取消、有售后的订单 |
+| 已完成 | `completed` | state=3 的订单 | 其他状态的订单 |
+| 已取消 | `cancelled` | state=4 且无售后的订单 | 有售后的已取消订单 |
+| 退款/售后 | `refund` | afterSaleStatus>0 的订单 | 正常订单 |
+
+**重点测试用例:**
+
+| # | 场景 | 预期结果 |
+|---|------|---------|
+| 1 | 自取订单(type=1)刚创建,payStatus=0 | 应出现在"进行中"Tab,**不应**出现在"待付款"Tab |
+| 2 | 堂食订单(type=2)刚创建,payStatus=0 | 同上,出现在"进行中",不在"待付款" |
+| 3 | 外送订单(type=0)未支付 | 应出现在"待付款"Tab |
+| 4 | 外送到付订单刚创建,payStatus=1 | 应出现在"进行中"Tab |
+| 5 | 传入 type=1 筛选 | 只显示自取订单 |
+| 6 | 不传 type | 显示所有类型的订单 |
+
+### 5.2 商家端
+
+接口:`GET /system/orderShOprate/orderList`
+
+| Tab 名称 | tab 参数 | 应出现的订单 |
+|----------|---------|-------------|
+| 待受理 | `pending` | state=0 且(已支付 OR 自取/堂食) |
+| 待出餐 | `preparing` | state=1 |
+| 已出餐 | `ready` | state=2 且无售后 |
+| 已完成 | `completed` | state=3 且无售后 |
+| 已取消 | `cancelled` | state=4 且无售后 |
+| 退款/售后 | `refund` | 有售后记录的订单 |
+
+**重点测试用例:**
+
+| # | 场景 | 预期结果 |
+|---|------|---------|
+| 1 | 外送未支付订单(state=0, payStatus=0) | **不应**出现在"待受理"(用户还没付款) |
+| 2 | 自取订单刚创建(state=0, payStatus=0) | **应**出现在"待受理"(现金线下收) |
+| 3 | 外送到付订单(state=0, payStatus=1) | 应出现在"待受理" |
+| 4 | 外送在线支付成功(state=0, payStatus=1) | 应出现在"待受理" |
+| 5 | 商家接单后 | 订单从"待受理"移到"待出餐" |
+| 6 | 商家出餐后 | 订单从"待出餐"移到"已出餐" |
+
+### 5.3 骑手端
+
+接口:`GET /system/orderQsOprate/orderList`
+
+| Tab 名称 | tab 参数 | 应出现的订单 |
+|----------|---------|-------------|
+| 新任务 | `newTask` | 外送订单(type=0)、已出餐(state=2)、等待骑手(deliveryStatus=0)、无售后 |
+| 待取货 | `toPickup` | 当前骑手已接单(deliveryStatus=1)、无售后 |
+| 配送中 | `delivering` | 当前骑手正在配送(deliveryStatus=2)、无售后 |
+| 已完成 | `completed` | 当前骑手已完成的订单 |
+| 已取消 | `cancelled` | 当前骑手关联的已取消订单 |
+| 退款/售后 | `refund` | 当前骑手关联的有售后的订单 |
+
+**重点测试用例:**
+
+| # | 场景 | 预期结果 |
+|---|------|---------|
+| 1 | 商家还没出餐(state=1)的外送订单 | **不应**出现在"新任务"Tab |
+| 2 | 自取订单(type=1) | **不应**出现在骑手端任何Tab |
+| 3 | 新任务列表排序 | 按距离从近到远排列(需要传入经纬度参数) |
+| 4 | 其他骑手已接单的订单 | **不应**出现在当前骑手的"新任务"中 |
+
+---
+
+## 六、用户端操作测试
+
+### 6.1 确认取餐
+
+接口:`POST /system/userOrder/confirmPickup`
+
+| # | 前置条件 | 操作 | 预期结果 |
+|---|---------|------|---------|
+| 1 | 自取订单、已出餐(state=2)、已支付(payStatus=1) | 点击确认取餐 | 成功,state→3 |
+| 2 | 自取订单、已出餐(state=2)、未支付(payStatus=0) | 点击确认取餐 | 失败,提示"订单未支付" |
+| 3 | 外送订单(type=0) | 点击确认取餐 | 失败,提示"仅自取订单可确认取餐" |
+| 4 | 堂食订单(type=2) | 点击确认取餐 | 失败,提示"仅自取订单可确认取餐" |
+| 5 | 自取订单、待处理(state=0) | 点击确认取餐 | 失败,提示"当前订单状态不可确认取餐" |
+| 6 | 自取订单、已接单(state=1) | 点击确认取餐 | 失败,提示"当前订单状态不可确认取餐" |
+| 7 | 自取订单、已完成(state=3) | 点击确认取餐 | 失败,提示"当前订单状态不可确认取餐" |
+| 8 | 别人的订单 | 点击确认取餐 | 失败,提示"订单不存在" |
+
+### 6.2 取消订单
+
+接口:`POST /system/userOrder/cancelOrder`
+
+| # | 前置条件 | 操作 | 预期结果 |
+|---|---------|------|---------|
+| 1 | 任何类型、待处理(state=0) | 点击取消订单 | 成功,state→4 |
+| 2 | 任何类型、已接单(state=1) | 点击取消订单 | 失败,提示"仅待处理状态的订单可取消" |
+| 3 | 任何类型、已出餐(state=2) | 点击取消订单 | 失败,同上 |
+| 4 | 别人的订单 | 点击取消订单 | 失败,提示"订单不存在" |
+| 5 | 自取订单(state=0) | 点击取消 | 成功 |
+| 6 | 堂食订单(state=0) | 点击取消 | 成功 |
+| 7 | 外送订单(state=0) | 点击取消 | 成功 |
+
+---
+
+## 七、商家端操作测试
+
+| 操作 | 接口 | 前置条件 | 预期结果 |
+|------|------|---------|---------|
+| 接单 | `POST /system/orderShOprate/acceptOrder` | state=0 | state→1 |
+| 出餐 | `GET /system/orderShOprate/dispatchOrder` | state=1 | state→2;外送额外设 deliveryStatus=0 |
+| 完成(自取/堂食) | `POST /system/orderShOprate/completeOrder` | type=1或2,state=2 | state→3,payStatus→1 |
+| 取消 | `POST /system/orderShOprate/cancelOrder` | state=0或1 | state→4 |
+
+**重点测试用例:**
+
+| # | 场景 | 预期结果 |
+|---|------|---------|
+| 1 | 商家对外送订单出餐 | state→2,**同时 deliveryStatus=0**(等待骑手) |
+| 2 | 商家对自取订单出餐 | state→2,deliveryStatus 保持 null |
+| 3 | 商家对自取订单点"完成" | state→3,payStatus→1(收款+完成一步到位) |
+| 4 | 商家对外送订单点"完成" | **不允许**(外送订单由骑手完成) |
+| 5 | 商家对已出餐(state=2)的订单接单 | **不允许**(只能对 state=0 的订单接单) |
+
+---
+
+## 八、骑手端操作测试
+
+| 操作 | 接口 | 前置条件 | 预期结果 |
+|------|------|---------|---------|
+| 接单 | `POST /system/orderQsOprate/acceptOrder` | type=0, deliveryStatus=0, afterSaleStatus=0 | deliveryStatus→1,绑定骑手ID |
+| 取餐出发 | `POST /system/orderQsOprate/pickupOrder` | deliveryStatus=1, afterSaleStatus=0 | deliveryStatus→2 |
+| 送达 | `POST /system/orderQsOprate/deliverOrder` | deliveryStatus=2 | deliveryStatus→3,**state→3** |
+
+**重点测试用例:**
+
+| # | 场景 | 预期结果 |
+|---|------|---------|
+| 1 | 骑手对自取订单(type=1)接单 | **不允许**(只有外送订单需要骑手) |
+| 2 | 骑手对已有人接的订单接单 | **不允许**(防止重复接单) |
+| 3 | 骑手送达后 | state=3, deliveryStatus=3,订单在骑手端移到"已完成"Tab |
+| 4 | 骑手接单时有活跃退款(afterSaleStatus>0) | 允许操作但需提示 |
+
+---
+
+## 九、用户看到的文字
+
+用户端页面不直接显示 state 数字,而是根据状态组合显示中文文字。
+
+### 9.1 外送订单 (type=0) 用户看到的
+
+| 用户看到 | 对应的订单状态 |
+|---------|--------------|
+| 待支付 | 还没付款(payStatus=0) |
+| 商家已接单 | 已付款,等商家处理(state=0或1) |
+| 配送中 | 商家出餐,骑手在送(state=2, deliveryStatus=0/1/2) |
+| 已送达 | 骑手送到(state=2, deliveryStatus=3) |
+| 已完成 | 订单结束(state=3) |
+| 已取消 | 订单取消(state=4) |
+| 退款中/已退款 | 有退款处理(afterSaleStatus>0) |
+
+### 9.2 自取订单 (type=1) 用户看到的
+
+| 用户看到 | 对应的订单状态 |
+|---------|--------------|
+| 待支付 | 还没付款(payStatus=0) |
+| 商家已接单 | 等商家处理(state=0或1) |
+| 待取餐 | 商家出餐了,等用户去取(state=2) |
+| 已完成 | 订单结束(state=3) |
+| 已取消 | 订单取消(state=4) |
+
+### 9.3 堂食订单 (type=2) 用户看到的
+
+| 用户看到 | 对应的订单状态 |
+|---------|--------------|
+| 待支付 | 还没付款(payStatus=0) |
+| 商家已接单 | 等商家处理(state=0或1) |
+| 备餐完成 | 商家出餐了(state=2) |
+| 已完成 | 订单结束(state=3) |
+| 已取消 | 订单取消(state=4) |
+
+### 9.4 商家端订单卡片显示
+
+**外送订单:**
+
+| 条件 | 显示 | 颜色建议 |
+|------|------|---------|
+| state=0, payStatus=0 | 待支付 | 灰色 |
+| state=0, payStatus=1 | 待接单 | 橙色 |
+| state=1 | 制作中 | 蓝色 |
+| state=2, deliveryStatus=0/1 | 待骑手取餐 | 蓝色 |
+| state=2, deliveryStatus=2 | 配送中 | 蓝色 |
+| state=2, deliveryStatus=3 | 已送达 | 绿色 |
+| state=3 | 已完成 | 灰色 |
+| state=4 | 已取消 | 红色 |
+
+**自取/堂食订单:**
+
+| 条件 | 显示 | 颜色建议 |
+|------|------|---------|
+| state=0 | 待接单 | 橙色 |
+| state=1 | 制作中 | 蓝色 |
+| state=2 | 备餐完成 | 橙色 |
+| state=3 | 已完成 | 灰色 |
+| state=4 | 已取消 | 红色 |
+
+**售后状态覆盖规则**:当 afterSaleStatus>0 时,不管上面的规则,统一显示售后状态文字(退款申请中/退款中/已退款/退款已拒绝),优先级最高。
+
+---
+
+## 十、边界场景和注意事项
+
+| # | 场景 | 说明 |
+|---|------|------|
+| 1 | 已取消的订单应出现在哪个Tab | 三个端都有独立的"已取消"Tab 承接 |
+| 2 | 自取/堂食订单的 deliveryStatus | 始终为 null,不应出现在骑手端 |
+| 3 | 外送订单商家出餐后 | deliveryStatus 应自动设为 0(等待骑手),不是 null |
+| 4 | 现金自取订单的支付 | 下单时 payStatus=0,只有商家点"完成"时才变为 1 |
+| 5 | 到付外送订单的支付 | 下单时 payStatus=1(直接确认支付方式) |
+| 6 | 用户确认取餐前提 | 必须是自取(type=1) + 已支付(payStatus=1) + 已出餐(state=2) |
+| 7 | 退款流程 | **本次不实现**,afterSaleStatus 字段已预留但无操作入口 |
+| 8 | 旧接口 | 旧的订单列表接口保留不删,但不再维护,前端应切换到新接口 |
+| 9 | 平台管理端 | 订单列表页需从单状态下拉改为四个独立下拉筛选(state/payStatus/deliveryStatus/afterSaleStatus) |
+| 10 | 定时任务 | 商家出餐后超时未完成的订单,系统自动设为已完成(state=3) |
+
+---
+
+## 十一、实现状态总览
+
+| 功能 | 状态 | 说明 |
+|------|------|------|
+| 订单创建(四字段初始化) | ✅ 已实现 | UserOrderController.createOrder |
+| 用户端订单列表 | ✅ 已实现 | UserOrderController.orderList |
+| 商家端订单列表 | ✅ 已实现 | PosOrderShOprateController.orderList |
+| 骑手端订单列表 | ✅ 已实现 | PosOrderQsOprateController.orderList |
+| 用户确认取餐 | ✅ 已实现 | UserOrderController.confirmPickup |
+| 用户取消订单(新) | ✅ 已实现 | UserOrderController.cancelOrder |
+| 用户取消订单(旧) | ⚠️ 已废弃 | OrderAppealController.userCancelpOrder 已注释 |
+| 商家接单/出餐/完成/取消 | ✅ 已实现 | PosOrderShOprateController |
+| 骑手接单/取餐/送达 | ✅ 已实现 | PosOrderQsOprateController |
+| 支付回调更新 payStatus | ✅ 已实现 | PayController |
+| 定时任务自动完成 | 待确认 | TestTask.java |
+| 用户申请退款 | ❌ 未实现 | 留待接入在线支付后开发 |
+| 商家同意/拒绝退款 | ❌ 未实现 | 留待接入在线支付后开发 |
+| 客服介入 | ❌ 未实现 | 留待接入在线支付后开发 |
+| 平台管理端四字段筛选 | ❌ 未实现 | foodie-admin-vue 待改造 |

+ 899 - 0
specs/007-orderlog/plan.md

@@ -0,0 +1,899 @@
+# 订单操作日志 Implementation Plan (COMPLETED)
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 新建 pos_order_log 表,在所有订单状态变更处自动记录日志,并在平台管理端提供查询/导出页面。
+
+**Architecture:** 跟随 SysOperLog 的模式——Entity + Mapper(XML) + Service + Controller。日志写入封装为一个工具类 `OrderLogHelper`,供各 Controller 和定时任务调用。前端新增独立的日志查询页面挂到"订单管理"菜单下。
+
+**Tech Stack:** Java (Spring Boot, MyBatis XML), Vue.js (Element UI), MySQL
+
+---
+
+## File Structure
+
+| 操作 | 文件 | 职责 |
+|------|------|------|
+| Create | `ruoyi-system/.../domain/PosOrderLog.java` | 实体类 |
+| Create | `ruoyi-system/.../mapper/PosOrderLogMapper.java` | Mapper 接口 |
+| Create | `ruoyi-system/.../mapper/PosOrderLogMapper.xml` (resources/mapper/system/) | XML mapper |
+| Create | `ruoyi-system/.../service/IPosOrderLogService.java` | Service 接口 |
+| Create | `ruoyi-system/.../service/impl/PosOrderLogServiceImpl.java` | Service 实现 |
+| Create | `ruoyi-admin/.../controller/system/PosOrderLogController.java` | 后台管理接口 |
+| Create | `ruoyi-system/.../utils/OrderLogHelper.java` | 日志写入工具类 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderShOprateController.java` | 商家操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderQsOprateController.java` | 骑手操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/UserOrderController.java` | 用户操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/PosOrderController.java` | 平台操作插入日志 |
+| Modify | `ruoyi-admin/.../app/order/TestTask.java` | 系统定时任务插入日志 |
+| Modify | `ruoyi-admin/.../app/pay/PayController.java` | 支付回调插入日志 |
+| Create | `foodie-admin-vue/src/api/system/orderLog.js` | 前端 API |
+| Create | `foodie-admin-vue/src/views/system/order/log.vue` | 前端页面 |
+
+---
+
+### Task 1: 创建 PosOrderLog 实体类
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java`
+
+- [ ] **Step 1: 创建实体类**
+
+```java
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 订单操作日志表 pos_order_log
+ */
+public class PosOrderLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @Excel(name = "主键ID", cellType = ColumnType.NUMERIC)
+    private Long id;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String ddId;
+
+    /** 操作人类型:0系统,1平台管理员,2商家,3骑手,4用户 */
+    @Excel(name = "操作人类型", readConverterExp = "0=系统,1=平台管理员,2=商家,3=骑手,4=用户")
+    private Integer operatorType;
+
+    /** 操作人ID */
+    @Excel(name = "操作人ID", cellType = ColumnType.NUMERIC)
+    private Long operatorId;
+
+    /** 操作人名称 */
+    @Excel(name = "操作人名称")
+    private String operatorName;
+
+    /** 操作内容 */
+    @Excel(name = "操作内容")
+    private String content;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date logTime;
+
+    public Long getId() { return id; }
+    public void setId(Long id) { this.id = id; }
+    public String getDdId() { return ddId; }
+    public void setDdId(String ddId) { this.ddId = ddId; }
+    public Integer getOperatorType() { return operatorType; }
+    public void setOperatorType(Integer operatorType) { this.operatorType = operatorType; }
+    public Long getOperatorId() { return operatorId; }
+    public void setOperatorId(Long operatorId) { this.operatorId = operatorId; }
+    public String getOperatorName() { return operatorName; }
+    public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Date getLogTime() { return logTime; }
+    public void setLogTime(Date logTime) { this.logTime = logTime; }
+}
+```
+
+注意:`logTime` 使用 `logTime` 而非 `createTime`,避免与 BaseEntity 的 `createTime` 冲突。BaseEntity 提供 `params` map 用于查询参数传递。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java
+git commit -m "feat(order-log): add PosOrderLog entity"
+```
+
+---
+
+### Task 2: 创建 MyBatis Mapper 接口和 XML
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java`
+- Create: `ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml`
+
+- [ ] **Step 1: 创建 Mapper 接口**
+
+```java
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+/**
+ * 订单操作日志 数据层
+ */
+public interface PosOrderLogMapper
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}
+```
+
+- [ ] **Step 2: 创建 XML Mapper**
+
+```xml
+<?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.ruoyi.system.mapper.PosOrderLogMapper">
+
+    <resultMap type="PosOrderLog" id="PosOrderLogResult">
+        <id     property="id"            column="id"             />
+        <result property="ddId"          column="dd_id"          />
+        <result property="operatorType"  column="operator_type"  />
+        <result property="operatorId"    column="operator_id"    />
+        <result property="operatorName"  column="operator_name"  />
+        <result property="content"       column="content"        />
+        <result property="logTime"       column="create_time"    />
+    </resultMap>
+
+    <sql id="selectOrderLogVo">
+        select id, dd_id, operator_type, operator_id, operator_name, content, create_time
+        from pos_order_log
+    </sql>
+
+    <insert id="insertOrderLog" parameterType="PosOrderLog">
+        insert into pos_order_log(dd_id, operator_type, operator_id, operator_name, content, create_time)
+        values (#{ddId}, #{operatorType}, #{operatorId}, #{operatorName}, #{content}, sysdate())
+    </insert>
+
+    <select id="selectOrderLogList" parameterType="PosOrderLog" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        <where>
+            <if test="ddId != null and ddId != ''">
+                AND dd_id like concat('%', #{ddId}, '%')
+            </if>
+            <if test="operatorId != null">
+                AND operator_id = #{operatorId}
+            </if>
+            <if test="operatorName != null and operatorName != ''">
+                AND operator_name like concat('%', #{operatorName}, '%')
+            </if>
+            <if test="operatorType != null">
+                AND operator_type = #{operatorType}
+            </if>
+            <if test="params.beginTime != null and params.beginTime != ''">
+                AND create_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params.endTime != null and params.endTime != ''">
+                AND create_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectOrderLogById" parameterType="Long" resultMap="PosOrderLogResult">
+        <include refid="selectOrderLogVo"/>
+        where id = #{id}
+    </select>
+
+</mapper>
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml
+git commit -m "feat(order-log): add PosOrderLog mapper interface and XML"
+```
+
+---
+
+### Task 3: 创建 Service 层
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java`
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java`
+
+- [ ] **Step 1: 创建 Service 接口**
+
+```java
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.PosOrderLog;
+
+/**
+ * 订单操作日志 服务层
+ */
+public interface IPosOrderLogService
+{
+    public void insertOrderLog(PosOrderLog orderLog);
+
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog);
+
+    public PosOrderLog selectOrderLogById(Long id);
+}
+```
+
+- [ ] **Step 2: 创建 Service 实现**
+
+```java
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.mapper.PosOrderLogMapper;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+/**
+ * 订单操作日志 服务层处理
+ */
+@Service
+public class PosOrderLogServiceImpl implements IPosOrderLogService
+{
+    @Autowired
+    private PosOrderLogMapper orderLogMapper;
+
+    @Override
+    public void insertOrderLog(PosOrderLog orderLog)
+    {
+        orderLogMapper.insertOrderLog(orderLog);
+    }
+
+    @Override
+    public List<PosOrderLog> selectOrderLogList(PosOrderLog orderLog)
+    {
+        return orderLogMapper.selectOrderLogList(orderLog);
+    }
+
+    @Override
+    public PosOrderLog selectOrderLogById(Long id)
+    {
+        return orderLogMapper.selectOrderLogById(id);
+    }
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java
+git commit -m "feat(order-log): add service interface and implementation"
+```
+
+---
+
+### Task 4: 创建 OrderLogHelper 工具类
+
+**Files:**
+- Create: `ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java`
+
+这个工具类封装日志构建逻辑,各调用方只需传入 ddId、operatorType、operatorId、operatorName、content 即可。
+
+- [ ] **Step 1: 创建工具类**
+
+```java
+package com.ruoyi.system.utils;
+
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.TimerTask;
+
+/**
+ * 订单操作日志工具类
+ */
+@Component
+public class OrderLogHelper {
+
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    /**
+     * 异步写入订单日志
+     */
+    public void log(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog log = new PosOrderLog();
+        log.setDdId(ddId);
+        log.setOperatorType(operatorType);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setContent(content);
+        AsyncManager.me().execute(new TimerTask() {
+            @Override
+            public void run() {
+                orderLogService.insertOrderLog(log);
+            }
+        });
+    }
+
+    /**
+     * 同步写入订单日志(用于定时任务等异步上下文)
+     */
+    public void logSync(String ddId, int operatorType, Long operatorId, String operatorName, String content) {
+        PosOrderLog log = new PosOrderLog();
+        log.setDdId(ddId);
+        log.setOperatorType(operatorType);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setContent(content);
+        orderLogService.insertOrderLog(log);
+    }
+}
+```
+
+使用 `AsyncManager` 异步写入,避免影响主流程性能。`logSync` 给定时任务使用(定时任务已在异步线程中)。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java
+git commit -m "feat(order-log): add OrderLogHelper utility for async log writing"
+```
+
+---
+
+### Task 5: 创建后台管理 Controller
+
+**Files:**
+- Create: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java`
+
+- [ ] **Step 1: 创建 Controller**
+
+```java
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.PosOrderLog;
+import com.ruoyi.system.service.IPosOrderLogService;
+
+/**
+ * 订单操作日志
+ */
+@RestController
+@RequestMapping("/system/orderLog")
+public class PosOrderLogController extends BaseController
+{
+    @Autowired
+    private IPosOrderLogService orderLogService;
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(PosOrderLog orderLog)
+    {
+        startPage();
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:orderLog:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id)
+    {
+        return success(orderLogService.selectOrderLogById(id));
+    }
+
+    @Log(title = "订单操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:orderLog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, PosOrderLog orderLog)
+    {
+        List<PosOrderLog> list = orderLogService.selectOrderLogList(orderLog);
+        ExcelUtil<PosOrderLog> util = new ExcelUtil<PosOrderLog>(PosOrderLog.class);
+        util.exportExcel(response, list, "订单操作日志");
+    }
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java
+git commit -m "feat(order-log): add admin controller with list/detail/export"
+```
+
+---
+
+### Task 6: 商家端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java`
+
+- [ ] **Step 1: 注入 OrderLogHelper**
+
+在 `PosOrderShOprateController` 的成员变量区域添加:
+
+```java
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+添加 import:
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+```
+
+- [ ] **Step 2: acceptOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已接单");
+```
+
+- [ ] **Step 3: dispatchOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后、`chuCan(order, update);` 之前添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已出餐");
+```
+
+- [ ] **Step 4: completeOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已完成订单");
+```
+
+- [ ] **Step 5: cancelOrder 方法添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String shName = shUser != null ? shUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "取消订单");
+```
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java
+git commit -m "feat(order-log): add order log to merchant operations"
+```
+
+---
+
+### Task 7: 骑手端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java`
+
+- [ ] **Step 1: 注入 OrderLogHelper**
+
+在成员变量区域添加:
+
+```java
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+添加 import:
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+```
+
+- [ ] **Step 2: acceptOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "接单成功");
+```
+
+- [ ] **Step 3: pickupOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已取餐");
+```
+
+- [ ] **Step 4: deliverOrder 方法添加日志**
+
+在 `return setOrderQsState(posOrder, qsId, push);` 之前添加:
+
+```java
+InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+String qsName = qsUser != null ? qsUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已送达");
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java
+git commit -m "feat(order-log): add order log to rider operations"
+```
+
+---
+
+### Task 8: 用户端和平台端操作插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java`
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java`
+
+- [ ] **Step 1: UserOrderController 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 2: UserOrderController.createOrderChild 添加日志**
+
+在订单创建成功后(`posOrderService.saveOrUpdate()` 之后)添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(posOrder.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "创建订单");
+```
+
+注意:`userId` 在 createOrderChild 方法中从 token 获取,变量名需确认是 `userId` 还是从 `jwtUtil.getusid(token)` 获取。根据实际方法中已有的变量名调整。
+
+- [ ] **Step 3: UserOrderController.confirmPickup 添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(new JwtUtil().getusid(token)), uName, "用户" + uName + "确认取餐");
+```
+
+- [ ] **Step 4: UserOrderController.cancelOrder 添加日志**
+
+在 `posOrderService.saveOrUpdate(update);` 之后添加:
+
+```java
+InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+String uName = uUser != null ? uUser.getNickName() : "";
+orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(new JwtUtil().getusid(token)), uName, "用户" + uName + "取消订单");
+```
+
+- [ ] **Step 5: PosOrderController.setorderuzt 添加日志**
+
+在 `PosOrderController` 中注入 OrderLogHelper。
+
+在 `setorderuzt` 方法中,通用的 `posOrderService.saveOrUpdate(posOrder)` 成功分支添加:
+
+```java
+// 获取平台管理员信息
+SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+String adminName = adminUser != null ? adminUser.getNickName() : "";
+PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+String ddId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+orderLogHelper.log(ddId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+    "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+```
+
+注意:`PosOrderController.setorderuzt` 是平台管理端调用的接口(使用 Spring Security 认证),通过 `SecurityUtils.getLoginUser()` 获取当前管理员信息。需添加 import `com.ruoyi.common.utils.SecurityUtils` 和 `com.ruoyi.common.core.domain.entity.SysUser`。
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java
+git commit -m "feat(order-log): add order log to user and platform operations"
+```
+
+---
+
+### Task 9: 定时任务和支付回调插入日志
+
+**Files:**
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java`
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java`
+
+- [ ] **Step 1: TestTask 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 2: TestTask.testTiming 添加日志**
+
+在 `posOrderService.saveOrUpdate(posOrder);` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动取消超时未接单订单");
+```
+
+使用 `logSync` 而非 `log`,因为定时任务已在异步上下文中。
+
+- [ ] **Step 3: TestTask.zidwancheng 添加日志**
+
+在 `posOrderService.saveOrUpdate(pos);` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动完成超时订单");
+```
+
+- [ ] **Step 4: TestTask.refundProcessing 添加日志**
+
+在退款成功后(`posOrderService.saveOrUpdate(posOrder);` 之后)添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(list.get(i).getDdId()), 0, null, "系统", "系统处理退款完成");
+```
+
+- [ ] **Step 5: PayController 注入 OrderLogHelper**
+
+添加 import 和成员变量:
+
+```java
+import com.ruoyi.system.utils.OrderLogHelper;
+
+@Autowired
+private OrderLogHelper orderLogHelper;
+```
+
+- [ ] **Step 6: PayController 支付回调添加日志**
+
+在支付成功设置 `payStatus=1` 之后添加:
+
+```java
+orderLogHelper.logSync(String.valueOf(order.getDdId()), 0, null, "系统", "系统收到支付成功回调");
+```
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java
+git commit -m "feat(order-log): add order log to scheduled tasks and payment callback"
+```
+
+---
+
+### Task 10: 前端 - API 文件
+
+**Files:**
+- Create: `E:\QtwCode\foodie\foodie-admin-vue\src\api\system\orderLog.js`
+
+- [ ] **Step 1: 创建 API 文件**
+
+```javascript
+import request from '@/utils/request'
+
+// 查询订单操作日志列表
+export function listOrderLog(query) {
+  return request({
+    url: '/system/orderLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询订单操作日志详细
+export function getOrderLog(id) {
+  return request({
+    url: '/system/orderLog/' + id,
+    method: 'get'
+  })
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add "E:/QtwCode/foodie/foodie-admin-vue/src/api/system/orderLog.js"
+git commit -m "feat(order-log): add frontend API file"
+```
+
+---
+
+### Task 11: 前端 - 日志查询页面
+
+**Files:**
+- Create: `E:\QtwCode\foodie\foodie-admin-vue\src\views\system\order\log.vue`
+
+这是核心前端页面,参照截图中的 UI 设计。页面使用 Element UI 组件,包含搜索栏、数据表格、分页。
+
+- [ ] **Step 1: 创建日志页面**
+
+创建 `log.vue`,包含以下核心结构:
+
+**搜索栏**:订单号输入框、操作人ID输入框、操作人名称输入框、搜索按钮、重置按钮、导出按钮
+
+**数据表格列**:主键ID、订单号、操作人类型(格式化为中文)、操作人ID、操作人名称、操作内容、时间、操作(查看链接)
+
+**操作人类型格式化方法**:
+
+```javascript
+operatorTypeFormat(type) {
+  const map = { 0: '系统', 1: '平台', 2: '商家', 3: '骑手', 4: '用户' }
+  return map[type] || type
+}
+```
+
+**核心 data 属性**:
+
+```javascript
+data() {
+  return {
+    orderLogList: [],
+    total: 0,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      ddId: undefined,
+      operatorId: undefined,
+      operatorName: undefined
+    }
+  }
+}
+```
+
+**核心方法**:
+
+```javascript
+methods: {
+  getList() {
+    listOrderLog(this.queryParams).then(response => {
+      this.orderLogList = response.rows
+      this.total = response.total
+    })
+  },
+  handleQuery() {
+    this.queryParams.pageNum = 1
+    this.getList()
+  },
+  resetQuery() {
+    this.queryParams = { pageNum: 1, pageSize: 10, ddId: undefined, operatorId: undefined, operatorName: undefined }
+    this.getList()
+  },
+  handleExport() {
+    this.download('system/orderLog/export', { ...this.queryParams }, '订单操作日志.xlsx')
+  }
+}
+```
+
+查看按钮点击后弹窗显示该条日志详情(使用 el-dialog)。
+
+由于前端文件使用 CRLF 换行,编写时使用 Python 脚本方式创建文件。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add "E:/QtwCode/foodie/foodie-admin-vue/src/views/system/order/log.vue"
+git commit -m "feat(order-log): add order log query page for admin"
+```
+
+---
+
+### Task 12: 前端 - 路由和菜单配置
+
+**Files:**
+- Modify: `E:\QtwCode\foodie\foodie-admin-vue\src\router\index.js`
+
+订单日志页面需要添加到路由配置中。在系统管理或订单管理路由组下添加。
+
+由于本项目使用动态路由(菜单由后台数据库配置),路由代码不需要手动添加。需要在数据库的 `sys_menu` 表中插入菜单记录。
+
+- [ ] **Step 1: 添加 SQL 到 updatesql/sql.md**
+
+在 `updatesql/sql.md` 末尾追加菜单插入 SQL:
+
+```sql
+-- 2026-05-19 新增订单操作日志菜单
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+SELECT '订单日志', menu_id, 6, 'orderLog', 'system/order/log', 'C', '0', '0', 'system:orderLog:list', 'log', 'admin', NOW(), '订单操作日志菜单'
+FROM sys_menu WHERE menu_name = '订单管理' AND parent_id = 0 LIMIT 1;
+
+-- 日志查询按钮权限
+SET @logMenuId = (SELECT menu_id FROM sys_menu WHERE perms = 'system:orderLog:list' LIMIT 1);
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志查询', @logMenuId, 1, '#', '', 'F', '0', '0', 'system:orderLog:query', '#', 'admin', NOW());
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志导出', @logMenuId, 2, '#', '', 'F', '0', '0', 'system:orderLog:export', '#', 'admin', NOW());
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add updatesql/sql.md
+git commit -m "feat(order-log): add menu SQL for order log page"
+```
+
+---
+
+### Task 13: 执行数据库建表 SQL
+
+- [ ] **Step 1: 在数据库执行建表语句**
+
+从 `updatesql/sql.md` 中复制 `CREATE TABLE pos_order_log` 和菜单插入 SQL,在 MySQL 中执行。
+
+- [ ] **Step 2: 验证**
+
+- 启动后端服务
+- 使用商家端操作一个订单(接单→出餐→完成)
+- 打开平台管理端,进入"订单管理 > 订单日志"页面
+- 确认能看到操作日志记录
+- 测试搜索和导出功能
+
+---
+
+## Self-Review Checklist
+
+### 1. Spec Coverage
+| Spec 要求 | 对应 Task |
+|-----------|----------|
+| pos_order_log 表 | Task 1-3 (Entity/Mapper/Service), SQL in updatesql/sql.md |
+| 5 种操作人类型 | Task 1 (Entity), Task 4 (Helper), Task 6-9 (各调用方) |
+| 日志写入时机(商家4种) | Task 6 |
+| 日志写入时机(骑手3种) | Task 7 |
+| 日志写入时机(用户3种) | Task 8 |
+| 日志写入时机(平台1种) | Task 8 |
+| 日志写入时机(系统3种) | Task 9 |
+| 平台管理端查询页面 | Task 10-12 |
+| 搜索条件(订单号/操作人ID/名称) | Task 2 (XML), Task 11 (前端) |
+| 导出功能 | Task 5 (Controller), Task 11 (前端) |
+| 菜单配置 | Task 12 |
+
+### 2. Placeholder Scan
+- 无 TBD/TODO/实现稍后
+- 所有代码步骤都有完整代码
+
+### 3. Type Consistency
+- PosOrderLog 字段在 Entity/Mapper XML/Service/Controller/Helper 中保持一致
+- `ddId` 类型 String, `operatorType` 类型 Integer, `operatorId` 类型 Long
+- `logTime` 映射到数据库 `create_time` 列

+ 141 - 0
specs/007-orderlog/tasks.md

@@ -0,0 +1,141 @@
+# Tasks: 订单操作日志
+
+**Input**: specs/007-orderlog/spec.md, specs/007-orderlog/plan.md
+**Prerequisites**: plan.md, spec.md
+**Tests**: 无自动化测试,通过手动操作验证(商家/骑手/用户操作后查看日志页面)。
+
+**Organization**: 按 Phase 分组——基础设施 → 日志写入集成 → 前端页面 → 部署验证。
+
+## Format: `[ID] [P?] Description`
+
+- **[P]**: 可并行(不同文件,无依赖)
+- 包含具体文件路径
+
+## Path Conventions
+
+- 后端源码: `ruoyi-system/src/main/java/com/ruoyi/system/`、`ruoyi-admin/src/main/java/com/ruoyi/`
+- MyBatis XML: `ruoyi-system/src/main/resources/mapper/system/`
+- SQL: `updatesql/sql.md`
+- 前端: `E:\QtwCode\foodie\foodie-admin-vue\src\`
+
+---
+
+## Phase 1: 数据基础设施(表 + Entity + Mapper + Service)
+
+**Purpose**: 新建 pos_order_log 表和完整的数据访问层,是所有后续任务的基础。
+
+- [x] T001 在 `updatesql/sql.md` 末尾追加 CREATE TABLE pos_order_log 语句(id, dd_id, operator_type, operator_id, operator_name, content, create_time + 索引)
+- [x] T002 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderLog.java` 实体类(id, ddId, operatorType, operatorId, operatorName, content, logTime),继承 BaseEntity,添加 @Excel 导出注解
+- [x] T003 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderLogMapper.java` 接口(insertOrderLog, selectOrderLogList, selectOrderLogById)
+- [x] T004 [P] 创建 `ruoyi-system/src/main/resources/mapper/system/PosOrderLogMapper.xml`(resultMap、insert、selectList 带动态条件、selectById)
+- [x] T005 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/service/IPosOrderLogService.java` 接口
+- [x] T006 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PosOrderLogServiceImpl.java` 实现
+
+**Checkpoint**: 数据库建表执行完毕,项目编译通过,PosOrderLog 的 CRUD 可通过 Mapper 调用。
+
+---
+
+## Phase 2: 日志写入基础设施(Helper + Controller)
+
+**Purpose**: 封装日志写入工具类和后台管理接口。
+
+- [x] T007 创建 `ruoyi-system/src/main/java/com/ruoyi/system/utils/OrderLogHelper.java` 工具类(异步 log + 同步 logSync 两个方法)
+- [x] T008 [P] 创建 `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PosOrderLogController.java`(list/detail/export 三个接口,权限 system:orderLog:*)
+
+**Checkpoint**: 后台管理 `/system/orderLog/list` 接口可正常返回空列表,项目编译通过。
+
+---
+
+## Phase 3: 日志写入集成 — 商家操作
+
+**Purpose**: 商家端 4 种操作(接单/出餐/完成/取消)自动写入日志。
+
+- [x] T009 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java`:注入 OrderLogHelper,在 acceptOrder 方法 saveOrUpdate 之后写入日志(operatorType=2, "商家{名}已接单")
+- [x] T010 修改 `PosOrderShOprateController.java`:在 dispatchOrder 方法 saveOrUpdate 之后写入日志("商家{名}已出餐")
+- [x] T011 修改 `PosOrderShOprateController.java`:在 completeOrder 方法 saveOrUpdate 之后写入日志("商家{名}已完成订单")
+- [x] T012 修改 `PosOrderShOprateController.java`:在 cancelOrder 方法 saveOrUpdate 之后写入日志("商家{名}取消订单")
+
+**Checkpoint**: 商家执行接单→出餐→完成操作后,pos_order_log 表中有对应 3 条日志记录。
+
+---
+
+## Phase 4: 日志写入集成 — 骑手操作
+
+**Purpose**: 骑手端 3 种操作(接单/取餐/送达)自动写入日志。
+
+- [x] T013 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java`:注入 OrderLogHelper,在 acceptOrder 方法写入日志(operatorType=3, "骑手{名}接单成功")
+- [x] T014 修改 `PosOrderQsOprateController.java`:在 pickupOrder 方法写入日志("骑手{名}已取餐")
+- [x] T015 修改 `PosOrderQsOprateController.java`:在 deliverOrder 方法写入日志("骑手{名}已送达")
+
+**Checkpoint**: 骑手执行接单→取餐→送达后,pos_order_log 表中有对应 3 条日志记录。
+
+---
+
+## Phase 5: 日志写入集成 — 用户 + 平台操作
+
+**Purpose**: 用户端和平台管理端操作写入日志。
+
+- [x] T016 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java`:注入 OrderLogHelper,在 createOrderChild 订单创建成功后写入日志(operatorType=4, "用户{名}创建订单")
+- [x] T017 修改 `UserOrderController.java`:在 confirmPickup 确认取餐后写入日志("用户{名}确认取餐")
+- [x] T018 修改 `UserOrderController.java`:在 cancelOrder 取消订单后写入日志("用户{名}取消订单")
+- [x] T019 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java`:注入 OrderLogHelper,在 setorderuzt 方法状态修改成功后写入日志(operatorType=1, "平台管理员{名}修改订单状态: X → Y")
+
+**Checkpoint**: 用户下单后日志表有 1 条记录,平台管理员修改状态后有对应日志。
+
+---
+
+## Phase 6: 日志写入集成 — 系统操作
+
+**Purpose**: 定时任务和支付回调写入日志。
+
+- [x] T020 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java`:注入 OrderLogHelper,在 testTiming 超时取消后调用 logSync(operatorType=0, "系统自动取消超时未接单订单")
+- [x] T021 修改 `TestTask.java`:在 zidwancheng 自动完成后调用 logSync("系统自动完成超时订单")
+- [x] T022 修改 `TestTask.java`:在 refundProcessing 退款完成后调用 logSync("系统处理退款完成")
+- [x] T023 修改 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java`:注入 OrderLogHelper,在支付回调成功后调用 logSync("系统收到支付成功回调")
+
+**Checkpoint**: 触发超时取消或支付回调后,日志表有对应系统日志记录。
+
+---
+
+## Phase 7: 前端页面
+
+**Purpose**: 平台管理端新增订单日志查询页面。
+
+- [x] T024 [P] 创建 `foodie-admin-vue/src/api/system/orderLog.js`(listOrderLog, getOrderLog 两个 API 方法)
+- [x] T025 创建 `foodie-admin-vue/src/views/system/order/log.vue` 日志查询页面:
+  - 搜索栏:订单号输入框、操作人ID输入框、操作人名称输入框、搜索/重置/导出按钮
+  - 数据表格:主键ID、订单号、操作人类型(格式化:0系统/1平台/2商家/3骑手/4用户)、操作人ID、操作人名称、操作内容、时间、操作(查看按钮)
+  - 分页组件
+  - 查看弹窗:el-dialog 显示单条日志详情
+- [x] T026 在 `updatesql/sql.md` 末尾追加菜单插入 SQL(sys_menu 表:订单日志菜单 + 查询权限 + 导出权限)
+
+**Checkpoint**: 平台管理端左侧菜单出现"订单日志",点击进入可看到日志列表,搜索和导出功能正常。
+
+---
+
+## Phase 8: 部署验证
+
+**Purpose**: 执行全部 SQL 并端到端验证。
+
+- [x] T027 在 MySQL 中执行 updatesql/sql.md 中 007-orderlog 相关的所有 SQL(CREATE TABLE + 菜单 INSERT)
+- [x] T028 启动后端服务,用商家端操作一个完整订单流程(接单→出餐→完成),打开平台管理端"订单日志"页面确认日志记录完整
+- [x] T029 测试搜索功能(按订单号、操作人ID、操作人名称筛选)和导出功能
+
+**Checkpoint**: 完整的端到端验证通过,所有操作日志正确记录并可查询导出。
+
+---
+
+## 依赖关系
+
+```
+Phase 1 (T001-T006) → Phase 2 (T007-T008) → Phase 3-6 (T009-T023, 可并行)
+                                                  ↓
+                                            Phase 7 (T024-T026, 部分可并行)
+                                                  ↓
+                                            Phase 8 (T027-T029)
+```
+
+- Phase 1 全部完成后才能进入 Phase 2
+- Phase 2 完成后,Phase 3-6 可并行执行(各修改不同文件)
+- Phase 7 的 T024 可与 Phase 3-6 并行,T025 依赖后端接口可用
+- Phase 8 必须最后执行