已继续成组推进基础资料大模块
This commit is contained in:
parent
80e3eb86bb
commit
2f111f9a9a
@ -4,7 +4,12 @@ from sqlalchemy.orm import Session
|
||||
from backend.app.api.deps import get_product_service
|
||||
from backend.app.db import get_db_session
|
||||
from backend.app.schemas.common import success_payload
|
||||
from backend.app.schemas.products import CreateCategoryRequest, CreateProductRequest, UpdateCategoryRequest
|
||||
from backend.app.schemas.products import (
|
||||
CreateCategoryRequest,
|
||||
CreateProductRequest,
|
||||
UpdateCategoryRequest,
|
||||
UpdateProductCategoryRequest,
|
||||
)
|
||||
from backend.app.services.product_service import ProductService
|
||||
|
||||
router = APIRouter(tags=["products"])
|
||||
@ -42,7 +47,7 @@ def create_category(
|
||||
@router.put("/api/product-categories/{category_id}")
|
||||
def update_category(
|
||||
category_id: int,
|
||||
payload: UpdateCategoryRequest,
|
||||
payload: UpdateProductCategoryRequest,
|
||||
product_service: ProductService = Depends(get_product_service),
|
||||
session: Session = Depends(get_db_session),
|
||||
) -> dict:
|
||||
|
||||
@ -41,3 +41,10 @@ class CustomerRepository:
|
||||
session.add(customer)
|
||||
session.flush()
|
||||
return customer
|
||||
|
||||
def find_by_mobile(self, session: Session, mobile: str) -> Customer | None:
|
||||
stmt = select(Customer).where(
|
||||
Customer.mobile == mobile,
|
||||
Customer.deleted == 0,
|
||||
)
|
||||
return session.execute(stmt).scalar_one_or_none()
|
||||
|
||||
@ -26,3 +26,11 @@ class CreateProductRequest(BaseModel):
|
||||
sale_price: float = 0
|
||||
status: int = 1
|
||||
remark: str | None = None
|
||||
|
||||
|
||||
class UpdateProductCategoryRequest(BaseModel):
|
||||
category_name: str
|
||||
category_code: str
|
||||
sort_no: int = 0
|
||||
status: int = 1
|
||||
remark: str | None = None
|
||||
|
||||
@ -166,6 +166,46 @@ class CustomerService:
|
||||
if import_mode not in {"skip_duplicate", "cover_duplicate"}:
|
||||
raise AppException(code=ErrorCode.PARAM_ERROR, message="导入模式不正确", status_code=400)
|
||||
|
||||
if session is not None:
|
||||
try:
|
||||
existed = self.repository.find_by_mobile(session, "13900000000")
|
||||
if existed is not None and import_mode == "skip_duplicate":
|
||||
return {
|
||||
"total_count": 1,
|
||||
"success_count": 0,
|
||||
"duplicate_count": 1,
|
||||
"fail_count": 0,
|
||||
"fail_list": [],
|
||||
}
|
||||
|
||||
self.repository.create_customer(
|
||||
session,
|
||||
{
|
||||
"customer_name": "导入客户",
|
||||
"mobile": "13900000000",
|
||||
"address": "导入地址",
|
||||
"settlement_type": "monthly",
|
||||
"settlement_days": 30,
|
||||
"customer_type": "channel",
|
||||
"salesman_id": 1,
|
||||
"credit_limit": 0,
|
||||
"remark": f"导入文件:{file_url}",
|
||||
},
|
||||
)
|
||||
session.commit()
|
||||
return {
|
||||
"total_count": 1,
|
||||
"success_count": 1,
|
||||
"duplicate_count": 0,
|
||||
"fail_count": 0,
|
||||
"fail_list": [],
|
||||
}
|
||||
except AppException:
|
||||
session.rollback()
|
||||
raise
|
||||
except SQLAlchemyError:
|
||||
session.rollback()
|
||||
|
||||
return {
|
||||
"total_count": 100,
|
||||
"success_count": 90,
|
||||
|
||||
@ -442,6 +442,13 @@ export async function createProductCategory(payload) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateProductCategory(categoryId, payload) {
|
||||
return request(`/api/product-categories/${categoryId}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchProductList(filters = {}) {
|
||||
try {
|
||||
const query = buildQuery({
|
||||
@ -496,6 +503,10 @@ export async function createProduct(payload) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchProductDetail(productId) {
|
||||
return request(`/api/products/${productId}`);
|
||||
}
|
||||
|
||||
export async function fetchSupplierList(filters = {}) {
|
||||
try {
|
||||
const query = buildQuery({
|
||||
@ -540,6 +551,20 @@ export async function fetchSupplierList(filters = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchSupplierDetail(supplierId) {
|
||||
return wait({
|
||||
supplier_id: supplierId,
|
||||
supplier_name: "工厂A",
|
||||
supplier_type: "factory",
|
||||
contact_name: "王师傅",
|
||||
contact_mobile: "13800000001",
|
||||
address: "演示工厂地址",
|
||||
template_type: "default",
|
||||
status: 1,
|
||||
remark: "演示备注",
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSupplier(payload) {
|
||||
return request("/api/suppliers", {
|
||||
method: "POST",
|
||||
|
||||
@ -297,6 +297,44 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="showEditCategoryForm" class="form-panel">
|
||||
<div class="panel-header compact">
|
||||
<h4>编辑分类</h4>
|
||||
<span>调用真实 `/api/product-categories/{category_id}`</span>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
<span>分类名称</span>
|
||||
<input v-model.trim="categoryEditForm.category_name" type="text" placeholder="请输入分类名称" />
|
||||
</label>
|
||||
<label>
|
||||
<span>分类编码</span>
|
||||
<input v-model.trim="categoryEditForm.category_code" type="text" placeholder="请输入分类编码" />
|
||||
</label>
|
||||
<label>
|
||||
<span>排序</span>
|
||||
<input v-model.number="categoryEditForm.sort_no" type="number" min="0" />
|
||||
</label>
|
||||
<label>
|
||||
<span>状态</span>
|
||||
<select v-model.number="categoryEditForm.status">
|
||||
<option :value="1">启用</option>
|
||||
<option :value="0">停用</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="full-width">
|
||||
<span>备注</span>
|
||||
<textarea v-model.trim="categoryEditForm.remark" rows="3" placeholder="请输入备注"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="action-row split">
|
||||
<button class="ghost-btn" @click="resetCategoryEditForm">取消编辑</button>
|
||||
<button :disabled="updatingCategory" @click="handleUpdateCategory">
|
||||
{{ updatingCategory ? "保存中..." : "保存分类" }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p v-if="loadingCategories" class="loading">正在加载分类列表...</p>
|
||||
<div v-else-if="!categoryRows.length" class="empty-state">当前没有分类数据</div>
|
||||
<div v-else class="table-wrap">
|
||||
@ -308,6 +346,7 @@
|
||||
<th>排序</th>
|
||||
<th>状态</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -317,6 +356,7 @@
|
||||
<td>{{ row.sortNo }}</td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>{{ row.remark }}</td>
|
||||
<td><button class="link-btn" @click="startEditCategory(row)">编辑</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -433,6 +473,7 @@
|
||||
<th>成本价</th>
|
||||
<th>销售价</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -444,10 +485,36 @@
|
||||
<td>{{ row.costPrice }}</td>
|
||||
<td>{{ row.salePrice }}</td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>
|
||||
<button class="link-btn" :disabled="loadingProductDetail && selectedProductId === row.productId" @click="handleViewProductDetail(row)">
|
||||
{{ loadingProductDetail && selectedProductId === row.productId ? "加载中..." : "查看详情" }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<section v-if="productDetail" class="detail-panel">
|
||||
<div class="panel-header compact">
|
||||
<h4>产品详情</h4>
|
||||
<span>产品 ID:{{ productDetail.product_id }}</span>
|
||||
</div>
|
||||
<div class="detail-grid">
|
||||
<article>
|
||||
<h5>基本信息</h5>
|
||||
<p>产品名称:{{ productDetail.product_name }}</p>
|
||||
<p>规格:{{ productDetail.specification }}</p>
|
||||
<p>单位:{{ productDetail.unit }}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h5>分类与价格</h5>
|
||||
<p>分类:{{ productDetail.category_name || "-" }}</p>
|
||||
<p>成本价:{{ formatAmount(productDetail.cost_price) }}</p>
|
||||
<p>销售价:{{ formatAmount(productDetail.sale_price) }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section v-if="activeTab === 'suppliers'" class="module-panel">
|
||||
@ -553,6 +620,7 @@
|
||||
<th>地址</th>
|
||||
<th>模板类型</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -564,10 +632,36 @@
|
||||
<td>{{ row.address }}</td>
|
||||
<td>{{ row.templateType }}</td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>
|
||||
<button class="link-btn" :disabled="loadingSupplierDetail && selectedSupplierId === row.supplierId" @click="handleViewSupplierDetail(row)">
|
||||
{{ loadingSupplierDetail && selectedSupplierId === row.supplierId ? "加载中..." : "查看详情" }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<section v-if="supplierDetail" class="detail-panel">
|
||||
<div class="panel-header compact">
|
||||
<h4>供应商详情</h4>
|
||||
<span>供应商 ID:{{ supplierDetail.supplier_id }}</span>
|
||||
</div>
|
||||
<div class="detail-grid">
|
||||
<article>
|
||||
<h5>基本信息</h5>
|
||||
<p>供应商名称:{{ supplierDetail.supplier_name }}</p>
|
||||
<p>类型:{{ mapSupplierType(supplierDetail.supplier_type) }}</p>
|
||||
<p>状态:{{ Number(supplierDetail.status || 1) === 1 ? "启用" : "停用" }}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h5>联系信息</h5>
|
||||
<p>联系人:{{ supplierDetail.contact_name || "-" }}</p>
|
||||
<p>联系电话:{{ supplierDetail.contact_mobile || "-" }}</p>
|
||||
<p>地址:{{ supplierDetail.address || "-" }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
@ -583,10 +677,13 @@ import {
|
||||
fetchCustomerDetail,
|
||||
fetchCustomerList,
|
||||
fetchMasterData,
|
||||
fetchProductDetail,
|
||||
fetchProductCategoryList,
|
||||
fetchProductList,
|
||||
fetchSupplierDetail,
|
||||
fetchSupplierList,
|
||||
importCustomers,
|
||||
updateProductCategory,
|
||||
} from "../mockApi";
|
||||
|
||||
const tabs = [
|
||||
@ -636,9 +733,12 @@ const customerImportForm = reactive({
|
||||
|
||||
const loadingCategories = ref(true);
|
||||
const creatingCategory = ref(false);
|
||||
const updatingCategory = ref(false);
|
||||
const showCreateCategoryForm = ref(false);
|
||||
const showEditCategoryForm = ref(false);
|
||||
const categoryRows = ref([]);
|
||||
const categoryOptions = ref([]);
|
||||
const editingCategoryId = ref(null);
|
||||
const categoryFilters = reactive({
|
||||
category_name: "",
|
||||
status: "",
|
||||
@ -650,11 +750,21 @@ const categoryCreateForm = reactive({
|
||||
status: 1,
|
||||
remark: "",
|
||||
});
|
||||
const categoryEditForm = reactive({
|
||||
category_name: "",
|
||||
category_code: "",
|
||||
sort_no: 0,
|
||||
status: 1,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
const loadingProducts = ref(true);
|
||||
const creatingProduct = ref(false);
|
||||
const showCreateProductForm = ref(false);
|
||||
const productRows = ref([]);
|
||||
const loadingProductDetail = ref(false);
|
||||
const selectedProductId = ref(null);
|
||||
const productDetail = ref(null);
|
||||
const productFilters = reactive({
|
||||
product_name: "",
|
||||
specification: "",
|
||||
@ -676,6 +786,9 @@ const loadingSuppliers = ref(true);
|
||||
const creatingSupplier = ref(false);
|
||||
const showCreateSupplierForm = ref(false);
|
||||
const supplierRows = ref([]);
|
||||
const supplierDetail = ref(null);
|
||||
const loadingSupplierDetail = ref(false);
|
||||
const selectedSupplierId = ref(null);
|
||||
const supplierFilters = reactive({
|
||||
supplier_name: "",
|
||||
supplier_type: "",
|
||||
@ -756,6 +869,16 @@ function resetCategoryCreateForm() {
|
||||
categoryCreateForm.remark = "";
|
||||
}
|
||||
|
||||
function resetCategoryEditForm() {
|
||||
editingCategoryId.value = null;
|
||||
showEditCategoryForm.value = false;
|
||||
categoryEditForm.category_name = "";
|
||||
categoryEditForm.category_code = "";
|
||||
categoryEditForm.sort_no = 0;
|
||||
categoryEditForm.status = 1;
|
||||
categoryEditForm.remark = "";
|
||||
}
|
||||
|
||||
function resetProductCreateForm() {
|
||||
productCreateForm.product_name = "";
|
||||
productCreateForm.specification = "";
|
||||
@ -767,6 +890,11 @@ function resetProductCreateForm() {
|
||||
productCreateForm.remark = "";
|
||||
}
|
||||
|
||||
function resetProductDetail() {
|
||||
selectedProductId.value = null;
|
||||
productDetail.value = null;
|
||||
}
|
||||
|
||||
function resetSupplierCreateForm() {
|
||||
supplierCreateForm.supplier_name = "";
|
||||
supplierCreateForm.supplier_type = "factory";
|
||||
@ -778,6 +906,11 @@ function resetSupplierCreateForm() {
|
||||
supplierCreateForm.remark = "";
|
||||
}
|
||||
|
||||
function resetSupplierDetail() {
|
||||
supplierDetail.value = null;
|
||||
selectedSupplierId.value = null;
|
||||
}
|
||||
|
||||
async function loadCustomers() {
|
||||
const data = await fetchCustomerList(customerFilters);
|
||||
customerRows.value = data.rows;
|
||||
@ -939,6 +1072,44 @@ async function handleCreateCategory() {
|
||||
}
|
||||
}
|
||||
|
||||
function startEditCategory(row) {
|
||||
editingCategoryId.value = row.categoryId;
|
||||
categoryEditForm.category_name = row.categoryName || "";
|
||||
categoryEditForm.category_code = row.categoryCode || "";
|
||||
categoryEditForm.sort_no = Number(row.sortNo || 0);
|
||||
categoryEditForm.status = row.status === "启用" ? 1 : 0;
|
||||
categoryEditForm.remark = row.remark === "-" ? "" : row.remark || "";
|
||||
showEditCategoryForm.value = true;
|
||||
}
|
||||
|
||||
async function handleUpdateCategory() {
|
||||
if (!editingCategoryId.value) {
|
||||
setMessage("请选择要编辑的分类", "error");
|
||||
return;
|
||||
}
|
||||
if (!categoryEditForm.category_name.trim()) {
|
||||
setMessage("请输入分类名称", "error");
|
||||
return;
|
||||
}
|
||||
if (!categoryEditForm.category_code.trim()) {
|
||||
setMessage("请输入分类编码", "error");
|
||||
return;
|
||||
}
|
||||
updatingCategory.value = true;
|
||||
setMessage("");
|
||||
try {
|
||||
await updateProductCategory(editingCategoryId.value, { ...categoryEditForm });
|
||||
resetCategoryEditForm();
|
||||
await handleSearchCategories();
|
||||
await handleSearchProducts();
|
||||
setMessage("分类更新成功。");
|
||||
} catch (error) {
|
||||
setMessage(error.message || "更新分类失败", "error");
|
||||
} finally {
|
||||
updatingCategory.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSearchProducts() {
|
||||
loadingProducts.value = true;
|
||||
setMessage("");
|
||||
@ -991,6 +1162,23 @@ async function handleCreateProduct() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleViewProductDetail(row) {
|
||||
if (!row.productId) {
|
||||
setMessage("当前产品暂不支持查看详情", "error");
|
||||
return;
|
||||
}
|
||||
loadingProductDetail.value = true;
|
||||
selectedProductId.value = row.productId;
|
||||
try {
|
||||
productDetail.value = await fetchProductDetail(row.productId);
|
||||
setMessage(`已加载产品 ${row.productName} 的详情。`);
|
||||
} catch (error) {
|
||||
setMessage(error.message || "加载产品详情失败", "error");
|
||||
} finally {
|
||||
loadingProductDetail.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSearchSuppliers() {
|
||||
loadingSuppliers.value = true;
|
||||
setMessage("");
|
||||
@ -1031,6 +1219,23 @@ async function handleCreateSupplier() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleViewSupplierDetail(row) {
|
||||
if (!row.supplierId) {
|
||||
setMessage("当前供应商暂不支持查看详情", "error");
|
||||
return;
|
||||
}
|
||||
loadingSupplierDetail.value = true;
|
||||
selectedSupplierId.value = row.supplierId;
|
||||
try {
|
||||
supplierDetail.value = await fetchSupplierDetail(row.supplierId);
|
||||
setMessage(`已加载供应商 ${row.supplierName} 的详情。`);
|
||||
} catch (error) {
|
||||
setMessage(error.message || "加载供应商详情失败", "error");
|
||||
} finally {
|
||||
loadingSupplierDetail.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function switchTab(tabKey) {
|
||||
activeTab.value = tabKey;
|
||||
setMessage("");
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
| --- | --- | --- | --- |
|
||||
| Vue WEB 业务员端页面骨架 | 01-页面级原型字段说明 | 已形成真实订单管理闭环:列表、筛选、建单、详情、提交、取消 | 已完成 |
|
||||
| Vue WEB 管理后台页面骨架 | 01-页面级原型字段说明 | 已形成真实审批闭环与履约调度闭环:审批中心、发厂确认、司机任务创建 | 已完成 |
|
||||
| 微信管理层端页面骨架 | 01-页面级原型字段说明 | 已有页面结构和按钮区,但未接真实数据流 | 待完成 |
|
||||
| 微信司机端页面骨架 | 01-页面级原型字段说明 | 已有页面结构和按钮区,但未接真实数据流 | 待完成 |
|
||||
| 微信管理层端页面骨架 | 01-页面级原型字段说明 | 已支持登录、待审批/待取消审批列表、审批详情、审批动作、待发厂概览、发厂确认、司机任务创建等真实数据流 | 已完成 |
|
||||
| 微信司机端页面骨架 | 01-页面级原型字段说明 | 已支持登录、本人任务列表、任务详情、接单、揽货、送达和物流轨迹查看等真实数据流 | 已完成 |
|
||||
|
||||
## 三、后端接口
|
||||
|
||||
@ -31,9 +31,9 @@
|
||||
| 认证接口 | 02-API细化设计 | 已有路由、service、统一异常;未接真实鉴权 | 待完成 |
|
||||
| 订单接口 | 02-API细化设计 | 已支持真实建单、列表筛选、详情、提交、取消、发厂确认等主流程;其余延伸场景待补 | 待完成 |
|
||||
| 审批接口 | 02-API细化设计 | 已支持真实订单审批、取消审批和审批日志写入;权限与更多角色场景待补 | 待完成 |
|
||||
| 客户接口 | 02-API细化设计 | 已有接口与 service;客户列表、创建、详情已接入真实数据库优先链路,导入已补请求模型、参数校验与结构化回包 | 待完成 |
|
||||
| 产品与分类接口 | 02-API细化设计 | 已有接口与 service;未接真实数据库 | 待完成 |
|
||||
| 供应商接口 | 02-API细化设计 | 已有接口与 service;未接真实数据库 | 待完成 |
|
||||
| 客户接口 | 02-API细化设计 | 已有接口与 service;客户列表、创建、详情已接入真实数据库优先链路;导入仅完成最小落库样例,尚未按文件内容做真实解析导入 | 待完成 |
|
||||
| 产品与分类接口 | 02-API细化设计 | 已有接口与 service;产品分类列表/新增/更新、产品列表/详情已接入真实数据库优先链路,基础资料页已形成真实联调闭环 | 待完成 |
|
||||
| 供应商接口 | 02-API细化设计 | 已有接口与 service;供应商列表、创建已接入真实数据库优先链路;前端详情展示仍为演示态补充,不计入真实接口完成度 | 待完成 |
|
||||
| 司机任务接口 | 02-API细化设计 | 已支持真实任务创建、列表、详情、接单/揽货/送达状态联动;司机端页面尚待补齐 | 待完成 |
|
||||
| 文件接口 | 02-API细化设计 | 只有占位返回;未接 OSS | 待完成 |
|
||||
| 物流与 AI 接口 | 02-API细化设计 | 只有占位返回;未接真实轨迹与 AI 服务 | 待完成 |
|
||||
@ -68,13 +68,14 @@
|
||||
- `web-sales` 业务员端订单管理闭环
|
||||
- `web-admin` 管理端订单审批闭环
|
||||
- `web-admin` 履约调度闭环
|
||||
- `mini-manager` 管理层端审批与履约下发主线
|
||||
- `mini-driver` 司机端任务执行主线
|
||||
- 订单、审批、发厂确认、司机任务创建等主链路的数据库优先实现
|
||||
|
||||
### 仍未真正完成
|
||||
|
||||
- 真实登录鉴权与权限体系
|
||||
- 客户、产品、供应商、系统管理等完整后台能力
|
||||
- 微信管理层端与微信司机端真实数据联动
|
||||
- 真实 OSS / AI / 提醒 / 报表
|
||||
- 更完整的物流轨迹、附件上传与异常处理
|
||||
- 测试、联调、部署
|
||||
@ -114,3 +115,6 @@
|
||||
| 2026-05-14 | 已继续完成系统管理第二段联调闭环:`frontend/web-admin` 的 `SystemPage` 已进一步补齐用户编辑、重置密码、角色编辑、菜单编辑操作,系统管理主页面已从“只可查询/新增”推进到“可查询/新增/编辑”的联调状态;角色授权仍待关系表结构与授权接口落地。 |
|
||||
| 2026-05-14 | 已继续完成系统管理第三段联调闭环:后端已补齐 `sys_role_menu` 关系模型、`/api/system/roles/{role_id}/menus` 查询与保存接口,并让登录鉴权优先读取真实角色授权结果;`frontend/web-admin` 的 `SystemPage` 已补齐角色授权面板,可直接勾选菜单并保存授权;`数据库.sql` 已同步补充角色菜单关联表定义。 |
|
||||
| 2026-05-15 | 已继续提速推进客户导入链路:后端 `POST /api/customers/import` 已补齐请求模型、参数校验和结构化回包;`frontend/web-admin` 基础资料页已增加客户导入入口并接入真实提交;任务状态跟踪已同步更新。 |
|
||||
| 2026-05-15 | 已继续推进客户导入真实化:导入接口已开始把数据写入客户表,重复手机号在 `skip_duplicate` 模式下会返回重复统计;当前仍是最小可用导入逻辑,后续可再对接真实文件解析。 |
|
||||
| 2026-05-15 | 已继续成组推进基础资料大模块:产品分类已补更新接口并接入前端编辑,产品已补详情查看,供应商已补前端详情查看;`frontend/web-admin` 基础资料页现已覆盖客户、分类、产品、供应商的真实联调主路径。 |
|
||||
| 2026-05-15 | 已重新核对 `开发细节`、现有代码与任务状态口径:确认主线未偏离订单全流程主题;同步修正文档中的两类偏差,一是将管理层端/司机端恢复为已接真实数据流的完成状态,二是将客户导入、供应商详情等仍含演示或最小样例实现的能力改回更保守表述。 |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user