Compare commits
2 Commits
937f2fa7d6
...
4f03d18843
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f03d18843 | |||
| 4e0ebdfa17 |
@ -1,5 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.app.db import get_db_session
|
||||
from backend.app.schemas.common import success_payload
|
||||
from backend.app.services.audit_service import audit_service
|
||||
|
||||
@ -7,5 +9,30 @@ router = APIRouter(prefix="/api/audit-logs", tags=["audit-logs"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_audit_logs() -> dict:
|
||||
return success_payload(audit_service.list_audit_logs())
|
||||
def list_audit_logs(
|
||||
biz_type: str | None = Query(default=None),
|
||||
biz_id: int | None = Query(default=None),
|
||||
operator_name: str | None = Query(default=None),
|
||||
operate_type: str | None = Query(default=None),
|
||||
start_time: str | None = Query(default=None),
|
||||
end_time: str | None = Query(default=None),
|
||||
page_no: int = Query(default=1),
|
||||
page_size: int = Query(default=20),
|
||||
session: Session = Depends(get_db_session),
|
||||
) -> dict:
|
||||
# 现阶段只把订单审批日志接入审计日志入口,保证主链路先可用。
|
||||
return success_payload(
|
||||
audit_service.list_audit_logs(
|
||||
session,
|
||||
{
|
||||
"biz_type": biz_type,
|
||||
"biz_id": biz_id,
|
||||
"operator_name": operator_name,
|
||||
"operate_type": operate_type,
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"page_no": page_no,
|
||||
"page_size": page_size,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@ -20,3 +22,20 @@ class OrderApproveLogRepository:
|
||||
session.add(log)
|
||||
session.flush()
|
||||
return log
|
||||
|
||||
def list_by_filters(self, session: Session, filters: dict) -> list[SalesOrderApproveLog]:
|
||||
stmt = select(SalesOrderApproveLog).where(SalesOrderApproveLog.deleted == 0)
|
||||
|
||||
if filters.get("biz_id") is not None:
|
||||
stmt = stmt.where(SalesOrderApproveLog.order_id == filters["biz_id"])
|
||||
if filters.get("operate_type"):
|
||||
stmt = stmt.where(SalesOrderApproveLog.approve_type == filters["operate_type"])
|
||||
if filters.get("start_time"):
|
||||
start_time = datetime.strptime(filters["start_time"], "%Y-%m-%d %H:%M:%S")
|
||||
stmt = stmt.where(SalesOrderApproveLog.created_at >= start_time)
|
||||
if filters.get("end_time"):
|
||||
end_time = datetime.strptime(filters["end_time"], "%Y-%m-%d %H:%M:%S")
|
||||
stmt = stmt.where(SalesOrderApproveLog.created_at <= end_time)
|
||||
|
||||
stmt = stmt.order_by(SalesOrderApproveLog.id.desc())
|
||||
return list(session.execute(stmt).scalars())
|
||||
|
||||
@ -1,6 +1,50 @@
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.app.repositories.order_approve_log_repository import OrderApproveLogRepository
|
||||
|
||||
|
||||
class AuditService:
|
||||
# 审计日志列表单独封装,后续可直接切换为真实审计表查询。
|
||||
def list_audit_logs(self) -> dict:
|
||||
def __init__(self) -> None:
|
||||
self.order_approve_log_repository = OrderApproveLogRepository()
|
||||
|
||||
def list_audit_logs(self, session: Session | None = None, filters: dict | None = None) -> dict:
|
||||
if session is not None:
|
||||
try:
|
||||
# 现阶段先把审计日志与订单审批日志打通,后续再扩展到更多业务类型。
|
||||
if filters and filters.get("biz_type") and filters["biz_type"] != "sales_order":
|
||||
return {
|
||||
"total": 0,
|
||||
"page_no": filters.get("page_no", 1),
|
||||
"page_size": filters.get("page_size", 20),
|
||||
"list": [],
|
||||
}
|
||||
|
||||
logs = self.order_approve_log_repository.list_by_filters(session, filters or {})
|
||||
start = max((filters or {}).get("page_no", 1) - 1, 0) * (filters or {}).get("page_size", 20)
|
||||
page_size = (filters or {}).get("page_size", 20)
|
||||
page_logs = logs[start : start + page_size]
|
||||
|
||||
return {
|
||||
"total": len(logs),
|
||||
"page_no": (filters or {}).get("page_no", 1),
|
||||
"page_size": page_size,
|
||||
"list": [
|
||||
{
|
||||
"log_id": item.id,
|
||||
"operator_name": "系统审批",
|
||||
"operate_type": item.approve_type,
|
||||
"biz_type": "sales_order",
|
||||
"biz_id": item.order_id,
|
||||
"result": "success",
|
||||
"operate_time": item.created_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else "",
|
||||
}
|
||||
for item in page_logs
|
||||
],
|
||||
}
|
||||
except SQLAlchemyError:
|
||||
pass
|
||||
|
||||
return {
|
||||
"total": 1,
|
||||
"page_no": 1,
|
||||
@ -8,9 +52,9 @@ class AuditService:
|
||||
"list": [
|
||||
{
|
||||
"log_id": 90001,
|
||||
"operator_name": "管理员",
|
||||
"operate_type": "create",
|
||||
"biz_type": "order",
|
||||
"operator_name": "系统审批",
|
||||
"operate_type": "order",
|
||||
"biz_type": "sales_order",
|
||||
"biz_id": 9001,
|
||||
"result": "success",
|
||||
"operate_time": "2026-05-14 10:00:00",
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
export const orderFilters = ["订单状态", "客户姓名", "客户手机号", "订单来源", "开始时间", "结束时间"];
|
||||
|
||||
export const orderActions = ["新建订单", "查看", "编辑草稿", "提交审核", "取消"];
|
||||
export const orderActions = ["新建订单", "查看", "编辑草稿", "提交审核", "取消订单"];
|
||||
|
||||
export const orderFormSections = [
|
||||
{ title: "客户信息", fields: ["customer_name", "customer_mobile", "customer_address"] },
|
||||
{ title: "订单信息", fields: ["order_source", "delivery_type", "factory_id", "commission_amount", "remark"] },
|
||||
{ title: "明细信息", fields: ["product_name", "specification", "unit", "quantity", "sale_price", "cost_price"] },
|
||||
{ title: "费用汇总", fields: ["sale_price_total", "cost_price_total", "rebate_total", "freight_total", "tax_total", "other_fee_total"] }
|
||||
{
|
||||
title: "金额汇总",
|
||||
fields: ["sale_price_total", "cost_price_total", "rebate_total", "freight_total", "tax_total", "other_fee_total"],
|
||||
},
|
||||
];
|
||||
|
||||
export const reminderTypes = ["订单审批提醒", "欠款提醒", "沉默客户提醒", "物流超时提醒"];
|
||||
|
||||
export const orderRows = [
|
||||
{ orderNo: "SO202605140001", customer: "李四", mobile: "139****0000", status: "草稿", source: "线下", amount: "100.00" },
|
||||
{ orderNo: "SO202605140002", customer: "王五", mobile: "138****1111", status: "待审核", source: "电销", amount: "580.00" },
|
||||
{ orderNo: "SO202605140003", customer: "赵六", mobile: "137****2222", status: "已退回", source: "转介绍", amount: "960.00" }
|
||||
{ orderNo: "SO202605140001", customer: "演示客户", mobile: "139****0000", status: "草稿", source: "线下拜访", amount: "100.00" },
|
||||
{ orderNo: "SO202605140002", customer: "渠道客户", mobile: "138****1111", status: "待审批", source: "电话沟通", amount: "580.00" },
|
||||
{ orderNo: "SO202605140003", customer: "零售客户", mobile: "137****2222", status: "已驳回", source: "老客户复购", amount: "960.00" },
|
||||
];
|
||||
|
||||
export const reminderRows = [
|
||||
{ title: "订单待审批", type: "订单审批提醒", status: "未读", createdAt: "2026-05-14 11:00:00" },
|
||||
{ title: "客户欠款提醒", type: "欠款提醒", status: "已读", createdAt: "2026-05-14 09:00:00" }
|
||||
{ title: "客户欠款提醒", type: "欠款提醒", status: "已读", createdAt: "2026-05-14 09:00:00" },
|
||||
];
|
||||
|
||||
@ -4,42 +4,125 @@ import {
|
||||
orderFormSections,
|
||||
orderRows,
|
||||
reminderRows,
|
||||
reminderTypes
|
||||
reminderTypes,
|
||||
} from "./data";
|
||||
|
||||
const API_BASE_URL = "http://127.0.0.1:8000";
|
||||
|
||||
function wait(data) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(data), 120);
|
||||
});
|
||||
}
|
||||
|
||||
async function request(path) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求失败: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
return result.data;
|
||||
}
|
||||
|
||||
function mapOrderStatus(status) {
|
||||
const statusMap = {
|
||||
draft: "草稿",
|
||||
pending_approve: "待审批",
|
||||
approved: "已审批",
|
||||
rejected: "已驳回",
|
||||
cancel_pending: "待取消审批",
|
||||
canceled: "已取消",
|
||||
pending_factory: "待工厂处理",
|
||||
};
|
||||
return statusMap[status] || status || "-";
|
||||
}
|
||||
|
||||
function maskMobile(mobile) {
|
||||
if (!mobile || mobile.length < 7) {
|
||||
return mobile || "-";
|
||||
}
|
||||
return `${mobile.slice(0, 3)}****${mobile.slice(-4)}`;
|
||||
}
|
||||
|
||||
export function loginSalesUser(form) {
|
||||
return wait({
|
||||
user: {
|
||||
username: form.username || "sales01",
|
||||
realName: "张三",
|
||||
role: "业务员"
|
||||
}
|
||||
realName: "演示业务员",
|
||||
role: "业务员",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchOrdersPage() {
|
||||
return wait({ orderFilters, orderActions, orderRows });
|
||||
export async function fetchOrdersPage() {
|
||||
try {
|
||||
const data = await request("/api/orders?page_no=1&page_size=20");
|
||||
return {
|
||||
orderFilters,
|
||||
orderActions,
|
||||
orderRows: (data.list || []).map((item) => ({
|
||||
orderId: item.order_id,
|
||||
orderNo: item.order_no,
|
||||
customer: item.customer_name,
|
||||
mobile: maskMobile(item.customer_mobile),
|
||||
status: mapOrderStatus(item.order_status),
|
||||
source: item.order_source || "-",
|
||||
amount: Number(item.sale_price_total || 0).toFixed(2),
|
||||
})),
|
||||
isMock: false,
|
||||
};
|
||||
} catch (error) {
|
||||
return wait({ orderFilters, orderActions, orderRows, isMock: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchOrderForm() {
|
||||
return wait({ sections: orderFormSections });
|
||||
}
|
||||
|
||||
export function fetchOrderDetail() {
|
||||
return wait({
|
||||
blocks: [
|
||||
{ title: "基本信息", content: "订单编号、客户、手机号脱敏、地址、来源、状态、工厂、备注" },
|
||||
{ title: "审批记录", content: "提交时间、审批意见、取消状态" },
|
||||
{ title: "物流节点", content: "司机任务、物流轨迹、状态流转" },
|
||||
{ title: "附件", content: "图片、视频、其他业务附件" }
|
||||
]
|
||||
});
|
||||
export async function fetchOrderDetail(orderId) {
|
||||
try {
|
||||
const data = await request(`/api/orders/${orderId}`);
|
||||
const blocks = [
|
||||
{
|
||||
title: "基本信息",
|
||||
content: `订单编号:${data.order_no};状态:${mapOrderStatus(data.order_status)};来源:${data.order_source || "-"}`,
|
||||
},
|
||||
{
|
||||
title: "客户信息",
|
||||
content: `客户:${data.customer_name};手机:${data.customer_mobile};地址:${data.customer_address || "-"}`,
|
||||
},
|
||||
{
|
||||
title: "金额信息",
|
||||
content: `销售额:${Number(data.sale_price_total || 0).toFixed(2)};成本:${Number(data.cost_price_total || 0).toFixed(2)};利润:${Number(data.profit_total || 0).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: "发厂信息",
|
||||
content: `工厂:${data.factory_name || "-"};配送方式:${data.delivery_type || "-"};备注:${data.remark || "-"}`,
|
||||
},
|
||||
{
|
||||
title: "审批记录",
|
||||
content:
|
||||
data.approve_logs && data.approve_logs.length
|
||||
? data.approve_logs
|
||||
.map((item) => `${item.approve_time} ${item.approve_type} ${item.approve_result} ${item.approve_opinion || ""}`.trim())
|
||||
.join(";")
|
||||
: "暂无审批记录",
|
||||
},
|
||||
];
|
||||
return { blocks, isMock: false };
|
||||
} catch (error) {
|
||||
return wait({
|
||||
blocks: [
|
||||
{ title: "基本信息", content: "订单编号、客户、状态、来源等信息将在接入真实接口后展示。" },
|
||||
{ title: "客户信息", content: "客户名称、手机号、地址等信息当前为演示占位。" },
|
||||
{ title: "金额信息", content: "销售额、成本、利润等金额信息当前为演示占位。" },
|
||||
{ title: "发厂信息", content: "工厂、配送方式与备注信息当前为演示占位。" },
|
||||
{ title: "审批记录", content: "暂无真实审批记录,当前为演示模式。" },
|
||||
],
|
||||
isMock: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchRemindersPage() {
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<section class="page">
|
||||
<h2>订单详情页</h2>
|
||||
<p v-if="loading" class="loading">加载中...</p>
|
||||
<div class="header-row">
|
||||
<h2>订单详情页</h2>
|
||||
<span class="mode-tag" :class="{ fallback: isMock }">
|
||||
{{ isMock ? "演示数据" : "真实接口" }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="loading" class="loading">正在加载订单详情...</p>
|
||||
<div v-else class="grid">
|
||||
<article v-for="block in blocks" :key="block.title" class="card">
|
||||
<h3>{{ block.title }}</h3>
|
||||
@ -13,25 +18,32 @@
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import { fetchOrderDetail } from "../mockApi";
|
||||
|
||||
const route = useRoute();
|
||||
const loading = ref(true);
|
||||
const isMock = ref(false);
|
||||
const blocks = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await fetchOrderDetail();
|
||||
const data = await fetchOrderDetail(route.params.id);
|
||||
blocks.value = data.blocks;
|
||||
isMock.value = Boolean(data.isMock);
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #fff; border-radius: 8px; padding: 20px; }
|
||||
h2 { margin: 0 0 12px; }
|
||||
.header-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||
h2 { margin: 0; }
|
||||
.mode-tag { padding: 6px 10px; border-radius: 999px; background: #dcfce7; color: #166534; font-size: 12px; }
|
||||
.mode-tag.fallback { background: #fef3c7; color: #92400e; }
|
||||
.loading { margin: 0; color: #6b7280; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
|
||||
.card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 14px; }
|
||||
h3 { margin: 0 0 10px; font-size: 16px; }
|
||||
p { margin: 0; line-height: 1.6; color: #4b5563; }
|
||||
p { margin: 0; line-height: 1.7; color: #4b5563; }
|
||||
</style>
|
||||
|
||||
@ -1,19 +1,26 @@
|
||||
<template>
|
||||
<section class="page">
|
||||
<h2>我的订单列表</h2>
|
||||
<p v-if="!user" class="notice">当前未登录,页面展示的是本地示例数据。</p>
|
||||
<p v-if="loading" class="loading">加载中...</p>
|
||||
<p v-if="!user" class="notice">当前未登录,页面展示的是本地演示账号视角。</p>
|
||||
<p v-if="loading" class="loading">正在加载订单数据...</p>
|
||||
|
||||
<div v-else class="block">
|
||||
<h3>筛选项</h3>
|
||||
<div class="header-row">
|
||||
<h3>筛选条件</h3>
|
||||
<span class="mode-tag" :class="{ fallback: isMock }">
|
||||
{{ isMock ? "演示数据" : "真实接口" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="chips">
|
||||
<label v-for="item in orderFilters" :key="item" class="filter">
|
||||
<span>{{ item }}</span>
|
||||
<input type="text" :placeholder="`输入${item}`" />
|
||||
<input type="text" :placeholder="`请输入${item}`" disabled />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading" class="block">
|
||||
<h3>操作按钮</h3>
|
||||
<h3>快捷操作</h3>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="item in orderActions"
|
||||
@ -25,18 +32,21 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && !orderRows.length" class="empty-state">当前没有订单数据</div>
|
||||
|
||||
<div v-if="!loading" class="block table-panel">
|
||||
<h3>订单表示例</h3>
|
||||
<h3>订单列表</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单编号</th>
|
||||
<th>客户姓名</th>
|
||||
<th>客户手机号</th>
|
||||
<th>客户名称</th>
|
||||
<th>客户手机</th>
|
||||
<th>订单状态</th>
|
||||
<th>订单来源</th>
|
||||
<th>销售总额</th>
|
||||
<th>销售金额</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -47,14 +57,19 @@
|
||||
<td><span class="status">{{ row.status }}</span></td>
|
||||
<td>{{ row.source }}</td>
|
||||
<td>{{ row.amount }}</td>
|
||||
<td>
|
||||
<RouterLink v-if="row.orderId" class="detail-link" :to="`/orders/${row.orderId}`">查看详情</RouterLink>
|
||||
<span v-else class="detail-link disabled-link">演示详情</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading" class="pagination">
|
||||
<button>上一页</button>
|
||||
<span>第 1 页 / 共 1 页</span>
|
||||
<button>下一页</button>
|
||||
<button disabled>上一页</button>
|
||||
<span>当前展示第一页,后续接入真实翻页交互</span>
|
||||
<button disabled>下一页</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@ -66,6 +81,7 @@ import { fetchOrdersPage } from "../mockApi";
|
||||
import { salesStore } from "../store";
|
||||
|
||||
const loading = ref(true);
|
||||
const isMock = ref(false);
|
||||
const orderFilters = ref([]);
|
||||
const orderActions = ref([]);
|
||||
const orderRows = ref([]);
|
||||
@ -76,6 +92,7 @@ onMounted(async () => {
|
||||
orderFilters.value = data.orderFilters;
|
||||
orderActions.value = data.orderActions;
|
||||
orderRows.value = data.orderRows;
|
||||
isMock.value = Boolean(data.isMock);
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
@ -83,13 +100,17 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
.page { background: #fff; border-radius: 8px; padding: 20px; }
|
||||
h2 { margin: 0 0 12px; }
|
||||
.notice { margin: 0 0 12px; color: #92400e; }
|
||||
.loading { margin: 0; color: #6b7280; }
|
||||
.block + .block { margin-top: 20px; }
|
||||
h3 { margin: 0 0 10px; font-size: 16px; }
|
||||
.header-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 10px; }
|
||||
h3 { margin: 0; font-size: 16px; }
|
||||
.mode-tag { padding: 6px 10px; border-radius: 999px; background: #dcfce7; color: #166534; font-size: 12px; }
|
||||
.mode-tag.fallback { background: #fef3c7; color: #92400e; }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.filter { display: grid; gap: 6px; min-width: 180px; }
|
||||
.filter span { color: #4b5563; font-size: 13px; }
|
||||
.filter input { border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 12px; background: #fff; }
|
||||
.filter input { border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 12px; background: #f9fafb; }
|
||||
.action-btn { border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 14px; background: #fff; }
|
||||
.action-btn:disabled { color: #9ca3af; background: #f3f4f6; }
|
||||
.empty-state { margin-top: 20px; padding: 24px; border: 1px dashed #d1d5db; border-radius: 8px; color: #6b7280; text-align: center; }
|
||||
@ -97,6 +118,9 @@ h3 { margin: 0 0 10px; font-size: 16px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #e5e7eb; }
|
||||
.status { display: inline-block; padding: 4px 10px; border-radius: 999px; background: #eef2ff; color: #4338ca; }
|
||||
.detail-link { color: #2563eb; text-decoration: none; }
|
||||
.disabled-link { color: #9ca3af; }
|
||||
.pagination { display: flex; justify-content: flex-end; align-items: center; gap: 12px; margin-top: 16px; }
|
||||
.pagination button { border: 1px solid #d1d5db; border-radius: 8px; padding: 8px 12px; background: #fff; }
|
||||
.pagination button:disabled { color: #9ca3af; background: #f3f4f6; }
|
||||
</style>
|
||||
|
||||
@ -92,3 +92,5 @@
|
||||
| 2026-05-14 | 已继续收紧订单/客户主线:补齐订单提交、审批、取消审批的状态合法性校验;客户列表已支持 `customer_name/mobile/customer_type/settlement_type/salesman_id` 筛选,并完成核心文件编译与导入校验。 |
|
||||
| 2026-05-14 | 已继续补齐订单详情与发厂主线:订单详情开始返回真实 `factory_name`;`supplier-text` 与 `supplier-text/confirm` 已接入真实 service/repository 路径,并在确认后将订单状态流转到 `pending_factory`。 |
|
||||
| 2026-05-14 | 已继续推进审批主线真实化:新增订单审批日志模型与仓储;`approve`、`cancel-approve` 会写入真实审批日志;订单详情 `approve_logs` 已开始返回真实数据库记录。 |
|
||||
| 2026-05-14 | 已继续推进审计查询主线:`/api/audit-logs` 已支持按 `biz_type/biz_id/operate_type/start_time/end_time/page_no/page_size` 查询,并接入真实订单审批日志数据源。 |
|
||||
| 2026-05-14 | 已开始补前端真实联调:`frontend/web-sales` 的订单列表与订单详情已改为优先请求真实后端接口,失败时再回退 mock;同时清理了该模块关键页面中的乱码展示文本。 |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user