first version

This commit is contained in:
liu
2026-07-23 20:41:25 +08:00
parent 00a0938a1b
commit 10cff754a4
211 changed files with 11577 additions and 842 deletions
+100
View File
@@ -0,0 +1,100 @@
import axios from 'axios';
/**
* 公共文件下载工具(blob 请求 + 触发浏览器保存)
*
* - 成功:解析响应头 Content-Disposition 的 filenameRFC 5987 `filename*=UTF-8''` 优先),兜底用 fallbackName
* - 失败兜底:后端业务错误也以 blob 返回(JSON),blob.type 为 application/json 时解析 msg 提示
* - 401:清 token 跳登录页(与 createAxios 行为一致)
*/
export async function downloadBlob(
url: string,
params: Record<string, unknown>,
fallbackName: string
): Promise<void> {
const token = localStorage.getItem('token');
let blob: Blob;
let disposition = '';
try {
const response = await axios.get(url, {
baseURL: import.meta.env.VITE_BASE_URL || '',
params,
responseType: 'blob',
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
blob = response.data as Blob;
disposition = (response.headers['content-disposition'] as string) || '';
} catch (err: any) {
const status = err?.response?.status;
const errBlob: Blob | undefined = err?.response?.data;
if (status === 401) {
window.$message?.error('您未登录,或者登录已经超时,请先登录!');
localStorage.removeItem('token');
localStorage.removeItem('auth-storage');
window.location.href = '/login';
return;
}
if (errBlob instanceof Blob) {
await showBlobError(errBlob);
return;
}
window.$message?.error('下载失败,请稍后重试');
return;
}
// 后端业务错误以 JSON blob 返回
if (blob.type.includes('application/json')) {
await showBlobError(blob);
return;
}
const filename = parseFilename(disposition) || fallbackName;
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(objectUrl);
}
/**
* 解析 blob 形式的错误响应并提示
*/
async function showBlobError(blob: Blob): Promise<void> {
try {
const body = JSON.parse(await blob.text());
window.$message?.error(body?.msg || '导出失败');
} catch {
window.$message?.error('导出失败');
}
}
/**
* 从 Content-Disposition 解析文件名:RFC 5987 filename*=UTF-8'' 优先,其次 filename="..."
*/
function parseFilename(disposition: string): string | null {
if (!disposition) {
return null;
}
const rfc5987 = disposition.match(/filename\*\s*=\s*(?:UTF-8|utf-8)''([^;]+)/i);
if (rfc5987?.[1]) {
try {
return decodeURIComponent(rfc5987[1].trim());
} catch {
return null;
}
}
const plain = disposition.match(/filename\s*=\s*"?([^";]+)"?/i);
if (plain?.[1]) {
try {
return decodeURIComponent(plain[1].trim());
} catch {
return plain[1].trim();
}
}
return null;
}
+10
View File
@@ -0,0 +1,10 @@
import createAxios from '@/utils/request';
import type ICustomerLevel from '@/domain/iCustomerLevel.ts';
/** 客户等级下拉选项(门店表单 / 价格矩阵用) */
export async function getLevelOptions() {
return createAxios<ICustomerLevel[]>({
url: '/customer/level/options',
method: 'get',
});
}
+26
View File
@@ -0,0 +1,26 @@
import createAxios from '@/utils/request';
export interface MiniUserBindParams {
/** 1门店 2供应商 */
type: number;
store_id?: number;
supplier_id?: number;
}
/** 绑定门店/供应商 */
export async function bindMiniUser(id: number, data: MiniUserBindParams) {
return createAxios({
url: `/customer/miniUser/${id}/bind`,
method: 'put',
data,
});
}
/** 启用/停用小程序用户 */
export async function toggleMiniUserStatus(id: number, status: number) {
return createAxios({
url: `/customer/miniUser/${id}/status`,
method: 'put',
data: { status },
});
}
+10
View File
@@ -0,0 +1,10 @@
import createAxios from '@/utils/request';
import type IStore from '@/domain/iStore.ts';
/** 门店下拉选项(小程序用户绑定、订单筛选用) */
export async function getStoreOptions() {
return createAxios<IStore[]>({
url: '/customer/store/options',
method: 'get',
});
}
+10
View File
@@ -0,0 +1,10 @@
import createAxios from '@/utils/request';
import type ISupplier from '@/domain/iSupplier.ts';
/** 供应商下拉选项 */
export async function getSupplierOptions() {
return createAxios<ISupplier[]>({
url: '/customer/supplier/options',
method: 'get',
});
}
+28
View File
@@ -0,0 +1,28 @@
import createAxios from '@/utils/request';
import type IStoreOrder from '@/domain/iStoreOrder.ts';
import type { IOrderSummaryRow } from '@/domain/iStoreOrder.ts';
/** 订单详情(头 + 明细) */
export async function getStoreOrder(id: number) {
return createAxios<IStoreOrder>({
url: `/order/store/${id}`,
method: 'get',
});
}
/** 订单状态流转(2配送中 3已完成 9取消) */
export async function updateOrderStatus(id: number, status: number) {
return createAxios({
url: `/order/store/${id}/status`,
method: 'put',
data: { status },
});
}
/** 待汇总预览(按商品聚合) */
export async function getOrderSummary() {
return createAxios<IOrderSummaryRow[]>({
url: '/order/store/summary',
method: 'get',
});
}
+18
View File
@@ -0,0 +1,18 @@
import createAxios from '@/utils/request';
import type IProductCategory from '@/domain/iProductCategory.ts';
/** 分类级联树(商品表单分类下拉、对账筛选用,仅启用分类) */
export async function getCategoryTree() {
return createAxios<IProductCategory[]>({
url: '/product/category/tree',
method: 'get',
});
}
/** 完整分类树(后台树表展示) */
export async function getCategoryTable() {
return createAxios<IProductCategory[]>({
url: '/product/category',
method: 'get',
});
}
+35
View File
@@ -0,0 +1,35 @@
import createAxios from '@/utils/request';
import type IProduct from '@/domain/iProduct.ts';
import type { IBatchPriceUpdate, IPriceMatrix } from '@/domain/iProduct.ts';
export interface PriceMatrixParams {
category_id?: number;
keyword?: string;
}
/** A2 价格矩阵:行=商品,列=启用等级,值=price(缺失 null */
export async function getPriceMatrix(params?: PriceMatrixParams) {
return createAxios<IPriceMatrix>({
url: '/product/goods/priceMatrix',
method: 'get',
params,
});
}
/** A2 批量调价(提交后给受影响门店生成价格变更通知) */
export async function batchPrice(updates: IBatchPriceUpdate[]) {
return createAxios({
url: '/product/goods/batchPrice',
method: 'put',
data: { updates },
});
}
/** 商品下拉选项(仅上架) */
export async function getProductOptions(keyword?: string) {
return createAxios<IProduct[]>({
url: '/product/goods/options',
method: 'get',
params: keyword ? { keyword } : {},
});
}
+70
View File
@@ -0,0 +1,70 @@
import createAxios from '@/utils/request';
import type {
ExportFormat,
IAllocationResult,
IPurchaseOrderItem,
PurchaseExportType,
} from '@/domain/iPurchaseOrder.ts';
import { downloadBlob } from '@/api/common/download.ts';
export interface PurchaseItemUpdateParams {
product_name?: string;
product_spec?: string;
price: number | string;
quantity: number | string;
weight?: number | string;
remark?: string;
}
/** C1 按门店订单汇总生成采购单 */
export async function generatePurchase(purchase_date: string) {
return createAxios<{ id: number; purchase_no: string }>({
url: '/purchase/order/generate',
method: 'post',
data: { purchase_date },
});
}
/** C2/C3 导出采购单(blob 下载) */
export async function exportPurchase(id: number, type: PurchaseExportType, format: ExportFormat) {
return downloadBlob(
`/purchase/order/${id}/export`,
{ type, format },
`采购单_${id}.${format}`
);
}
/** C4 修改采购明细(amount 由后端重算) */
export async function updatePurchaseItem(id: number, data: PurchaseItemUpdateParams) {
return createAxios<{ amount: string }>({
url: `/purchase/order/item/${id}`,
method: 'put',
data,
});
}
/** C5/C6 明细发送供应商 */
export async function sendPurchaseItem(id: number) {
return createAxios({
url: `/purchase/order/item/${id}/send`,
method: 'put',
});
}
/** D3 执行金额分摊 */
export async function allocatePurchase(id: number) {
return createAxios<{ count: number }>({
url: `/purchase/order/${id}/allocate`,
method: 'post',
});
}
/** 分摊结果(按门店 / 按商品聚合) */
export async function getAllocation(id: number) {
return createAxios<IAllocationResult>({
url: `/purchase/order/${id}/allocation`,
method: 'get',
});
}
export type { IPurchaseOrderItem };
+60
View File
@@ -0,0 +1,60 @@
import createAxios from '@/utils/request';
import type { IReconDiff } from '@/domain/iReconciliation.ts';
export interface ReconItemUpdateParams {
product_name?: string;
quantity?: number | string;
weight?: number | string;
publish_amount?: number | string;
actual_amount?: number | string;
}
/** 生成对账明细(按周期 + 品类 + 供应商拉取采购分摊数据) */
export async function buildRecon(id: number) {
return createAxios<{ count: number }>({
url: `/recon/list/${id}/build`,
method: 'post',
});
}
/** D4 修改对账明细(diff 与头汇总后端重算) */
export async function updateReconItem(id: number, data: ReconItemUpdateParams) {
return createAxios<{ diff_amount: string }>({
url: `/recon/item/${id}`,
method: 'put',
data,
});
}
/** D8 对账状态标记翻转 */
export async function toggleReconItem(id: number) {
return createAxios<{ is_reconciled: number }>({
url: `/recon/item/${id}/toggle`,
method: 'put',
});
}
/** D6 单品级门店备注 */
export async function remarkReconItem(id: number, store_remark: string) {
return createAxios({
url: `/recon/item/${id}/remark`,
method: 'put',
data: { store_remark },
});
}
/** D5 差额对比视图(按门店 / 按商品 + 合计) */
export async function getReconDiff(id: number) {
return createAxios<IReconDiff>({
url: `/recon/list/${id}/diff`,
method: 'get',
});
}
/** D9 生成结算表 */
export async function settleRecon(id: number) {
return createAxios<{ count: number }>({
url: `/recon/list/${id}/settle`,
method: 'post',
});
}
+11
View File
@@ -0,0 +1,11 @@
import type { ExportFormat } from '@/domain/iPurchaseOrder.ts';
import { downloadBlob } from '@/api/common/download.ts';
/** D10 结算表下载(blob,成功后后端回写 file_path 存档标记) */
export async function downloadSettlement(id: number, format: ExportFormat) {
return downloadBlob(
`/recon/settlement/${id}/download`,
{ format },
`结算表_${id}.${format}`
);
}