first version
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* 公共文件下载工具(blob 请求 + 触发浏览器保存)
|
||||
*
|
||||
* - 成功:解析响应头 Content-Disposition 的 filename(RFC 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;
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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 } : {},
|
||||
});
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user