501 lines
15 KiB
TypeScript
501 lines
15 KiB
TypeScript
/**
|
||
* Demo 模板页 Mock API(本地内存,不依赖后端)
|
||
* 供 views/demo/* 与 product-view-dialog 使用
|
||
*/
|
||
import { fetchUserList } from '@/api/admin'
|
||
import {
|
||
demoCategories,
|
||
demoOrders,
|
||
demoProducts,
|
||
demoRegions,
|
||
demoUnits
|
||
} from './mock-data'
|
||
import { DEFAULT_LIST_PAGE_SIZE } from '@/utils/table/tableConfig'
|
||
import request from '@/utils/http'
|
||
|
||
type PageParams = { page?: number; pageSize?: number; keyword?: string }
|
||
|
||
const delay = (ms = 120) => new Promise((resolve) => setTimeout(resolve, ms))
|
||
|
||
function paginate<T>(list: T[], page = 1, pageSize = DEFAULT_LIST_PAGE_SIZE) {
|
||
const p = Math.max(1, page)
|
||
const ps = Math.max(1, pageSize)
|
||
const start = (p - 1) * ps
|
||
const slice = list.slice(start, start + ps)
|
||
return {
|
||
list: slice,
|
||
total: list.length,
|
||
page: p,
|
||
pageSize: ps,
|
||
totalPages: Math.max(1, Math.ceil(list.length / ps))
|
||
}
|
||
}
|
||
|
||
function filterKeyword<T extends Record<string, any>>(
|
||
list: T[],
|
||
keys: string[],
|
||
keyword?: string
|
||
) {
|
||
const kw = (keyword || '').trim().toLowerCase()
|
||
if (!kw) return list
|
||
return list.filter((row) => keys.some((k) => String(row[k] ?? '').toLowerCase().includes(kw)))
|
||
}
|
||
|
||
const units = [...demoUnits]
|
||
const categories = [...demoCategories]
|
||
const products = [...demoProducts]
|
||
const orders = [...demoOrders]
|
||
|
||
function newId(prefix: string) {
|
||
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||
}
|
||
|
||
// ─── 物品单位 ─────────────────────────────────────────
|
||
|
||
export async function fetchItemUnitList(params?: PageParams) {
|
||
await delay()
|
||
const filtered = filterKeyword(units, ['unitName', 'unitCode'], params?.keyword)
|
||
return paginate(filtered, params?.page, params?.pageSize)
|
||
}
|
||
|
||
export async function fetchCreateItemUnit(data: {
|
||
unitCode: string
|
||
unitName: string
|
||
orderNum?: number
|
||
remark?: string
|
||
}) {
|
||
await delay()
|
||
const row: Api.ItemUnit.ItemUnitItem = {
|
||
id: newId('demo-unit'),
|
||
unitCode: data.unitCode,
|
||
unitName: data.unitName,
|
||
tenantNo: 'demo',
|
||
orderNum: data.orderNum ?? 0,
|
||
remark: data.remark || '',
|
||
createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||
updateTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||
}
|
||
units.unshift(row)
|
||
return row
|
||
}
|
||
|
||
export async function fetchUpdateItemUnit(
|
||
id: string,
|
||
data: Partial<{ unitCode: string; unitName: string; orderNum: number; remark: string }>
|
||
) {
|
||
await delay()
|
||
const idx = units.findIndex((u) => u.id === id)
|
||
if (idx >= 0) units[idx] = { ...units[idx], ...data }
|
||
return units[idx]
|
||
}
|
||
|
||
export async function fetchDeleteItemUnit(id: string) {
|
||
await delay()
|
||
const idx = units.findIndex((u) => u.id === id)
|
||
if (idx >= 0) units.splice(idx, 1)
|
||
return { success: true }
|
||
}
|
||
|
||
// ─── 分类 ───────────────────────────────────────────
|
||
|
||
export async function fetchCategoryList(params?: PageParams & { isActive?: boolean }) {
|
||
await delay()
|
||
let list = [...categories]
|
||
if (params?.isActive != null) list = list.filter((c) => c.isActive === params.isActive)
|
||
list = filterKeyword(list, ['name'], params?.keyword)
|
||
return paginate(list, params?.page, params?.pageSize)
|
||
}
|
||
|
||
export async function fetchCreateCategory(data: {
|
||
name: string
|
||
parentId?: string
|
||
sortOrder?: number
|
||
imageUrl?: string
|
||
}) {
|
||
await delay()
|
||
const row: Api.Category.CategoryItem = {
|
||
id: newId('demo-cat'),
|
||
name: data.name,
|
||
parentId: data.parentId || '0',
|
||
sortOrder: data.sortOrder ?? 0,
|
||
isActive: true,
|
||
imageUrl: data.imageUrl || '',
|
||
createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||
}
|
||
categories.push(row)
|
||
return row
|
||
}
|
||
|
||
export async function fetchUpdateCategory(id: string, data: Partial<Api.Category.CategoryItem>) {
|
||
await delay()
|
||
const idx = categories.findIndex((c) => c.id === id)
|
||
if (idx >= 0) categories[idx] = { ...categories[idx], ...data }
|
||
return categories[idx]
|
||
}
|
||
|
||
export async function fetchDeleteCategory(id: string) {
|
||
await delay()
|
||
const idx = categories.findIndex((c) => c.id === id)
|
||
if (idx >= 0) categories.splice(idx, 1)
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchUploadCategoryImage(_data: FormData) {
|
||
await delay()
|
||
return { fileUrl: '', fileName: 'demo.png' }
|
||
}
|
||
|
||
export async function fetchProductParameterSimple() {
|
||
await delay()
|
||
return { list: [] as Api.Product.ProductParameterSimple[], total: 0 }
|
||
}
|
||
|
||
export async function fetchCategoryParameters(_categoryId: string) {
|
||
await delay()
|
||
return { list: [] as Api.Product.CategoryParameter[], total: 0 }
|
||
}
|
||
|
||
export async function fetchUpdateCategoryParameters(_categoryId: string, _items: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
// ─── 商品单位(Unit 命名,商品页用) ─────────────────
|
||
|
||
export async function fetchUnitList(params?: PageParams) {
|
||
await delay()
|
||
const list: Api.Unit.UnitItem[] = units.map((u) => ({
|
||
id: u.id,
|
||
name: u.unitName,
|
||
code: u.unitCode,
|
||
isActive: true,
|
||
createAt: u.createTime
|
||
}))
|
||
const filtered = filterKeyword(list, ['name', 'code'], params?.keyword)
|
||
return paginate(filtered, params?.page, params?.pageSize)
|
||
}
|
||
|
||
// ─── 商品 ───────────────────────────────────────────
|
||
|
||
export async function fetchProductList(params?: PageParams & { categoryId?: string }) {
|
||
await delay()
|
||
let list = [...products]
|
||
if (params?.categoryId) list = list.filter((p) => p.categoryId === params.categoryId)
|
||
list = filterKeyword(list, ['name'], params?.keyword)
|
||
return paginate(list, params?.page, params?.pageSize)
|
||
}
|
||
|
||
export async function fetchProductDetail(id: string) {
|
||
await delay()
|
||
const base = products.find((p) => p.id === id)
|
||
if (!base) throw new Error('商品不存在')
|
||
return {
|
||
...base,
|
||
updateAt: base.createAt,
|
||
updateBy: 'demo',
|
||
skuCode: 'SKU-DEMO',
|
||
measureValue: 1,
|
||
detailContent: ''
|
||
} as Api.Product.ProductDetail & { skuCode?: string; measureValue?: number; detailContent?: string }
|
||
}
|
||
|
||
export async function fetchCreateProduct(data: Partial<Api.Product.ProductItem> & { name: string }) {
|
||
await delay()
|
||
const row: Api.Product.ProductItem = {
|
||
id: newId('demo-prod'),
|
||
name: data.name,
|
||
categoryId: data.categoryId,
|
||
categoryName: categories.find((c) => c.id === data.categoryId)?.name,
|
||
unitId: data.unitId,
|
||
unitName: units.find((u) => u.id === data.unitId)?.unitName,
|
||
description: data.description || '',
|
||
imageUrl: data.imageUrl || '',
|
||
isActive: data.isActive ?? true,
|
||
createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||
}
|
||
products.unshift(row)
|
||
return row
|
||
}
|
||
|
||
export async function fetchUpdateProduct(id: string, data: Partial<Api.Product.ProductItem>) {
|
||
await delay()
|
||
const idx = products.findIndex((p) => p.id === id)
|
||
if (idx >= 0) products[idx] = { ...products[idx], ...data }
|
||
return products[idx]
|
||
}
|
||
|
||
export async function fetchDeleteProduct(id: string) {
|
||
await delay()
|
||
const idx = products.findIndex((p) => p.id === id)
|
||
if (idx >= 0) products.splice(idx, 1)
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchProductBannerList(_productId: string) {
|
||
await delay()
|
||
return { list: [] as Api.Product.ProductBanner[], total: 0 }
|
||
}
|
||
|
||
export async function fetchCreateProductBanners(_productId: string, _items: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchUpdateProductBannerSort(_productId: string, _items: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchDeleteProductBanner(_id: string) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchProductDetailMediaList(_productId: string) {
|
||
await delay()
|
||
return { list: [] as Api.Product.ProductDetailMedia[], total: 0 }
|
||
}
|
||
|
||
export async function fetchCreateProductDetailMedia(_productId: string, _items: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchUpdateProductDetailMediaSort(_productId: string, _items: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchDeleteProductDetailMedia(_id: string) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchProductParameterValues(_productId: string) {
|
||
await delay()
|
||
return { list: [] as Api.Product.ProductParameterValue[], total: 0 }
|
||
}
|
||
|
||
export async function fetchSaveProductParameterValues(_productId: string, _values: unknown[]) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchProductRegionPrices(_productId: string) {
|
||
await delay()
|
||
return {
|
||
list: demoRegions.map((r) => ({
|
||
regionId: r.id,
|
||
regionName: r.name,
|
||
price: 100,
|
||
unitId: null,
|
||
unitName: null
|
||
}))
|
||
}
|
||
}
|
||
|
||
// ─── 区域 / 用户(订单页) ───────────────────────────
|
||
|
||
export async function fetchNotDeletedRegions() {
|
||
await delay()
|
||
return [...demoRegions]
|
||
}
|
||
|
||
export { fetchUserList }
|
||
|
||
// ─── business.ts 订单等 ─────────────────────────────
|
||
|
||
export async function fetchOrderList(params?: PageParams & Record<string, unknown>) {
|
||
await delay()
|
||
let list = [...orders]
|
||
list = filterKeyword(list, ['orderNo', 'clientName'], params?.keyword as string | undefined)
|
||
return paginate(list, params?.page, params?.pageSize)
|
||
}
|
||
|
||
export async function fetchOrderDetail(id: string) {
|
||
await delay()
|
||
const base = orders.find((o) => o.id === id)
|
||
if (!base) throw new Error('订单不存在')
|
||
return {
|
||
...base,
|
||
items: [],
|
||
remark: 'Demo 订单详情'
|
||
} as Api.Order.OrderDetail
|
||
}
|
||
|
||
export async function fetchAssignDelivery(_data: unknown) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchReviewCancel(_data: unknown) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchCancelOrder(_data: unknown) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchOrderItems(_orderId: string) {
|
||
await delay()
|
||
return { list: [] }
|
||
}
|
||
|
||
export async function fetchAdjustOrder(_data: unknown) {
|
||
await delay()
|
||
return { success: true }
|
||
}
|
||
|
||
export async function fetchOrderAdjusts(_orderId: string) {
|
||
await delay()
|
||
return { list: [] }
|
||
}
|
||
|
||
export async function fetchPriceVersionDetail(_id: string) {
|
||
await delay()
|
||
return { id: _id, name: 'Demo 价目表' }
|
||
}
|
||
|
||
export async function fetchUploadAttachment(_data: FormData) {
|
||
await delay()
|
||
return {
|
||
id: newId('att'),
|
||
fileName: 'demo.pdf',
|
||
fileUrl: '#',
|
||
fileSize: 0
|
||
}
|
||
}
|
||
|
||
export async function fetchUploadProductImage(_data: FormData) {
|
||
await delay()
|
||
return { fileUrl: '', fileName: 'demo.png' }
|
||
}
|
||
|
||
export async function fetchSystemConfig() {
|
||
await delay()
|
||
return {
|
||
orderCancelReasons: ['客户取消', '库存不足'],
|
||
paymentMethods: [
|
||
{ value: 'cash', label: '现金' },
|
||
{ value: 'transfer', label: '转账' }
|
||
]
|
||
}
|
||
}
|
||
|
||
export async function fetchAttachmentDownloadUrl(_data: { fileUrl: string; fileName?: string }) {
|
||
await delay()
|
||
return { downloadUrl: '#' }
|
||
}
|
||
|
||
|
||
const DEMO_PRODUCT_EXPORT_COLS = [
|
||
{ field: 'id', title: 'id' },
|
||
{ field: 'name', title: '商品名称' },
|
||
{ field: 'skuCode', title: 'SKU编码' },
|
||
{ field: 'categoryName', title: '分类' },
|
||
{ field: 'measureValue', title: '计量' },
|
||
{ field: 'unitName', title: '单位' },
|
||
{ field: 'imageUrl', title: '图片URL' },
|
||
{ field: 'isActive', title: '状态' },
|
||
{ field: 'createAt', title: '创建时间' }
|
||
]
|
||
|
||
const DEMO_CATEGORY_EXPORT_COLS = [
|
||
{ field: 'id', title: 'id' },
|
||
{ field: 'name', title: '分类名称' },
|
||
{ field: 'parentId', title: '父分类ID' },
|
||
{ field: 'parentName', title: '父分类名称' },
|
||
{ field: 'sortOrder', title: '排序' },
|
||
{ field: 'priceCoefficient', title: '价格系数(%)' },
|
||
{ field: 'isActive', title: '状态' },
|
||
{ field: 'imageUrl', title: '图片URL' }
|
||
]
|
||
|
||
function pickExportColumns(
|
||
all: { field: string; title: string }[],
|
||
visible?: string[]
|
||
) {
|
||
if (!visible?.length) return all
|
||
const set = new Set(visible)
|
||
const picked = all.filter((c) => set.has(c.field))
|
||
return picked.length ? picked : all
|
||
}
|
||
|
||
/** Demo 商品列表导出(Mock 筛行 + 后端 xlsx) */
|
||
export async function fetchExportDemoProducts(payload: Record<string, any> = {}) {
|
||
await delay()
|
||
let list = [...products]
|
||
if (payload.categoryId) list = list.filter((p) => p.categoryId === payload.categoryId)
|
||
if (payload.isActive === true || payload.isActive === false) {
|
||
list = list.filter((p) => p.isActive === payload.isActive)
|
||
}
|
||
list = filterKeyword(list, ['name', 'skuCode'], payload.keyword)
|
||
if (payload.ids?.length) {
|
||
const idSet = new Set(payload.ids.map(String))
|
||
list = list.filter((p) => idSet.has(String(p.id)))
|
||
}
|
||
const cols = pickExportColumns(DEMO_PRODUCT_EXPORT_COLS, payload.columns)
|
||
const rows = list.map((row) => ({
|
||
id: row.id,
|
||
name: row.name,
|
||
skuCode: (row as any).skuCode || 'SKU-DEMO',
|
||
categoryName: row.categoryName,
|
||
measureValue: (row as any).measureValue ?? 1,
|
||
unitName: row.unitName,
|
||
imageUrl: row.imageUrl,
|
||
isActive: row.isActive ? '启用' : '禁用',
|
||
createAt: row.createAt
|
||
}))
|
||
return request.post({
|
||
url: '/web/admin/demo/xlsx-export',
|
||
data: {
|
||
sheetTitle: '商品数据',
|
||
filenameStem: '商品数据',
|
||
columns: cols,
|
||
rows
|
||
},
|
||
responseType: 'blob'
|
||
})
|
||
}
|
||
|
||
/** Demo 分类列表导出(Mock 筛行 + 后端 xlsx) */
|
||
export async function fetchExportDemoCategories(payload: Record<string, any> = {}) {
|
||
await delay()
|
||
let list = [...categories]
|
||
if (payload.isActive === true || payload.isActive === false) {
|
||
list = list.filter((c) => c.isActive === payload.isActive)
|
||
}
|
||
list = filterKeyword(list, ['name'], payload.keyword)
|
||
if (payload.ids?.length) {
|
||
const idSet = new Set(payload.ids.map(String))
|
||
list = list.filter((c) => idSet.has(String(c.id)))
|
||
}
|
||
const idToName = new Map(list.map((c) => [c.id, c.name]))
|
||
// parent may be outside filtered set — use full categories map
|
||
const allNames = new Map(categories.map((c) => [c.id, c.name]))
|
||
const cols = pickExportColumns(DEMO_CATEGORY_EXPORT_COLS, payload.columns)
|
||
const rows = list.map((row) => ({
|
||
id: row.id,
|
||
name: row.name,
|
||
parentId: row.parentId || '',
|
||
parentName: row.parentId ? allNames.get(row.parentId) || '' : '',
|
||
sortOrder: row.sortOrder,
|
||
priceCoefficient: row.priceCoefficient ?? 0,
|
||
isActive: row.isActive ? '启用' : '禁用',
|
||
imageUrl: row.imageUrl || ''
|
||
}))
|
||
return request.post({
|
||
url: '/web/admin/demo/xlsx-export',
|
||
data: {
|
||
sheetTitle: '分类数据',
|
||
filenameStem: '分类数据',
|
||
columns: cols,
|
||
rows
|
||
},
|
||
responseType: 'blob'
|
||
})
|
||
}
|
||
|