feat:配方配置使用mock数据源
parent
2e9fd85c92
commit
a22d95d134
@ -0,0 +1,280 @@
|
|||||||
|
export interface RecipeConfigVO {
|
||||||
|
id: number
|
||||||
|
recipeCode: string
|
||||||
|
recipeName: string
|
||||||
|
recipeType?: string
|
||||||
|
productId?: number
|
||||||
|
productName?: string
|
||||||
|
deviceId?: number
|
||||||
|
deviceName?: string
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecipePointDetailVO {
|
||||||
|
id: number
|
||||||
|
recipeId: number
|
||||||
|
attributeId?: number
|
||||||
|
pointName: string
|
||||||
|
pointType: string
|
||||||
|
dataType: string
|
||||||
|
dataUnit: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecipePointCandidateVO {
|
||||||
|
id: number
|
||||||
|
pointName: string
|
||||||
|
pointType: string
|
||||||
|
dataType: string
|
||||||
|
dataUnit: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageResult<T> = { list: T[]; total: number }
|
||||||
|
|
||||||
|
const STORAGE_KEYS = {
|
||||||
|
recipeList: 'mock:recipeConfig:recipes',
|
||||||
|
pointCandidates: 'mock:recipeConfig:pointCandidates',
|
||||||
|
recipePointRelation: 'mock:recipeConfig:recipePointRelation'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const safeParseJson = <T>(value: string | null): T | undefined => {
|
||||||
|
if (!value) return undefined
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as T
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFromStorage = <T>(key: string, fallback: T): T => {
|
||||||
|
const parsed = safeParseJson<T>(window.localStorage.getItem(key))
|
||||||
|
return parsed ?? fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveToStorage = (key: string, value: any) => {
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMockRecipes = (): RecipeConfigVO[] => {
|
||||||
|
return Array.from({ length: 18 }).map((_, idx) => {
|
||||||
|
const no = String(idx + 1).padStart(3, '0')
|
||||||
|
return {
|
||||||
|
id: idx + 1,
|
||||||
|
recipeCode: `RCP-${no}`,
|
||||||
|
recipeName: `配方-${no}`,
|
||||||
|
recipeType: idx % 2 === 0 ? '标准' : '自定义',
|
||||||
|
productId: undefined,
|
||||||
|
productName: idx % 3 === 0 ? '产品A' : idx % 3 === 1 ? '产品B' : '产品C',
|
||||||
|
deviceId: undefined,
|
||||||
|
deviceName: idx % 2 === 0 ? '设备1' : '设备2',
|
||||||
|
remark: idx % 4 === 0 ? '示例数据' : ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMockPointCandidates = (): RecipePointCandidateVO[] => {
|
||||||
|
const pointTypes = ['模拟量', '开关量', '计算量']
|
||||||
|
const dataTypes = ['int', 'float', 'string', 'bool']
|
||||||
|
const units = ['kg', 'm³', '℃', 'A', 'V', '']
|
||||||
|
return Array.from({ length: 60 }).map((_, idx) => {
|
||||||
|
const no = String(idx + 1).padStart(3, '0')
|
||||||
|
return {
|
||||||
|
id: idx + 1,
|
||||||
|
pointName: `点位-${no}`,
|
||||||
|
pointType: pointTypes[idx % pointTypes.length],
|
||||||
|
dataType: dataTypes[idx % dataTypes.length],
|
||||||
|
dataUnit: units[idx % units.length]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureMockSeeded = () => {
|
||||||
|
const recipeList = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, [])
|
||||||
|
if (!recipeList.length) saveToStorage(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
|
||||||
|
const pointCandidates = loadFromStorage<RecipePointCandidateVO[]>(STORAGE_KEYS.pointCandidates, [])
|
||||||
|
if (!pointCandidates.length) saveToStorage(STORAGE_KEYS.pointCandidates, buildMockPointCandidates())
|
||||||
|
|
||||||
|
const relation = loadFromStorage<Record<string, number[]>>(STORAGE_KEYS.recipePointRelation, {})
|
||||||
|
if (!Object.keys(relation).length) saveToStorage(STORAGE_KEYS.recipePointRelation, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
const paginate = <T>(items: T[], pageNo: number, pageSize: number): PageResult<T> => {
|
||||||
|
const safePageNo = Math.max(1, Number(pageNo) || 1)
|
||||||
|
const safePageSize = Math.max(1, Number(pageSize) || 10)
|
||||||
|
const start = (safePageNo - 1) * safePageSize
|
||||||
|
return {
|
||||||
|
total: items.length,
|
||||||
|
list: items.slice(start, start + safePageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contains = (value: string | undefined, keyword: string | undefined) => {
|
||||||
|
const v = (value ?? '').toLowerCase()
|
||||||
|
const k = (keyword ?? '').trim().toLowerCase()
|
||||||
|
if (!k) return true
|
||||||
|
return v.includes(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RecipeConfigApi = {
|
||||||
|
getRecipeConfigPage: async (params: any) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const recipeCode = params?.recipeCode
|
||||||
|
const recipeName = params?.recipeName
|
||||||
|
const productName = params?.productName
|
||||||
|
const pageNo = params?.pageNo
|
||||||
|
const pageSize = params?.pageSize
|
||||||
|
const list = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
const filtered = list.filter((item) => {
|
||||||
|
return (
|
||||||
|
contains(item.recipeCode, recipeCode) &&
|
||||||
|
contains(item.recipeName, recipeName) &&
|
||||||
|
contains(item.productName, productName)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return paginate(filtered, pageNo, pageSize)
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecipeConfig: async (data: Partial<RecipeConfigVO>) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const list = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
const maxId = list.reduce((acc, cur) => Math.max(acc, cur.id), 0)
|
||||||
|
const nextId = maxId + 1
|
||||||
|
const newItem: RecipeConfigVO = {
|
||||||
|
id: nextId,
|
||||||
|
recipeCode: data.recipeCode ?? '',
|
||||||
|
recipeName: data.recipeName ?? '',
|
||||||
|
recipeType: data.recipeType,
|
||||||
|
productId: data.productId,
|
||||||
|
productName: data.productName,
|
||||||
|
deviceId: data.deviceId,
|
||||||
|
deviceName: data.deviceName,
|
||||||
|
remark: data.remark
|
||||||
|
}
|
||||||
|
saveToStorage(STORAGE_KEYS.recipeList, [newItem, ...list])
|
||||||
|
return nextId
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecipeConfig: async (data: Partial<RecipeConfigVO>) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
if (!data.id) return true
|
||||||
|
const list = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
const next = list.map((item) => {
|
||||||
|
if (item.id !== data.id) return item
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
recipeCode: data.recipeCode ?? item.recipeCode,
|
||||||
|
recipeName: data.recipeName ?? item.recipeName,
|
||||||
|
recipeType: data.recipeType ?? item.recipeType,
|
||||||
|
productId: data.productId ?? item.productId,
|
||||||
|
productName: data.productName ?? item.productName,
|
||||||
|
deviceId: data.deviceId ?? item.deviceId,
|
||||||
|
deviceName: data.deviceName ?? item.deviceName,
|
||||||
|
remark: data.remark ?? item.remark
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveToStorage(STORAGE_KEYS.recipeList, next)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecipeConfig: async (id: number) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const list = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
saveToStorage(
|
||||||
|
STORAGE_KEYS.recipeList,
|
||||||
|
list.filter((item) => item.id !== id)
|
||||||
|
)
|
||||||
|
|
||||||
|
const relation = loadFromStorage<Record<string, number[]>>(STORAGE_KEYS.recipePointRelation, {})
|
||||||
|
delete relation[String(id)]
|
||||||
|
saveToStorage(STORAGE_KEYS.recipePointRelation, relation)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
exportRecipeConfig: async (params: any) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const ids = String(params?.ids ?? '')
|
||||||
|
.split(',')
|
||||||
|
.map((v) => Number(v))
|
||||||
|
.filter((v) => !Number.isNaN(v))
|
||||||
|
const list = loadFromStorage<RecipeConfigVO[]>(STORAGE_KEYS.recipeList, buildMockRecipes())
|
||||||
|
const exportList = ids.length ? list.filter((item) => ids.includes(item.id)) : list
|
||||||
|
const header = ['配方编码', '配方名称', '配方类型', '关联产品', '关联设备', '备注']
|
||||||
|
const rows = exportList.map((item) => [
|
||||||
|
item.recipeCode,
|
||||||
|
item.recipeName,
|
||||||
|
item.recipeType ?? '',
|
||||||
|
item.productName ?? '',
|
||||||
|
item.deviceName ?? '',
|
||||||
|
item.remark ?? ''
|
||||||
|
])
|
||||||
|
const csv = [header, ...rows].map((r) => r.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(',')).join('\n')
|
||||||
|
return new Blob([csv], { type: 'text/csv;charset=utf-8' })
|
||||||
|
},
|
||||||
|
|
||||||
|
getPointCandidatePage: async (params: any): Promise<PageResult<RecipePointCandidateVO>> => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(80)
|
||||||
|
const keyword = params?.keyword
|
||||||
|
const pageNo = params?.pageNo
|
||||||
|
const pageSize = params?.pageSize
|
||||||
|
const list = loadFromStorage<RecipePointCandidateVO[]>(
|
||||||
|
STORAGE_KEYS.pointCandidates,
|
||||||
|
buildMockPointCandidates()
|
||||||
|
)
|
||||||
|
const filtered = keyword
|
||||||
|
? list.filter((item) => contains(item.pointName, keyword))
|
||||||
|
: list
|
||||||
|
return paginate(filtered, pageNo, pageSize)
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecipePointDetailPage: async (params: any) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const recipeId = Number(params?.recipeId)
|
||||||
|
const pageNo = params?.pageNo
|
||||||
|
const pageSize = params?.pageSize
|
||||||
|
if (!recipeId) return { list: [], total: 0 }
|
||||||
|
|
||||||
|
const relation = loadFromStorage<Record<string, number[]>>(STORAGE_KEYS.recipePointRelation, {})
|
||||||
|
const selectedIds = relation[String(recipeId)] ?? []
|
||||||
|
const candidates = loadFromStorage<RecipePointCandidateVO[]>(
|
||||||
|
STORAGE_KEYS.pointCandidates,
|
||||||
|
buildMockPointCandidates()
|
||||||
|
)
|
||||||
|
const map = candidates.reduce((acc, cur) => {
|
||||||
|
acc[cur.id] = cur
|
||||||
|
return acc
|
||||||
|
}, {} as Record<number, RecipePointCandidateVO>)
|
||||||
|
const detailAll: RecipePointDetailVO[] = selectedIds
|
||||||
|
.map((id) => map[id])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
recipeId,
|
||||||
|
attributeId: item.id,
|
||||||
|
pointName: item.pointName,
|
||||||
|
pointType: item.pointType,
|
||||||
|
dataType: item.dataType,
|
||||||
|
dataUnit: item.dataUnit
|
||||||
|
}))
|
||||||
|
return paginate(detailAll, pageNo, pageSize)
|
||||||
|
},
|
||||||
|
|
||||||
|
saveRecipePointConfig: async (data: { recipeId: number; attributeIds: number[] }) => {
|
||||||
|
ensureMockSeeded()
|
||||||
|
await sleep(120)
|
||||||
|
const relation = loadFromStorage<Record<string, number[]>>(STORAGE_KEYS.recipePointRelation, {})
|
||||||
|
relation[String(data.recipeId)] = Array.from(new Set(data.attributeIds ?? [])).filter((v) => {
|
||||||
|
return typeof v === 'number' && !Number.isNaN(v)
|
||||||
|
})
|
||||||
|
saveToStorage(STORAGE_KEYS.recipePointRelation, relation)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue