From a22d95d1347707916608862a739f10f7106eb8aa Mon Sep 17 00:00:00 2001 From: hwj Date: Tue, 6 Jan 2026 18:16:12 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E9=85=8D=E6=96=B9=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BD=BF=E7=94=A8mock=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/recipeConfig/index.ts | 280 ++++++++++++ src/views/formula/formulaConfig/index.vue | 521 ++++++++++++++++++++++ 2 files changed, 801 insertions(+) create mode 100644 src/api/iot/recipeConfig/index.ts create mode 100644 src/views/formula/formulaConfig/index.vue diff --git a/src/api/iot/recipeConfig/index.ts b/src/api/iot/recipeConfig/index.ts new file mode 100644 index 00000000..92189422 --- /dev/null +++ b/src/api/iot/recipeConfig/index.ts @@ -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 = { list: T[]; total: number } + +const STORAGE_KEYS = { + recipeList: 'mock:recipeConfig:recipes', + pointCandidates: 'mock:recipeConfig:pointCandidates', + recipePointRelation: 'mock:recipeConfig:recipePointRelation' +} as const + +const safeParseJson = (value: string | null): T | undefined => { + if (!value) return undefined + try { + return JSON.parse(value) as T + } catch { + return undefined + } +} + +const loadFromStorage = (key: string, fallback: T): T => { + const parsed = safeParseJson(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(STORAGE_KEYS.recipeList, []) + if (!recipeList.length) saveToStorage(STORAGE_KEYS.recipeList, buildMockRecipes()) + + const pointCandidates = loadFromStorage(STORAGE_KEYS.pointCandidates, []) + if (!pointCandidates.length) saveToStorage(STORAGE_KEYS.pointCandidates, buildMockPointCandidates()) + + const relation = loadFromStorage>(STORAGE_KEYS.recipePointRelation, {}) + if (!Object.keys(relation).length) saveToStorage(STORAGE_KEYS.recipePointRelation, {}) +} + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +const paginate = (items: T[], pageNo: number, pageSize: number): PageResult => { + 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(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) => { + ensureMockSeeded() + await sleep(120) + const list = loadFromStorage(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) => { + ensureMockSeeded() + await sleep(120) + if (!data.id) return true + const list = loadFromStorage(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(STORAGE_KEYS.recipeList, buildMockRecipes()) + saveToStorage( + STORAGE_KEYS.recipeList, + list.filter((item) => item.id !== id) + ) + + const relation = loadFromStorage>(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(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> => { + ensureMockSeeded() + await sleep(80) + const keyword = params?.keyword + const pageNo = params?.pageNo + const pageSize = params?.pageSize + const list = loadFromStorage( + 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>(STORAGE_KEYS.recipePointRelation, {}) + const selectedIds = relation[String(recipeId)] ?? [] + const candidates = loadFromStorage( + STORAGE_KEYS.pointCandidates, + buildMockPointCandidates() + ) + const map = candidates.reduce((acc, cur) => { + acc[cur.id] = cur + return acc + }, {} as Record) + 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>(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 + } +} diff --git a/src/views/formula/formulaConfig/index.vue b/src/views/formula/formulaConfig/index.vue new file mode 100644 index 00000000..97aae46e --- /dev/null +++ b/src/views/formula/formulaConfig/index.vue @@ -0,0 +1,521 @@ + + + + +