Merge remote-tracking branch 'origin/dev_NEW_pro' into dev_鹤壁_强信宇_pro
# Conflicts:
# multiple/assets/favicon/QXYfavicon.ico
# multiple/config.json
| | |
| | | }, |
| | | "BTYX": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ²³å帮太ä¼éè¿åºå£æéå
¬å¸", |
| | | "VITE_BASE_API": "http://127.0.0.1:9001", |
| | | "VITE_JAVA_API": "http://127.0.0.1:9000" |
| | | "VITE_APP_TITLE": "æ²³å帮太ä¼éé£åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://1.15.17.182:9056", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9057" |
| | | }, |
| | | "logo": "logo/BTYXLogo.png", |
| | | "favicon": "favicon/BTYXfavicon.ico" |
| | |
| | | }, |
| | | "logo": "logo/ZXZNLogo.png", |
| | | "favicon": "favicon/ZXZNfavicon.ico" |
| | | }, |
| | | "QXY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "强信å®çµå¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.134.154.10:9001", |
| | | "VITE_JAVA_API": "http://36.134.154.10:9000" |
| | | }, |
| | | "logo": "logo/QXYLogo.png", |
| | | "favicon": "favicon/QXYfavicon.ico" |
| | | }, |
| | | "HYZC": { |
| | | "env": { |
| | |
| | | "logo": "logo/DYKJLogo.png", |
| | | "favicon": "favicon/DYKJfavicon.ico" |
| | | }, |
| | | "ZQSY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ³½æ·å®ä¸", |
| | | "VITE_BASE_API": "http://36.213.128.159:9000", |
| | | "VITE_JAVA_API": "http://36.213.128.159:9001" |
| | | }, |
| | | "logo": "logo/ZQSYLogo.png", |
| | | "favicon": "favicon/ZQSYfavicon.ico" |
| | | }, |
| | | "JXJH": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æµå¿æ±æµ·æ°´æ³¥å¶åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.201.20:9000", |
| | | "VITE_JAVA_API": "http://36.139.201.20:9001" |
| | | }, |
| | | "logo": "logo/JXJHLogo.png", |
| | | "favicon": "favicon/JXJHfavicon.ico" |
| | | }, |
| | | "YTJZ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "è±«æ³°å»ºçæææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.201.181:9000", |
| | | "VITE_JAVA_API": "http://36.139.201.181:9001" |
| | | }, |
| | | "logo": "logo/YTJZLogo.png", |
| | | "favicon": "favicon/YTJZfavicon.ico" |
| | | }, |
| | | "HYLQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "èªé¸è·¯æ¡¥å·¥ç¨æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.202.111:9000", |
| | | "VITE_JAVA_API": "http://36.139.202.111:9001" |
| | | }, |
| | | "logo": "logo/HYLQLogo.png", |
| | | "favicon": "favicon/HYLQfavicon.ico" |
| | | }, |
| | | "QXY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "强信å®çµå¨äºä¸»æº", |
| | | "VITE_BASE_API": "http://36.134.154.10:9000", |
| | | "VITE_JAVA_API": "http://36.134.154.10:9001" |
| | | }, |
| | | "logo": "logo/QXYLogo.png", |
| | | "favicon": "favicon/QXYfavicon.ico" |
| | | }, |
| | | "HYJC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ææ´å»ºæ", |
| | | "VITE_BASE_API": "http://36.138.94.178:9000", |
| | | "VITE_JAVA_API": "http://36.138.94.178:9001" |
| | | }, |
| | | "logo": "logo/HYJCLogo.png", |
| | | "favicon": "favicon/HYJCfavicon.ico" |
| | | }, |
| | | "JHY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å±±è¥¿çæ¦ç¤¾å¿æååé£åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://223.15.233.27:9001", |
| | | "VITE_JAVA_API": "http://223.15.233.27:9002" |
| | | }, |
| | | "logo": "logo/JHYLogo.png", |
| | | "favicon": "favicon/JHYfavicon.ico" |
| | | }, |
| | | "XCDQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ææ¨çµå¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9001", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9002" |
| | | }, |
| | | "logo": "logo/XCDQLogo.png", |
| | | "favicon": "favicon/XCDQfavicon.ico" |
| | | }, |
| | | "logo": "/src/assets/logo/logo.png", |
| | | "favicon": "/public/favicon.ico" |
| | | } |
| | |
| | | import fs from 'fs/promises'; |
| | | import fsSync from 'fs'; |
| | | import path from 'path'; |
| | | import { fileURLToPath } from 'url'; |
| | | import fs from "fs/promises"; |
| | | import fsSync from "fs"; |
| | | import path from "path"; |
| | | import { fileURLToPath } from "url"; |
| | | import { execSync } from "child_process"; |
| | | |
| | | // è·å __dirname |
| | | const __filename = fileURLToPath(import.meta.url); |
| | | const __dirname = path.dirname(__filename); |
| | | |
| | | // 读å JSON é
ç½® |
| | | const data = await fs.readFile(path.join(__dirname, 'config.json'), 'utf-8'); |
| | | const data = await fs.readFile(path.join(__dirname, "config.json"), "utf-8"); |
| | | const config = JSON.parse(data); |
| | | |
| | | // 项ç®è·¯å¾ |
| | | const rootPath = path.resolve(__dirname, '..'); |
| | | const resourcePath = path.join(rootPath, 'multiple', 'assets'); |
| | | const replacePath = path.join(rootPath, 'replace'); |
| | | const rootPath = path.resolve(__dirname, ".."); |
| | | const resourcePath = path.join(rootPath, "multiple", "assets"); |
| | | const replacePath = path.join(rootPath, "replace"); |
| | | const envFilePath = path.join(rootPath, ".env.production.local"); |
| | | |
| | | // è·åå½ä»¤è¡åæ° |
| | | const params = parseArgs(process.argv); |
| | | const company = params["company"] ?? "default"; |
| | | const company = resolveCompany(params); |
| | | const companyMap = config[company]; |
| | | |
| | | const envFilePath = path.join(process.cwd(), '.env.production.local'); |
| | | if (!companyMap) { |
| | | const availableCompanies = Object.entries(config) |
| | | .filter(([, value]) => value && typeof value === "object" && value.env) |
| | | .map(([key]) => key) |
| | | .sort(); |
| | | throw new Error( |
| | | `æªç¥ company: "${company}"ãå¯éå¼: ${availableCompanies.join(", ")}` |
| | | ); |
| | | } |
| | | |
| | | console.log(`å½å company: ${company}`); |
| | | |
| | | async function copyFileWithOverwrite(src, dest) { |
| | | await fs.mkdir(path.dirname(dest), { recursive: true }); |
| | | if (fsSync.existsSync(dest)) { |
| | | try { |
| | | await fs.chmod(dest, 0o666); |
| | | } catch { |
| | | // Ignore chmod failure and try delete directly. |
| | | } |
| | | await fs.rm(dest, { force: true }); |
| | | await fs.mkdir(path.dirname(dest), { recursive: true }); |
| | | if (fsSync.existsSync(dest)) { |
| | | try { |
| | | await fs.chmod(dest, 0o666); |
| | | } catch { |
| | | // Ignore chmod failure and continue. |
| | | } |
| | | await fs.copyFile(src, dest); |
| | | await fs.rm(dest, { force: true }); |
| | | } |
| | | await fs.copyFile(src, dest); |
| | | } |
| | | |
| | | try { |
| | | // 1ï¸â£ çæ .env |
| | | console.log("=======çæ.env======="); |
| | | const envContent = Object.entries(companyMap.env) |
| | | .map(([key, value]) => `${key}='${value}'`) |
| | | .join('\n') + '\n'; |
| | | await fs.writeFile(envFilePath, envContent, 'utf-8'); |
| | | console.log("=======çæ.env======="); |
| | | const envContent = |
| | | Object.entries(companyMap.env) |
| | | .map(([key, value]) => `${key}='${value}'`) |
| | | .join("\n") + "\n"; |
| | | await fs.writeFile(envFilePath, envContent, "utf-8"); |
| | | |
| | | // 2ï¸â£ å¤ä»½åå§èµæºå¹¶æ¿æ¢ |
| | | console.log("=======ä¿®æ¹èµæº======="); |
| | | for (const [key, value] of Object.entries(companyMap)) { |
| | | if (key === 'env') continue; |
| | | console.log("=======ä¿®æ¹èµæº======="); |
| | | for (const [key] of Object.entries(companyMap)) { |
| | | if (key === "env") continue; |
| | | |
| | | const originFile = path.join(rootPath, config[key]); |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | const replaceFile = path.join(resourcePath, companyMap[key]); |
| | | const originFile = path.join(rootPath, config[key]); |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | const replaceFile = path.join(resourcePath, companyMap[key]); |
| | | |
| | | await copyFileWithOverwrite(originFile, backupFile); |
| | | await copyFileWithOverwrite(replaceFile, originFile); |
| | | } |
| | | await copyFileWithOverwrite(originFile, backupFile); |
| | | await copyFileWithOverwrite(replaceFile, originFile); |
| | | } |
| | | |
| | | console.log("=====å¼å§æå
======"); |
| | | execSync("vite build", { stdio: "inherit" }); |
| | | console.log("=====æå
宿======"); |
| | | console.log("=====å¼å§æå
====="); |
| | | const buildEnv = createBuildEnv(companyMap.env); |
| | | execSync("vite build", { stdio: "inherit", cwd: rootPath, env: buildEnv }); |
| | | console.log("=====æå
宿======"); |
| | | } finally { |
| | | console.log("=====æ¢å¤èµæº======"); |
| | | console.log("=====æ¢å¤èµæº======"); |
| | | |
| | | // å é¤ä¸´æ¶ .env æä»¶ |
| | | if (fsSync.existsSync(envFilePath)) { |
| | | await fs.unlink(envFilePath); |
| | | console.log(`ðï¸ å·²å é¤ ${envFilePath}`); |
| | | if (fsSync.existsSync(envFilePath)) { |
| | | await fs.unlink(envFilePath); |
| | | console.log(`ðï¸ å·²å é¤ ${envFilePath}`); |
| | | } |
| | | |
| | | if (fsSync.existsSync(replacePath)) { |
| | | for (const [key] of Object.entries(companyMap)) { |
| | | if (key === "env") continue; |
| | | |
| | | const originFile = path.join(rootPath, config[key]); |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | await copyFileWithOverwrite(backupFile, originFile); |
| | | } |
| | | |
| | | // æ¢å¤èµæºæä»¶ |
| | | if (fsSync.existsSync(replacePath)) { |
| | | for (const [key, value] of Object.entries(companyMap)) { |
| | | if (key === 'env') continue; |
| | | |
| | | const originFile = path.join(rootPath, config[key]); |
| | | const backupFile = path.join(replacePath, config[key]); |
| | | |
| | | await copyFileWithOverwrite(backupFile, originFile); |
| | | } |
| | | await fs.rm(replacePath, { recursive: true, force: true }); |
| | | console.log(`ðï¸ å·²å é¤ ${replacePath}`); |
| | | } |
| | | await fs.rm(replacePath, { recursive: true, force: true }); |
| | | console.log(`ðï¸ å·²å é¤ ${replacePath}`); |
| | | } |
| | | } |
| | | |
| | | // ç®åå½ä»¤è¡åæ°è§£æ |
| | | function parseArgs(argv) { |
| | | const params = {}; |
| | | for (const arg of argv.slice(2)) { |
| | | if (arg.startsWith('--')) { |
| | | const [key, value] = arg.slice(2).split('='); |
| | | params[key] = value ?? true; |
| | | } |
| | | const params = {}; |
| | | for (let index = 2; index < argv.length; index++) { |
| | | const arg = argv[index]; |
| | | if (!arg.startsWith("--")) continue; |
| | | |
| | | const normalized = arg.slice(2); |
| | | const equalIndex = normalized.indexOf("="); |
| | | if (equalIndex >= 0) { |
| | | const key = normalized.slice(0, equalIndex); |
| | | const value = normalized.slice(equalIndex + 1); |
| | | params[key] = value || true; |
| | | continue; |
| | | } |
| | | return params; |
| | | |
| | | const nextArg = argv[index + 1]; |
| | | if (nextArg && !nextArg.startsWith("--")) { |
| | | params[normalized] = nextArg; |
| | | index += 1; |
| | | continue; |
| | | } |
| | | |
| | | params[normalized] = true; |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | function resolveCompany(parsedParams) { |
| | | const fromArg = parseValue(parsedParams.company); |
| | | if (fromArg) return fromArg; |
| | | |
| | | const fromNpmConfig = parseValue(process.env.npm_config_company); |
| | | if (fromNpmConfig) return fromNpmConfig; |
| | | |
| | | const fromEnv = parseValue(process.env.COMPANY ?? process.env.company); |
| | | if (fromEnv) return fromEnv; |
| | | |
| | | return "default"; |
| | | } |
| | | |
| | | function parseValue(value) { |
| | | if (value == null || value === true) return undefined; |
| | | if (typeof value !== "string") return undefined; |
| | | const trimmed = value.trim(); |
| | | if (!trimmed) return undefined; |
| | | return trimmed.replace(/^["']|["']$/g, ""); |
| | | } |
| | | |
| | | function createBuildEnv(companyEnv) { |
| | | const env = { ...process.env }; |
| | | for (const key of Object.keys(env)) { |
| | | if (key.startsWith("VITE_")) { |
| | | delete env[key]; |
| | | } |
| | | } |
| | | return { |
| | | ...env, |
| | | ...companyEnv, |
| | | VITE_APP_ENV: "production", |
| | | }; |
| | | } |
| | |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc éªæ¶å®¡æ¹ |
| | | * @param {éªæ¶åæ°} data |
| | | * @returns |
| | | */ |
| | | export const repairAcceptance = (data) => { |
| | | return request({ |
| | | url: `/device/repair/acceptance`, |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryBatchNoQty = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/getBatchNoQty", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å建åºåè®°å½ |
| | | export const createStockInventory = (params) => { |
| | | return request({ |
| | |
| | | width: 0; |
| | | overflow: hidden; |
| | | visibility: hidden; |
| | | display: inline-block;
|
| | | }
|
| | | & > i {
|
| | | height: 0;
|
| | | width: 0;
|
| | | overflow: hidden;
|
| | | visibility: hidden;
|
| | | display: inline-block;
|
| | | display: inline-block; |
| | | } |
| | | } |
| | | } |
| | |
| | | import { generalAssistant } from './generalAssistant' |
| | | import { purchaseAssistant } from './purchaseAssistant' |
| | | import { productionAssistant } from './productionAssistant' |
| | | import { salesAssistant } from './salesAssistant' |
| | | |
| | | export { generalAssistant, purchaseAssistant } |
| | | export { generalAssistant, purchaseAssistant, productionAssistant, salesAssistant } |
| | | |
| | | export const builtInAssistants = [generalAssistant, purchaseAssistant] |
| | | export const assistantRegistry = { |
| | | general: generalAssistant, |
| | | sales: salesAssistant, |
| | | purchase: purchaseAssistant, |
| | | production: productionAssistant |
| | | } |
| | | |
| | | export const builtInAssistants = [generalAssistant, salesAssistant, purchaseAssistant, productionAssistant] |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { Operation } from '@element-plus/icons-vue' |
| | | |
| | | export const productionAssistant = { |
| | | key: 'production', |
| | | label: 'ç产å©ç', |
| | | title: 'ç产æºè½å©ç', |
| | | tooltip: 'ç产æºè½å©æ', |
| | | icon: Operation, |
| | | apiBase: '/manufacturing-ai', |
| | | storageKey: 'production_ai_chat_uuid', |
| | | placeholder: '请è¾å
¥ç产ç¸å
³é®é¢... (Enter åé, Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | description: 'æå¯ä»¥å´ç»ç产ç°åºã计åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤çæä¾æ¥è¯¢ãé¢è¦ãåæååç建议ã', |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ ç产ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥è¯¢æ¬æç产计å', |
| | | 'æ¥çæè¿10æ¡å·¥å', |
| | | 'æ¥è®¾å¤A-01çç»´ä¿®æ
åµ', |
| | | 'æ¥è´¨éä¸åæ ¼è®°å½', |
| | | 'æ¥ä½åºåç©æ', |
| | | 'æ¥è¿7天å¼å¸¸å¤ç', |
| | | 'çæå¶é é¢è¦çæ¿', |
| | | 'åææ¬æçäº§å®æçåå¼å¸¸ç', |
| | | 'ç»åºå·¥å龿å设å¤å¾
ä¿®çåç建议' |
| | | ] |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { TrendCharts } from '@element-plus/icons-vue' |
| | | |
| | | export const salesAssistant = { |
| | | key: 'sales', |
| | | label: 'éå®å©æ', |
| | | title: 'é宿ºè½å©æ', |
| | | tooltip: 'é宿ºè½å©æ', |
| | | icon: TrendCharts, |
| | | apiBase: '/sales-ai', |
| | | storageKey: 'sales_ai_chat_uuid', |
| | | placeholder: '请è¾å
¥éå®ç¸å
³é®é¢... (Enter åé / Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | description: 'æå¯ä»¥åå©ä½ æ¥è¯¢å®¢æ·æ¡£æ¡ãé宿¥ä»·ãéå®å°è´¦ãéå®éè´§ã客æ·å¾æ¥ãåè´§å°è´¦ï¼å¹¶éç¹åæå®¢æ·æµå¤±é£é©å忬¾/æ¥ä»·çç¥ã', |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ éå®ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥è¯¢ç§æµ·å®¢æ·æ¡£æ¡å10æ¡', |
| | | 'æ¥è¯¢å
¬æµ·å®¢æ·æ¡£æ¡', |
| | | 'æ¥è¯¢æ¬æé宿¥ä»·', |
| | | 'æ¥è¯¢æ¬æéå®å°è´¦', |
| | | 'æ¥è¯¢è¿30天éå®éè´§', |
| | | 'æ¥è¯¢è¿30天客æ·åæ¬¾å¾æ¥', |
| | | 'æ¥è¯¢æ¬æåè´§å°è´¦', |
| | | 'æ¥çé宿æ ç»è®¡', |
| | | '帮æåå®¢æ·æµå¤±é£é©åæï¼è¿30天ï¼å20æ¡', |
| | | 'çæåæ¬¾ä¸æ¥ä»·çç¥å»ºè®®ï¼ä¼å
é«é£é©å®¢æ·' |
| | | ] |
| | | } |
| | |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æåä¸å¨ç» --> |
| | | <div v-if="message.manufacturingData" class="manufacturing-card"> |
| | | <div class="manufacturing-card__title">{{ getManufacturingTypeLabel(message.type) }}</div> |
| | | |
| | | <div |
| | | v-if="message.manufacturingData.summaryEntries?.length || message.manufacturingData.coreMetrics?.length" |
| | | class="manufacturing-summary-grid" |
| | | > |
| | | <div |
| | | v-for="(entry, entryIndex) in message.manufacturingData.summaryEntries" |
| | | :key="`summary-${entry.key}-${entryIndex}`" |
| | | class="manufacturing-summary-item" |
| | | > |
| | | <span class="manufacturing-summary-label">{{ entry.label }}</span> |
| | | <strong class="manufacturing-summary-value">{{ entry.value }}</strong> |
| | | </div> |
| | | <div |
| | | v-for="(metric, metricIndex) in message.manufacturingData.coreMetrics" |
| | | :key="`core-${metric.key}-${metricIndex}`" |
| | | class="manufacturing-summary-item manufacturing-summary-item--core" |
| | | > |
| | | <span class="manufacturing-summary-label">{{ metric.label }}</span> |
| | | <strong class="manufacturing-summary-value">{{ metric.value }}</strong> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.manufacturingData.warningItems?.length" class="manufacturing-warning-list"> |
| | | <div |
| | | v-for="(warning, warningIndex) in message.manufacturingData.warningItems" |
| | | :key="`warning-${warning.title || warningIndex}`" |
| | | class="manufacturing-warning-item" |
| | | > |
| | | <div class="manufacturing-warning-item__head"> |
| | | <el-tag size="small" :type="getManufacturingWarningLevelType(warning.level)"> |
| | | {{ getManufacturingWarningLevelLabel(warning.level) }} |
| | | </el-tag> |
| | | <strong>{{ warning.title || `é¢è¦ ${warningIndex + 1}` }}</strong> |
| | | <span v-if="warning.count !== '' && warning.count !== null && warning.count !== undefined" class="manufacturing-warning-count"> |
| | | {{ warning.count }} |
| | | </span> |
| | | </div> |
| | | <p v-if="warning.detail" class="manufacturing-warning-detail">{{ warning.detail }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div |
| | | v-if="message.manufacturingData.listItems?.length && message.manufacturingData.columns?.length" |
| | | class="table-wrapper manufacturing-table-wrapper" |
| | | > |
| | | <el-table :data="message.manufacturingData.listItems" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in message.manufacturingData.columns" |
| | | :key="col" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="140" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="message.manufacturingData.actionCards?.length" class="manufacturing-action-list"> |
| | | <div |
| | | v-for="(card, cardIndex) in message.manufacturingData.actionCards" |
| | | :key="card.runtimeKey || `${card.code}-${card.targetApi}-${cardIndex}`" |
| | | class="manufacturing-action-card" |
| | | > |
| | | <div class="manufacturing-action-card__head"> |
| | | <strong>{{ card.name || `å¨ä½ ${cardIndex + 1}` }}</strong> |
| | | <el-tag size="small" type="info">{{ getNormalizedRequestMethod(card.method) }}</el-tag> |
| | | </div> |
| | | <div class="manufacturing-action-card__meta"> |
| | | <span>{{ card.code || '--' }}</span> |
| | | <span>{{ card.targetApi || '--' }}</span> |
| | | </div> |
| | | <p v-if="card.description" class="manufacturing-action-card__desc">{{ card.description }}</p> |
| | | <div v-if="card.requiredFields?.length" class="manufacturing-required-fields"> |
| | | <span>å¿
å¡«åæ®µ</span> |
| | | <el-tag |
| | | v-for="field in card.requiredFields" |
| | | :key="field" |
| | | size="small" |
| | | type="warning" |
| | | > |
| | | {{ getStructuredPathLabel(field) }} |
| | | </el-tag> |
| | | </div> |
| | | <el-input |
| | | v-model="card.payloadText" |
| | | type="textarea" |
| | | :rows="6" |
| | | resize="vertical" |
| | | :disabled="card.executing" |
| | | placeholder="请è¾å
¥ JSON 请æ±åæ°" |
| | | /> |
| | | <div class="manufacturing-action-footer"> |
| | | <span |
| | | v-if="card.executeResult" |
| | | :class="['manufacturing-action-result', card.executeError ? 'error' : 'success']" |
| | | > |
| | | {{ card.executeResult }} |
| | | </span> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | :loading="card.executing" |
| | | @click="executeManufacturingAction(message, card, cardIndex)" |
| | | > |
| | | 确认并æ§è¡ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.salesData" class="sales-structured-card"> |
| | | <div class="sales-structured-card__title">{{ getSalesTypeLabel(message.type) }}</div> |
| | | |
| | | <div v-if="message.salesData.summaryEntries?.length" class="sales-summary-grid"> |
| | | <div |
| | | v-for="(entry, entryIndex) in message.salesData.summaryEntries" |
| | | :key="`sales-summary-${entry.key}-${entryIndex}`" |
| | | class="sales-summary-item" |
| | | > |
| | | <span class="sales-summary-label">{{ entry.label }}</span> |
| | | <strong class="sales-summary-value">{{ entry.value }}</strong> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.type === 'sales_customer_churn_risk' && message.salesData.listItems?.length" class="sales-focus-list"> |
| | | <div |
| | | v-for="(item, itemIndex) in message.salesData.listItems" |
| | | :key="`risk-${item.customerName || itemIndex}`" |
| | | class="sales-focus-item" |
| | | > |
| | | <div class="sales-focus-item__head"> |
| | | <strong>{{ formatStructuredValue(item.customerName) }}</strong> |
| | | <div class="sales-focus-tags"> |
| | | <el-tag size="small" :type="getSalesLevelTagType(item.riskLevel)"> |
| | | {{ getSalesLevelLabel(item.riskLevel, 'risk') }} |
| | | </el-tag> |
| | | <el-tag size="small" type="warning">é£é©å {{ formatStructuredValue(item.riskScore) }}</el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="sales-focus-metrics"> |
| | | <span>å¾
忬¾ï¼{{ formatStructuredValue(item.pendingAmount) }}</span> |
| | | <span>å¾
忬¾å æ¯ï¼{{ formatStructuredValue(item.pendingRate) }}</span> |
| | | <span>è·ä¸æ¬¡ä¸åï¼{{ formatStructuredValue(item.daysSinceLastOrder) }}</span> |
| | | </div> |
| | | <div v-if="toStructuredStringArray(item.riskReasons).length" class="sales-focus-reasons"> |
| | | <el-tag |
| | | v-for="(reason, reasonIndex) in toStructuredStringArray(item.riskReasons)" |
| | | :key="`${item.customerName || itemIndex}-reason-${reasonIndex}`" |
| | | size="small" |
| | | type="danger" |
| | | effect="plain" |
| | | > |
| | | {{ reason }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.type === 'sales_collection_quote_strategy' && message.salesData.listItems?.length" class="sales-focus-list"> |
| | | <div |
| | | v-for="(item, itemIndex) in message.salesData.listItems" |
| | | :key="`strategy-${item.customerName || itemIndex}`" |
| | | class="sales-focus-item sales-focus-item--strategy" |
| | | > |
| | | <div class="sales-focus-item__head"> |
| | | <strong>{{ formatStructuredValue(item.customerName) }}</strong> |
| | | <div class="sales-focus-tags"> |
| | | <el-tag size="small" :type="getSalesLevelTagType(item.priority)"> |
| | | {{ getSalesLevelLabel(item.priority, 'priority') }} |
| | | </el-tag> |
| | | <el-tag size="small" type="success">转åç {{ formatStructuredValue(item.quoteConversionRate) }}</el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="sales-focus-metrics"> |
| | | <span>å¾
忬¾ï¼{{ formatStructuredValue(item.pendingAmount) }}</span> |
| | | <span v-if="item.nextAction">ä¸ä¸æ¥ï¼{{ formatStructuredValue(item.nextAction) }}</span> |
| | | </div> |
| | | <p v-if="item.collectionStrategy" class="sales-strategy-line"> |
| | | <strong>忬¾çç¥ï¼</strong>{{ formatStructuredValue(item.collectionStrategy) }} |
| | | </p> |
| | | <p v-if="item.quotationStrategy" class="sales-strategy-line"> |
| | | <strong>æ¥ä»·çç¥ï¼</strong>{{ formatStructuredValue(item.quotationStrategy) }} |
| | | </p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div |
| | | v-if="message.salesData.listItems?.length && message.salesData.columns?.length && !isSalesFocusType(message.type)" |
| | | class="table-wrapper manufacturing-table-wrapper" |
| | | > |
| | | <el-table :data="message.salesData.listItems" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in message.salesData.columns" |
| | | :key="col" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="140" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="message.salesData.topCustomers?.length && message.salesData.topCustomerColumns?.length" class="table-wrapper manufacturing-table-wrapper"> |
| | | <div class="sales-section-title">éç¹å®¢æ·</div> |
| | | <el-table :data="message.salesData.topCustomers" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in message.salesData.topCustomerColumns" |
| | | :key="`top-customer-${col}`" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="120" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="message.salesData.contractTrend?.length && message.salesData.contractTrendColumns?.length" class="table-wrapper manufacturing-table-wrapper"> |
| | | <div class="sales-section-title">ååè¶å¿</div> |
| | | <el-table :data="message.salesData.contractTrend" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in message.salesData.contractTrendColumns" |
| | | :key="`contract-trend-${col}`" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="120" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card"> |
| | | <div class="purchase-confirm-header"> |
| | | <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || 'éè´ä¸å¡' }}</span> |
| | |
| | | import request from '@/utils/request' |
| | | import * as echarts from 'echarts' |
| | | import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { builtInAssistants, generalAssistant } from './assistants' |
| | | import todoAssistantAvatar from '@/assets/AI/å¾
å婿.png' |
| | | import salesAssistantAvatar from '@/assets/AI/éå®å©æ.png' |
| | |
| | | purchase_return_order: 'éè´éè´§å', |
| | | unknown: 'æªç¥éè´ä¸å¡' |
| | | } |
| | | const salesStructuredTypeSet = new Set([ |
| | | 'sales_customer_profile_list', |
| | | 'sales_quotation_list', |
| | | 'sales_ledger_list', |
| | | 'sales_return_list', |
| | | 'sales_customer_interaction_list', |
| | | 'sales_shipping_list', |
| | | 'sales_dashboard', |
| | | 'sales_customer_churn_risk', |
| | | 'sales_collection_quote_strategy' |
| | | ]) |
| | | const salesFocusTypeSet = new Set([ |
| | | 'sales_customer_churn_risk', |
| | | 'sales_collection_quote_strategy' |
| | | ]) |
| | | const salesTypeLabelMap = { |
| | | sales_customer_profile_list: 'å®¢æ·æ¡£æ¡', |
| | | sales_quotation_list: 'é宿¥ä»·', |
| | | sales_ledger_list: 'éå®å°è´¦', |
| | | sales_return_list: 'éå®éè´§', |
| | | sales_customer_interaction_list: '客æ·å¾æ¥', |
| | | sales_shipping_list: 'åè´§å°è´¦', |
| | | sales_dashboard: 'é宿æ ç»è®¡', |
| | | sales_customer_churn_risk: 'å®¢æ·æµå¤±é£é©åæ', |
| | | sales_collection_quote_strategy: '忬¾ä¸æ¥ä»·çç¥å»ºè®®' |
| | | } |
| | | const manufacturingStructuredTypeSet = new Set([ |
| | | 'manufacturing_site_snapshot', |
| | | 'manufacturing_plan_list', |
| | | 'manufacturing_workorder_list', |
| | | 'manufacturing_device_list', |
| | | 'manufacturing_device_repair_list', |
| | | 'manufacturing_quality_list', |
| | | 'manufacturing_material_list', |
| | | 'manufacturing_exception_list', |
| | | 'manufacturing_warning', |
| | | 'manufacturing_analysis', |
| | | 'manufacturing_action_plan' |
| | | ]) |
| | | const manufacturingListTypeSet = new Set([ |
| | | 'manufacturing_plan_list', |
| | | 'manufacturing_workorder_list', |
| | | 'manufacturing_device_list', |
| | | 'manufacturing_device_repair_list', |
| | | 'manufacturing_quality_list', |
| | | 'manufacturing_material_list', |
| | | 'manufacturing_exception_list' |
| | | ]) |
| | | const manufacturingTypeLabelMap = { |
| | | manufacturing_site_snapshot: 'ç产ç°åºæ¦è§', |
| | | manufacturing_plan_list: 'è®¡åæ¥è¯¢', |
| | | manufacturing_workorder_list: 'å·¥åæ¥è¯¢', |
| | | manufacturing_device_list: 'è®¾å¤æ¥è¯¢', |
| | | manufacturing_device_repair_list: '设å¤ç»´ä¿®è®°å½æ¥è¯¢', |
| | | manufacturing_quality_list: 'è´¨éæ¥è¯¢', |
| | | manufacturing_material_list: 'ç©ææ¥è¯¢', |
| | | manufacturing_exception_list: 'å¼å¸¸æ¥è¯¢', |
| | | manufacturing_warning: 'é¢è¦çæ¿', |
| | | manufacturing_analysis: 'ç»è¥åæ', |
| | | manufacturing_action_plan: 'åç建议' |
| | | } |
| | | const structuredFieldLabelMap = { |
| | | workOrderNo: 'å·¥åå·', |
| | | planEndTime: '计åç»ææ¶é´', |
| | | planStartTime: '计åå¼å§æ¶é´', |
| | | timeRange: 'æ¶é´èå´', |
| | | startDate: 'å¼å§æ¥æ', |
| | | endDate: 'ç»ææ¥æ', |
| | | warningCount: 'é¢è¦æ°é', |
| | | overduePlanCount: 'é¾æè®¡åæ°', |
| | | overdueWorkOrderCount: '龿工忰', |
| | | actionCount: '建议å¨ä½æ°', |
| | | qualityOpenCount: 'è´¨éå¾
å¤çæ°', |
| | | lowStockCount: 'ä½åºåæ°', |
| | | exceptionCount: 'å¼å¸¸æ°', |
| | | userId: 'ç¨æ·ID', |
| | | tenantId: 'ç§æ·ID', |
| | | status: 'ç¶æ', |
| | | deviceName: '设å¤åç§°', |
| | | deviceModel: '设å¤åå·', |
| | | pendingRepairCount: 'å¾
ç»´ä¿®æ°', |
| | | repairTime: 'ç»´ä¿®æ¶é´', |
| | | repairName: 'æ¥ä¿®äºº', |
| | | maintenanceName: '维修人å', |
| | | level: 'é¢è¦ç级', |
| | | title: 'æ é¢', |
| | | count: 'æ°é', |
| | | detail: '详æ
', |
| | | remark: '夿³¨', |
| | | createTime: 'å建æ¶é´', |
| | | updateTime: 'æ´æ°æ¶é´', |
| | | exceptionType: 'å¼å¸¸ç±»å', |
| | | materialName: 'ç©æåç§°', |
| | | stockQty: 'åºåé' |
| | | } |
| | | Object.assign(structuredFieldLabelMap, { |
| | | customerName: '客æ·åç§°', |
| | | riskLevel: 'é£é©ç级', |
| | | riskScore: 'é£é©è¯å', |
| | | riskReasons: 'é£é©åå ', |
| | | pendingAmount: 'å¾
忬¾éé¢', |
| | | pendingRate: 'å¾
忬¾å æ¯', |
| | | daysSinceLastOrder: 'è·ä¸æ¬¡ä¸å天æ°', |
| | | priority: 'ä¼å
级', |
| | | quoteConversionRate: 'æ¥ä»·è½¬åç', |
| | | collectionStrategy: '忬¾çç¥', |
| | | quotationStrategy: 'æ¥ä»·çç¥', |
| | | nextAction: 'ä¸ä¸æ¥å¨ä½', |
| | | contractAmountTotal: 'ååæ»é¢', |
| | | receivedAmountTotal: '已忬¾éé¢', |
| | | pendingAmountTotal: 'å¾
忬¾æ»é¢', |
| | | shipRate: 'åè´§ç' |
| | | }) |
| | | const purchasePayloadFieldLabelMap = { |
| | | purchaseLedgers: 'éè´å°è´¦', |
| | | productData: '产åæç»', |
| | |
| | | inventoryWarningQuantity: 'inventoryWarningQuantity', |
| | | isInspected: 'isInspected', |
| | | isChecked: 'isInspected' |
| | | } |
| | | const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) |
| | | |
| | | const stringifyStructuredPayload = (value, spaces = 2) => { |
| | | if (typeof value === 'string') return value |
| | | try { |
| | | return JSON.stringify(value ?? {}, null, spaces) |
| | | } catch (err) { |
| | | return '{}' |
| | | } |
| | | } |
| | | |
| | | const structuredFieldTokenLabelMap = { |
| | | time: 'æ¶é´', |
| | | range: 'èå´', |
| | | start: 'å¼å§', |
| | | end: 'ç»æ', |
| | | date: 'æ¥æ', |
| | | warning: 'é¢è¦', |
| | | overdue: '龿', |
| | | plan: '计å', |
| | | work: 'å·¥', |
| | | order: 'å', |
| | | workorder: 'å·¥å', |
| | | count: 'æ°é', |
| | | quality: 'è´¨é', |
| | | low: 'ä½', |
| | | stock: 'åºå', |
| | | exception: 'å¼å¸¸', |
| | | action: 'å¨ä½', |
| | | user: 'ç¨æ·', |
| | | tenant: 'ç§æ·', |
| | | id: 'ID', |
| | | no: 'ç¼å·', |
| | | number: 'ç¼å·', |
| | | code: 'ç¼ç ', |
| | | name: 'åç§°', |
| | | status: 'ç¶æ', |
| | | level: 'ç级', |
| | | title: 'æ é¢', |
| | | detail: '详æ
', |
| | | total: 'æ»æ°', |
| | | rate: 'æ¯ç', |
| | | type: 'ç±»å', |
| | | pending: 'å¾
', |
| | | repair: 'ç»´ä¿®', |
| | | device: '设å¤', |
| | | material: 'ç©æ' |
| | | } |
| | | |
| | | const convertStructuredFieldKeyToChinese = (fieldKey = '') => { |
| | | const key = String(fieldKey || '').trim() |
| | | if (!key) return '-' |
| | | if (structuredFieldLabelMap[key]) return structuredFieldLabelMap[key] |
| | | if (/[\u4e00-\u9fa5]/.test(key)) return key |
| | | |
| | | const rawTokens = key |
| | | .replace(/([a-z0-9])([A-Z])/g, '$1 $2') |
| | | .replace(/[_-]+/g, ' ') |
| | | .trim() |
| | | .split(/\s+/) |
| | | .filter(Boolean) |
| | | .map(token => token.toLowerCase()) |
| | | |
| | | if (!rawTokens.length) return 'åæ®µ' |
| | | |
| | | const mappedTokens = rawTokens |
| | | .map(token => structuredFieldTokenLabelMap[token] || '') |
| | | .filter(Boolean) |
| | | |
| | | if (mappedTokens.length) { |
| | | return mappedTokens.join('') |
| | | } |
| | | |
| | | return 'åæ®µ' |
| | | } |
| | | |
| | | const getStructuredFieldLabel = (fieldKey = '') => { |
| | | return convertStructuredFieldKeyToChinese(fieldKey) |
| | | } |
| | | |
| | | const getStructuredPathLabel = (fieldPath = '') => { |
| | | const path = String(fieldPath || '').trim() |
| | | if (!path) return '-' |
| | | |
| | | const segments = path |
| | | .replace(/\[(\d+)]/g, '.$1') |
| | | .split('.') |
| | | .filter(Boolean) |
| | | |
| | | if (!segments.length) return getStructuredFieldLabel(path) |
| | | |
| | | return segments.map((segment) => { |
| | | if (/^\d+$/.test(segment)) { |
| | | return `第${Number(segment) + 1}项` |
| | | } |
| | | return getStructuredFieldLabel(segment) |
| | | }).join(' / ') |
| | | } |
| | | |
| | | const formatStructuredValue = (value) => { |
| | | if (value === null || value === undefined || value === '') return '-' |
| | | if (Array.isArray(value)) { |
| | | const preview = value.slice(0, 3).map(item => formatStructuredValue(item)).join('ã') |
| | | return value.length > 3 ? `${preview} ç${value.length}项` : preview |
| | | } |
| | | if (isPlainObject(value)) return stringifyStructuredPayload(value, 0) |
| | | return String(value) |
| | | } |
| | | |
| | | const normalizeManufacturingSummaryEntries = (summary) => { |
| | | if (!isPlainObject(summary)) return [] |
| | | return Object.entries(summary) |
| | | .filter(([, value]) => value !== undefined && value !== null && `${value}`.trim() !== '') |
| | | .map(([key, value]) => ({ |
| | | key, |
| | | label: getStructuredFieldLabel(key), |
| | | value: formatStructuredValue(value) |
| | | })) |
| | | } |
| | | |
| | | const normalizeManufacturingCoreMetrics = (coreMetrics) => { |
| | | if (Array.isArray(coreMetrics)) { |
| | | return coreMetrics.map((item, index) => { |
| | | if (isPlainObject(item)) { |
| | | const label = item.label || item.name || item.key || `ææ ${index + 1}` |
| | | const metricValue = item.value ?? item.metricValue ?? item.data ?? '-' |
| | | const unit = item.unit ? ` ${item.unit}` : '' |
| | | return { |
| | | key: String(item.key || item.name || index), |
| | | label, |
| | | value: `${formatStructuredValue(metricValue)}${unit}`.trim() |
| | | } |
| | | } |
| | | return { |
| | | key: String(index), |
| | | label: `ææ ${index + 1}`, |
| | | value: formatStructuredValue(item) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | if (isPlainObject(coreMetrics)) { |
| | | return Object.entries(coreMetrics).map(([key, value]) => ({ |
| | | key, |
| | | label: getStructuredFieldLabel(key), |
| | | value: formatStructuredValue(value) |
| | | })) |
| | | } |
| | | |
| | | return [] |
| | | } |
| | | |
| | | const normalizeManufacturingWarningItems = (items = []) => { |
| | | if (!Array.isArray(items)) return [] |
| | | return items |
| | | .filter(item => isPlainObject(item)) |
| | | .map(item => ({ |
| | | level: String(item.level || '').toLowerCase(), |
| | | title: item.title || '', |
| | | count: item.count ?? '', |
| | | detail: item.detail ?? '' |
| | | })) |
| | | } |
| | | |
| | | const inferManufacturingColumns = (items = []) => { |
| | | if (!Array.isArray(items) || !items.length) return [] |
| | | const fieldSet = new Set() |
| | | items.forEach((item) => { |
| | | if (!isPlainObject(item)) return |
| | | Object.keys(item).forEach((key) => fieldSet.add(key)) |
| | | }) |
| | | return Array.from(fieldSet) |
| | | } |
| | | |
| | | const getManufacturingActionCardRuntimeKey = (card = {}, index = 0) => { |
| | | const code = String(card?.code || '').trim() |
| | | const api = String(card?.targetApi || '').trim() |
| | | const name = String(card?.name || '').trim() |
| | | return `${code}::${api}::${name}::${index}` |
| | | } |
| | | |
| | | const normalizeManufacturingActionCards = (actionCards = [], previousCards = []) => { |
| | | const previousMap = new Map() |
| | | if (Array.isArray(previousCards)) { |
| | | previousCards.forEach((card, index) => { |
| | | previousMap.set(getManufacturingActionCardRuntimeKey(card, index), card) |
| | | }) |
| | | } |
| | | |
| | | return (Array.isArray(actionCards) ? actionCards : []) |
| | | .filter(card => isPlainObject(card)) |
| | | .map((card, index) => { |
| | | const runtimeKey = getManufacturingActionCardRuntimeKey(card, index) |
| | | const previousCard = previousMap.get(runtimeKey) |
| | | const fallbackPayloadText = stringifyStructuredPayload(card.examplePayload, 2) |
| | | return { |
| | | ...card, |
| | | runtimeKey, |
| | | payloadText: previousCard?.payloadText ?? fallbackPayloadText, |
| | | executing: Boolean(previousCard?.executing), |
| | | executed: Boolean(previousCard?.executed), |
| | | executeResult: previousCard?.executeResult || '', |
| | | executeError: Boolean(previousCard?.executeError) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const buildManufacturingStructuredData = (parsedData, previousData = null) => { |
| | | const type = String(parsedData?.type || '') |
| | | if (!manufacturingStructuredTypeSet.has(type)) return null |
| | | |
| | | const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {} |
| | | const items = Array.isArray(rawData.items) ? rawData.items.filter(item => isPlainObject(item)) : [] |
| | | const warningItems = type === 'manufacturing_warning' ? normalizeManufacturingWarningItems(items) : [] |
| | | const listItems = manufacturingListTypeSet.has(type) ? items : [] |
| | | const actionCards = type === 'manufacturing_action_plan' |
| | | ? normalizeManufacturingActionCards(rawData.actionCards, previousData?.actionCards) |
| | | : [] |
| | | |
| | | return { |
| | | type, |
| | | summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary), |
| | | coreMetrics: normalizeManufacturingCoreMetrics(rawData.coreMetrics), |
| | | listItems, |
| | | columns: inferManufacturingColumns(listItems), |
| | | warningItems, |
| | | actionCards |
| | | } |
| | | } |
| | | |
| | | const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || 'å¶é ç»æ' |
| | | |
| | | const inferSalesColumns = (items = []) => { |
| | | if (!Array.isArray(items) || !items.length) return [] |
| | | const fieldSet = new Set() |
| | | items.forEach((item) => { |
| | | if (!isPlainObject(item)) return |
| | | Object.keys(item).forEach((key) => fieldSet.add(key)) |
| | | }) |
| | | return Array.from(fieldSet) |
| | | } |
| | | |
| | | const normalizeSalesListItems = (items) => { |
| | | if (!Array.isArray(items)) return [] |
| | | return items.filter(item => isPlainObject(item)) |
| | | } |
| | | |
| | | const buildSalesStructuredData = (parsedData) => { |
| | | const type = String(parsedData?.type || '') |
| | | if (!salesStructuredTypeSet.has(type)) return null |
| | | |
| | | const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {} |
| | | const listItems = normalizeSalesListItems(rawData.items) |
| | | const topCustomers = normalizeSalesListItems(rawData.topCustomers) |
| | | const contractTrend = normalizeSalesListItems(rawData.contractTrend) |
| | | |
| | | return { |
| | | type, |
| | | summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary), |
| | | listItems, |
| | | columns: inferSalesColumns(listItems), |
| | | topCustomers, |
| | | topCustomerColumns: inferSalesColumns(topCustomers), |
| | | contractTrend, |
| | | contractTrendColumns: inferSalesColumns(contractTrend) |
| | | } |
| | | } |
| | | |
| | | const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || 'é宿¥è¯¢ç»æ' |
| | | |
| | | const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || '')) |
| | | |
| | | const getSalesLevelTagType = (level = '') => { |
| | | const normalizedLevel = String(level || '').toLowerCase() |
| | | if (normalizedLevel === 'high') return 'danger' |
| | | if (normalizedLevel === 'medium') return 'warning' |
| | | if (normalizedLevel === 'low') return 'success' |
| | | return 'info' |
| | | } |
| | | |
| | | const getSalesLevelLabel = (level = '', mode = 'risk') => { |
| | | const normalizedLevel = String(level || '').toLowerCase() |
| | | const suffix = mode === 'priority' ? 'ä¼å
级' : 'é£é©' |
| | | if (normalizedLevel === 'high') return `é«${suffix}` |
| | | if (normalizedLevel === 'medium') return `ä¸${suffix}` |
| | | if (normalizedLevel === 'low') return `ä½${suffix}` |
| | | if (!normalizedLevel) return mode === 'priority' ? 'æªå级' : 'æªè¯ä¼°' |
| | | return normalizedLevel.toUpperCase() |
| | | } |
| | | |
| | | const toStructuredStringArray = (value) => { |
| | | if (Array.isArray(value)) { |
| | | return value.map(item => String(item || '').trim()).filter(Boolean) |
| | | } |
| | | if (typeof value === 'string') { |
| | | return value |
| | | .split(/[,\uFF0C\u3001;\uFF1B\n]/) |
| | | .map(item => item.trim()) |
| | | .filter(Boolean) |
| | | } |
| | | return [] |
| | | } |
| | | |
| | | const getManufacturingWarningLevelType = (level = '') => { |
| | | const normalizedLevel = String(level || '').toLowerCase() |
| | | if (normalizedLevel === 'high') return 'danger' |
| | | if (normalizedLevel === 'medium') return 'warning' |
| | | return 'info' |
| | | } |
| | | |
| | | const getManufacturingWarningLevelLabel = (level = '') => { |
| | | const normalizedLevel = String(level || '').toLowerCase() |
| | | if (normalizedLevel === 'high') return 'é«' |
| | | if (normalizedLevel === 'medium') return 'ä¸' |
| | | return normalizedLevel ? normalizedLevel.toUpperCase() : 'ä¸è¬' |
| | | } |
| | | |
| | | const normalizeRequestMethod = (method = 'POST') => { |
| | | const normalized = String(method || 'POST').trim().toUpperCase() |
| | | if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(normalized)) return normalized |
| | | return 'POST' |
| | | } |
| | | |
| | | const getNormalizedRequestMethod = (method) => normalizeRequestMethod(method) |
| | | |
| | | const getPayloadValueByPath = (payload, fieldPath = '') => { |
| | | const normalizedPath = String(fieldPath || '').trim() |
| | | if (!normalizedPath || !isPlainObject(payload)) return undefined |
| | | |
| | | const pathSegments = normalizedPath |
| | | .replace(/\[(\d+)]/g, '.$1') |
| | | .split('.') |
| | | .filter(Boolean) |
| | | |
| | | return pathSegments.reduce((current, segment) => { |
| | | if (current === null || current === undefined) return undefined |
| | | if (!['object', 'function'].includes(typeof current)) return undefined |
| | | return current[segment] |
| | | }, payload) |
| | | } |
| | | |
| | | const getMissingRequiredFields = (requiredFields = [], payload = {}) => { |
| | | if (!Array.isArray(requiredFields) || !requiredFields.length) return [] |
| | | return requiredFields.filter((fieldPath) => { |
| | | const value = getPayloadValueByPath(payload, fieldPath) |
| | | return !hasMeaningfulPayloadValue(value) |
| | | }) |
| | | } |
| | | |
| | | const parseManufacturingActionPayload = (payloadText = '') => { |
| | | const text = String(payloadText ?? '').trim() |
| | | if (!text) return {} |
| | | return JSON.parse(text) |
| | | } |
| | | |
| | | const executeManufacturingAction = async (message, actionCard, cardIndex = 0) => { |
| | | if (!message?.manufacturingData || !actionCard || actionCard.executing) return |
| | | |
| | | const actionName = actionCard.name || `å¨ä½ ${cardIndex + 1}` |
| | | const targetApi = String(actionCard.targetApi || '').trim() |
| | | if (!targetApi) { |
| | | actionCard.executeError = true |
| | | actionCard.executeResult = 'ç¼ºå° targetApiï¼æ æ³æ§è¡å¨ä½' |
| | | return |
| | | } |
| | | |
| | | let payload = {} |
| | | try { |
| | | payload = parseManufacturingActionPayload(actionCard.payloadText) |
| | | } catch (err) { |
| | | actionCard.executeError = true |
| | | actionCard.executeResult = '请æ±åæ°ä¸æ¯åæ³ JSONï¼è¯·æ£æ¥åéè¯' |
| | | return |
| | | } |
| | | |
| | | const requiredFields = Array.isArray(actionCard.requiredFields) ? actionCard.requiredFields : [] |
| | | if (requiredFields.length && !isPlainObject(payload)) { |
| | | actionCard.executeError = true |
| | | actionCard.executeResult = 'å¿
å¡«åæ®µæ ¡éªå¤±è´¥ï¼è¯·æ±åæ°å¿
é¡»æ¯ JSON 对象' |
| | | return |
| | | } |
| | | |
| | | const missingFields = getMissingRequiredFields(requiredFields, payload) |
| | | if (missingFields.length) { |
| | | actionCard.executeError = true |
| | | const missingFieldLabels = missingFields.map(field => getStructuredPathLabel(field)) |
| | | actionCard.executeResult = `缺å°å¿
å¡«åæ®µï¼${missingFieldLabels.join('ã')}` |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm(`确认æ§è¡ã${actionName}ãåï¼`, 'æ§è¡ç¡®è®¤', { |
| | | confirmButtonText: '确认æ§è¡', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }) |
| | | } catch (err) { |
| | | return |
| | | } |
| | | |
| | | actionCard.executing = true |
| | | actionCard.executeError = false |
| | | actionCard.executeResult = '' |
| | | |
| | | const method = normalizeRequestMethod(actionCard.method) |
| | | const requestConfig = { |
| | | url: targetApi, |
| | | method: method.toLowerCase() |
| | | } |
| | | |
| | | if (method === 'GET') { |
| | | requestConfig.params = payload |
| | | } else { |
| | | requestConfig.data = payload |
| | | } |
| | | |
| | | try { |
| | | const res = await request(requestConfig) |
| | | const successMsg = res?.msg || `${actionName}æ§è¡æå` |
| | | actionCard.executed = true |
| | | actionCard.executeError = false |
| | | actionCard.executeResult = successMsg |
| | | ElMessage.success(successMsg) |
| | | } catch (err) { |
| | | actionCard.executed = false |
| | | actionCard.executeError = true |
| | | actionCard.executeResult = err?.message || `${actionName}æ§è¡å¤±è´¥ï¼è¯·ç¨åéè¯` |
| | | } finally { |
| | | actionCard.executing = false |
| | | } |
| | | } |
| | | |
| | | // åå²ä¼è¯ç¸å
³ |
| | |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null, |
| | | localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : [] |
| | | } |
| | | |
| | |
| | | const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => { |
| | | if (!messageObj || !parsedData?.success) return |
| | | |
| | | const previousManufacturingData = messageObj.manufacturingData |
| | | messageObj.type = parsedData.type || '' |
| | | messageObj.tableData = null |
| | | messageObj.purchaseAnalysisData = null |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | |
| | | if (messageObj.type === 'todo_list' && parsedData.data) { |
| | | messageObj.tableData = parsedData.data |
| | | } |
| | | |
| | | const salesData = buildSalesStructuredData(parsedData) |
| | | if (salesData) { |
| | | messageObj.salesData = salesData |
| | | } |
| | | |
| | | const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData) |
| | | if (manufacturingData) { |
| | | messageObj.manufacturingData = manufacturingData |
| | | } |
| | | |
| | | if (parsedData.action === 'confirm_required' && parsedData.businessType) { |
| | | messageObj.type = 'purchase_analysis_confirm' |
| | | messageObj.purchaseAnalysisData = parsedData |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) { |
| | | initializePurchasePayloadTree(messageObj, parsedData.payload || {}) |
| | | } |
| | |
| | | } |
| | | |
| | | return null |
| | | } |
| | | |
| | | const getStructuredFallbackText = (parsedData) => { |
| | | if (!parsedData) return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | if (parsedData.type === 'todo_list') return 'å·²ä¸ºæ¨æ´ç好ç¸å
³æ°æ®ã' |
| | | if (salesStructuredTypeSet.has(parsedData.type)) { |
| | | if (parsedData.type === 'sales_customer_churn_risk') return '已为æ¨çæå®¢æ·æµå¤±é£é©åæã' |
| | | if (parsedData.type === 'sales_collection_quote_strategy') return '已为æ¨çæåæ¬¾ä¸æ¥ä»·çç¥å»ºè®®ã' |
| | | if (parsedData.type === 'sales_dashboard') return '已为æ¨çæé宿æ ç»è®¡ã' |
| | | return 'å·²è¿åé宿¥è¯¢ç»æã' |
| | | } |
| | | if (manufacturingStructuredTypeSet.has(parsedData.type)) { |
| | | if (parsedData.type === 'manufacturing_action_plan') return '已为æ¨çæåç建议ï¼è¯·ç¡®è®¤å¨ä½åæ§è¡ã' |
| | | if (parsedData.type === 'manufacturing_warning') return '已为æ¨çæå¶é é¢è¦çæ¿ã' |
| | | if (parsedData.type === 'manufacturing_analysis') return '已为æ¨çæå¶é åæç»æã' |
| | | return 'å·²è¿åå¶é æ¥è¯¢ç»æã' |
| | | } |
| | | if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '已为æ¨çæåæå¾è¡¨ã' |
| | | return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | } |
| | | |
| | | const buildPurchaseMaterialRankCharts = (parsedData) => { |
| | |
| | | type: '', |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | }) |
| | | |
| | | outputState.value[botMsgIndex] = { |
| | |
| | | type: '', |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | } |
| | | messages.value.push(botMsg) |
| | | |
| | |
| | | } |
| | | |
| | | if (!display) { |
| | | if (parsed.type === 'todo_list') { |
| | | display = 'å·²ä¸ºæ¨æ´ç好ç¸å
³æ°æ®ã' |
| | | } else if (parsed.charts && Object.keys(parsed.charts).length > 0) { |
| | | display = '已为æ¨çæåæå¾è¡¨ã' |
| | | } else { |
| | | display = 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | } |
| | | display = getStructuredFallbackText(parsed) |
| | | } |
| | | } else if (startIdx !== -1) { |
| | | const lastBraceIdx = output.lastIndexOf('}') |
| | |
| | | } |
| | | |
| | | if (!display) { |
| | | if (parsed.type === 'todo_list') { |
| | | display = 'å·²ä¸ºæ¨æ´ç好ç¸å
³æ°æ®ï¼' |
| | | } else if (parsed.charts && Object.keys(parsed.charts).length > 0) { |
| | | display = '已为æ¨çæåæå¾è¡¨ï¼' |
| | | } else { |
| | | display = 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | } |
| | | display = getStructuredFallbackText(parsed) |
| | | } |
| | | } catch (e) { |
| | | // è§£æå¤±è´¥ï¼è¯´æ JSON è¿å¨ä¼ è¾ä¸ææ ¼å¼ä¸æ£ç¡® |
| | |
| | | } |
| | | } |
| | | |
| | | .manufacturing-card { |
| | | margin-top: 12px; |
| | | width: 100%; |
| | | background: #fff; |
| | | border: 1px solid rgba(0, 85, 212, 0.12); |
| | | border-radius: 12px; |
| | | box-shadow: $shadow-card; |
| | | padding: 14px; |
| | | } |
| | | |
| | | .manufacturing-card__title { |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: $deep-blue; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .manufacturing-summary-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); |
| | | gap: 8px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .manufacturing-summary-item { |
| | | border-radius: 10px; |
| | | padding: 10px 12px; |
| | | border: 1px solid rgba(0, 85, 212, 0.08); |
| | | background: linear-gradient(180deg, #f8fbff, #f1f7ff); |
| | | min-height: 66px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .manufacturing-summary-item--core { |
| | | border-color: rgba(30, 91, 255, 0.24); |
| | | } |
| | | |
| | | .manufacturing-summary-label { |
| | | font-size: 12px; |
| | | color: #4b5563; |
| | | } |
| | | |
| | | .manufacturing-summary-value { |
| | | font-size: 15px; |
| | | color: #1f2937; |
| | | line-height: 1.4; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .manufacturing-warning-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .manufacturing-warning-item { |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(245, 158, 11, 0.22); |
| | | background: linear-gradient(135deg, rgba(255, 247, 237, 0.9), rgba(255, 255, 255, 0.98)); |
| | | padding: 10px 12px; |
| | | } |
| | | |
| | | .manufacturing-warning-item__head { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | color: #92400e; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .manufacturing-warning-count { |
| | | margin-left: auto; |
| | | font-weight: 700; |
| | | color: #c2410c; |
| | | } |
| | | |
| | | .manufacturing-warning-detail { |
| | | margin: 8px 0 0; |
| | | font-size: 12px; |
| | | line-height: 1.6; |
| | | color: #7c2d12; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .manufacturing-table-wrapper { |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .manufacturing-action-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .manufacturing-action-card { |
| | | border: 1px solid rgba(0, 85, 212, 0.1); |
| | | border-radius: 10px; |
| | | padding: 10px 12px; |
| | | background: #f8fbff; |
| | | } |
| | | |
| | | .manufacturing-action-card__head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | font-size: 13px; |
| | | color: #1f2937; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .manufacturing-action-card__meta { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | margin-bottom: 8px; |
| | | font-size: 12px; |
| | | color: #64748b; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .manufacturing-action-card__desc { |
| | | margin: 0 0 8px; |
| | | font-size: 12px; |
| | | line-height: 1.6; |
| | | color: #475467; |
| | | } |
| | | |
| | | .manufacturing-required-fields { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 6px; |
| | | margin-bottom: 8px; |
| | | font-size: 12px; |
| | | color: #7c2d12; |
| | | } |
| | | |
| | | .manufacturing-action-footer { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .manufacturing-action-result { |
| | | flex: 1; |
| | | font-size: 12px; |
| | | line-height: 1.5; |
| | | |
| | | &.success { |
| | | color: #1f9d55; |
| | | } |
| | | |
| | | &.error { |
| | | color: #d93025; |
| | | } |
| | | } |
| | | |
| | | .sales-structured-card { |
| | | margin-top: 12px; |
| | | width: 100%; |
| | | background: #fff; |
| | | border: 1px solid rgba(31, 122, 114, 0.2); |
| | | border-radius: 12px; |
| | | box-shadow: $shadow-card; |
| | | padding: 14px; |
| | | } |
| | | |
| | | .sales-structured-card__title { |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: #1f5ddf; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .sales-summary-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); |
| | | gap: 8px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .sales-summary-item { |
| | | border-radius: 10px; |
| | | padding: 10px 12px; |
| | | border: 1px solid rgba(30, 91, 255, 0.12); |
| | | background: linear-gradient(180deg, #f7fbff, #edf6ff); |
| | | min-height: 66px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .sales-summary-label { |
| | | font-size: 12px; |
| | | color: #4b5563; |
| | | } |
| | | |
| | | .sales-summary-value { |
| | | font-size: 15px; |
| | | color: #1f2937; |
| | | line-height: 1.4; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .sales-focus-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .sales-focus-item { |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(30, 91, 255, 0.14); |
| | | background: #f8fbff; |
| | | padding: 10px 12px; |
| | | } |
| | | |
| | | .sales-focus-item--strategy { |
| | | border-color: rgba(31, 122, 114, 0.22); |
| | | background: linear-gradient(180deg, #f7fcfb, #edf9f6); |
| | | } |
| | | |
| | | .sales-focus-item__head { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | gap: 10px; |
| | | font-size: 13px; |
| | | color: #1f2937; |
| | | } |
| | | |
| | | .sales-focus-tags { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | flex-wrap: wrap; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .sales-focus-metrics { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | font-size: 12px; |
| | | color: #475467; |
| | | } |
| | | |
| | | .sales-focus-reasons { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .sales-strategy-line { |
| | | margin: 8px 0 0; |
| | | font-size: 12px; |
| | | line-height: 1.6; |
| | | color: #334155; |
| | | } |
| | | |
| | | .sales-section-title { |
| | | margin: 4px 0 8px; |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | color: $deep-blue; |
| | | } |
| | | |
| | | .purchase-confirm-card { |
| | | margin-top: 12px; |
| | | width: 100%; |
| | |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼"> |
| | | <el-input v-model="selectedParam.standardValue" |
| | | @input="val => onStandardValueInput(val, selectedParam)" |
| | | placeholder="请è¾å
¥é»è®¤å¼" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯å¦å¿
å¡«"> |
| | |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <el-button type="primary" @click="handleParamSelectSubmit">ç¡®å®</el-button> |
| | | <el-button type="primary" |
| | | @click="handleParamSelectSubmit">ç¡®å®</el-button> |
| | | <el-button @click="selectParamDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | <el-form-item label="æ åå¼" |
| | | prop="standardValue"> |
| | | <el-input v-model="editParamForm.standardValue" |
| | | @input="val => onStandardValueInput(val, editParamForm)" |
| | | placeholder="请è¾å
¥æ åå¼" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="handleEditParamSubmit">ç¡®å®</el-button> |
| | | <el-button type="primary" |
| | | @click="handleEditParamSubmit">ç¡®å®</el-button> |
| | | <el-button @click="editParamDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | paramFormat: "", |
| | | unit: "", |
| | | }); |
| | | |
| | | const onStandardValueInput = (val, target) => { |
| | | const data = target.value || target; |
| | | const type = data.paramType || data.parameterType; |
| | | if (type === 1) { |
| | | // æ°å¼æ ¼å¼ï¼ä¸è½è¾å
¥ä¸ææè±æå符 |
| | | data.standardValue = val.replace(/[a-zA-Z\u4e00-\u9fa5]/g, ""); |
| | | } |
| | | }; |
| | | |
| | | const editParamRules = ref({ |
| | | // standardValue: [{ required: true, message: "请è¾å
¥æ åå¼", trigger: "blur" }], |
| | | standardValue: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | const type = |
| | | editParamForm.value.paramType || editParamForm.value.parameterType; |
| | | if (type === 1 && value) { |
| | | if (/[a-zA-Z\u4e00-\u9fa5]/.test(value)) { |
| | | return callback(new Error("æ°å¼æ ¼å¼ä¸è½å
å«ä¸è±æå符")); |
| | | } |
| | | } |
| | | callback(); |
| | | }, |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | }); |
| | | const editParamFormRef = ref(null); |
| | | |
| | |
| | | margin-bottom: 6px; |
| | | border-radius: 14px; |
| | | color: v-bind(getMenuTextColor); |
| | | font-size: 13px; |
| | | |
| | | &:hover { |
| | | background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important; |
| | |
| | | |
| | | .el-sub-menu__title { |
| | | color: v-bind(getMenuTextColor); |
| | | } |
| | | |
| | | :deep(.el-sub-menu__icon-arrow) { |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 14px; |
| | | height: 14px; |
| | | margin-top: -7px; |
| | | right: 12px; |
| | | font-size: 14px !important; |
| | | color: currentColor !important; |
| | | opacity: 0.72; |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title) { |
| | |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .menu-title), |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .svg-icon), |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .el-sub-menu__icon-arrow), |
| | | :deep(.el-menu-item.is-active .menu-title), |
| | | :deep(.el-menu-item.is-active .svg-icon) { |
| | | color: v-bind(theme) !important; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # AIå·¥ä¸å¤§èç»´æ¤è§å |
| | | |
| | | 1. å½ `src/views/aiIndustrialBrain/index.vue` æ°å¢æºè½ä½ï¼`agents`ï¼é»è¾æ¶ï¼å¿
é¡»åæ¥ç¡®è®¤å¼¹çªå©æå¯ç¨æ§ã |
| | | 2. å¼¹çªå©æç± `src/components/AIChatSidebar/assistants/index.js` ç `assistantRegistry` ç»ä¸æ³¨åã |
| | | 3. æ°å¢æºè½ä½ç `key` è¥è¦å¨å¼¹çªä¸å¯ç¨ï¼å¿
é¡»å¨ `assistantRegistry` 䏿ä¾ååé
ç½®ã |
| | | 4. æªå¨ `assistantRegistry` 注åçæºè½ä½ä¼å¨å¼¹çªä¸æ¾ç¤ºä¸º `pending`ï¼å¼åä¸ï¼æã |
| | | |
| | |
| | | v-if="assistantMode !== 'pending'" |
| | | :key="assistantMode" |
| | | class="workspace-chat" |
| | | :assistants="assistantMode === 'purchase' ? [purchaseAssistant] : [generalAssistant]" |
| | | :assistants="resolvedAssistants" |
| | | :default-assistant="assistantMode" |
| | | :hide-trigger="true" |
| | | :auto-open="true" |
| | |
| | | import { computed } from "vue"; |
| | | import { ArrowLeftBold } from "@element-plus/icons-vue"; |
| | | import AIChatSidebar from "@/components/AIChatSidebar/index.vue"; |
| | | import { generalAssistant, purchaseAssistant } from "@/components/AIChatSidebar/assistants"; |
| | | import { assistantRegistry } from "@/components/AIChatSidebar/assistants"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | |
| | | |
| | | const agentKey = computed(() => String(props.agent?.key || "")); |
| | | const agentTitle = computed(() => String(props.agent?.name || "AI婿")); |
| | | |
| | | /** |
| | | * ç»´æ¤è§åï¼ |
| | | * AIå·¥ä¸å¤§èæ°å¢æºè½ä½æ¶ï¼è¥å¸æå³ä¾§å¼¹çªå¯ç¨ï¼éä¿è¯æºè½ä½ key å¨ assistantRegistry 䏿ååé
ç½®ã |
| | | * æªé
ç½®æ¶ä¼è¿å
¥ pendingï¼å¼åä¸ï¼æï¼ä½ä¸ºæ¾å¼æéã |
| | | */ |
| | | const resolvedAssistant = computed(() => assistantRegistry[agentKey.value] || null); |
| | | const assistantMode = computed(() => { |
| | | if (agentKey.value === "purchase") return "purchase"; |
| | | if (agentKey.value === "general") return "general"; |
| | | return "pending"; |
| | | return resolvedAssistant.value ? agentKey.value : "pending"; |
| | | }); |
| | | const resolvedAssistants = computed(() => (resolvedAssistant.value ? [resolvedAssistant.value] : [])); |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | |
| | | const router = useRouter(); |
| | | |
| | | // ç»´æ¤çº¦å®è§ï¼src/views/aiIndustrialBrain/MAINTAIN_RULES.md |
| | | const agents = [ |
| | | { |
| | | key: "general", |
| | |
| | | <el-input v-model="search" |
| | | style="width: 210px" |
| | | placeholder="è¾å
¥å
³é®åè¿è¡æç´¢" |
| | | @change="searchFilter" |
| | | @input="debouncedSearch" |
| | | @clear="searchFilter" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ |
| | | const filterNode = (value, data, node) => { |
| | | if (!value) { |
| | | //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ |
| | | return true; |
| | | } |
| | | // æ¥è¯¢å表æ¯å¦æå¹é
æ°æ®ï¼å°å¼å°åï¼å¹é
è±ææ°æ® |
| | | let val = value.toLowerCase(); |
| | | return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ |
| | | const debounce = (fn, delay = 300) => { |
| | | let timer; |
| | | return (...args) => { |
| | | clearTimeout(timer); |
| | | timer = setTimeout(() => fn(...args), delay); |
| | | }; |
| | | }; |
| | | // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å
¥çåæ°æ¯ç¶èç¹ä¸è½å¹é
ï¼åè¿å该èç¹ä»¥åå
¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. |
| | | const chooseNode = (value, data, node) => { |
| | | if (data.label.indexOf(value) !== -1) { |
| | | |
| | | const debouncedSearch = debounce(() => { |
| | | searchFilter(); |
| | | }, 300); |
| | | |
| | | const filterNode = (value, data) => { |
| | | if (!value) return true; |
| | | return chooseNode(value.toLowerCase(), data); |
| | | }; |
| | | |
| | | const chooseNode = (value, data) => { |
| | | const label = (data.label || '').toLowerCase(); |
| | | if (label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | const level = node.level; |
| | | // å¦æä¼ å
¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº |
| | | if (level === 1) { |
| | | return false; |
| | | if (data.children && data.children.length > 0) { |
| | | return data.children.some(child => chooseNode(value, child)); |
| | | } |
| | | // å
åå½åèç¹çç¶èç¹ |
| | | let parentData = node.parent; |
| | | // éåå½åèç¹çç¶èç¹ |
| | | let index = 0; |
| | | while (index < level - 1) { |
| | | // 妿å¹é
å°ç´æ¥è¿åï¼æ¤å¤name弿¯ä¸æå符ï¼enNameæ¯è±æå符ã夿å¹é
ä¸è±æè¿æ»¤ |
| | | if (parentData.data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | // å¦åçè¯åå¾ä¸ä¸å±åå¹é
|
| | | parentData = parentData.parent; |
| | | index++; |
| | | } |
| | | // 没å¹é
å°è¿åfalse |
| | | return false; |
| | | }; |
| | | getProductTreeList(); |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | <FileList v-if="fileListDialogVisible" |
| | | v-model:visible="fileListDialogVisible" |
| | | record-type="supplier_manage" |
| | | :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "../filesDia.vue"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row) |
| | | openFileDialog(row) |
| | | } |
| | | } |
| | | ], |
| | |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const filesDia = ref() |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(row) |
| | | }) |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | <FileList v-if="fileListDialogVisible" |
| | | v-model:visible="fileListDialogVisible" |
| | | record-type="supplier_manage" |
| | | :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "../filesDia.vue"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row) |
| | | openFileDialog(row) |
| | | } |
| | | } |
| | | ], |
| | |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const filesDia = ref() |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(row) |
| | | }) |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | | price: null, // æ¥ééé¢ |
| | | location: "" // åºå·®å°ç¹ |
| | | location: "", // åºå·®å°ç¹ |
| | | storageBlobDTOS: [] |
| | | }, |
| | | rules: { |
| | | approveId: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | return |
| | | } |
| | | } |
| | | form.value.storageBlobDTOList = fileList.value |
| | | form.value.storageBlobDTOS = fileList.value |
| | | |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | |
| | | <!-- å¼¹çªç»ä»¶ --> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | record-type="approve_process" |
| | | :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FileList from "./fileList.vue"; |
| | | import { Search, Plus, Delete, Download, RefreshRight, DocumentChecked } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, defineAsyncComponent} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { useRoute } from 'vue-router'; |
| | | import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | openFilesFormDia(row); |
| | | }, |
| | | }); |
| | | } |
| | |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const fileListRef = ref(null) |
| | | const downLoadFile = (row) => { |
| | | fileListRef.value.open(row.commonFileList) |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async (row) => { |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | |
| | | </el-form-item> |
| | | <el-form-item label="ç´§æ¥ç¨åº¦" prop="urgency"> |
| | | <el-radio-group v-model="sealForm.urgency"> |
| | | <el-radio label="normal">æ®é</el-radio> |
| | | <el-radio label="urgent">ç´§æ¥</el-radio> |
| | | <el-radio label="very-urgent">ç¹æ¥</el-radio> |
| | | <el-radio value="normal">æ®é</el-radio> |
| | | <el-radio value="urgent">ç´§æ¥</el-radio> |
| | | <el-radio value="very-urgent">ç¹æ¥</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="éä»¶ä¸ä¼ "> |
| | | <AttachmentUploadFile |
| | | v-model:fileList="sealForm.storageBlobDTOs" |
| | | :limit="10" |
| | | :fileSize="50" |
| | | buttonText="ç¹å»ä¸ä¼ éä»¶" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·åå " :span="2">{{ currentSealDetail.reason }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <!-- éä»¶å表 --> |
| | | <div v-if="currentSealDetail.storageBlobVOList?.length || currentSealDetail.storageBlobDTOs?.length" class="attachment-section"> |
| | | <div class="attachment-title">éä»¶å表ï¼</div> |
| | | <el-table :data="currentSealDetail.storageBlobVOList || currentSealDetail.storageBlobDTOs" border class="attachment-table"> |
| | | <el-table-column label="éä»¶åç§°" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || 'æªå½åæä»¶' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="previewFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="primary" size="small" @click="downloadFile(scope.row)">ä¸è½½</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </FormDialog> |
| | | <!-- æä»¶é¢è§ç»ä»¶ --> |
| | | <FilePreview ref="filePreviewRef" /> |
| | | |
| | | </div> |
| | | </template> |
| | |
| | | import useUserStore from '@/store/modules/user' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue' |
| | | import FilePreview from '@/components/filePreview/index.vue' |
| | | import download from '@/plugins/download.js' |
| | | |
| | | // ååºå¼æ°æ® |
| | | // ç¨å°ç³è¯·ç¸å
³ |
| | |
| | | const tableLoading = ref(false) |
| | | const showSealDetailDialog = ref(false) |
| | | const currentSealDetail = ref(null) |
| | | const filePreviewRef = ref(null) |
| | | const sealFormRef = ref() |
| | | const userList = ref([]) |
| | | const sealForm = reactive({ |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | |
| | | const sealRules = { |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | } |
| | | }).catch(err => { |
| | |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending' |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | // æ¸
é¤è¡¨åéªè¯ç¶æ |
| | | if (sealFormRef.value) { |
| | |
| | | const viewSealDetail = (row) => { |
| | | currentSealDetail.value = row |
| | | showSealDetailDialog.value = true |
| | | } |
| | | |
| | | // é¢è§æä»¶ |
| | | const previewFile = (row) => { |
| | | const url = row.previewURL || row.previewUrl || row.url |
| | | if (url && filePreviewRef.value) { |
| | | filePreviewRef.value.open(url) |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³é¢è§') |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½æä»¶ |
| | | const downloadFile = (row) => { |
| | | const url = row.downloadURL || row.downloadUrl || row.url |
| | | if (url) { |
| | | const filename = row.originalFilename || row.name || row.fileName || 'download' |
| | | download.byUrl(url, filename) |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³ä¸è½½') |
| | | } |
| | | } |
| | | // 审æ¹ç¨å°ç³è¯· |
| | | const approveSeal = (row) => { |
| | |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .attachment-section { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .attachment-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .attachment-table { |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·¡æ£é¡¹ç®" prop="inspectionProject"> |
| | | <el-input v-model="form.inspectionProject" placeholder="请è¾å
¥å·¡æ£é¡¹ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦å¯ç¨" prop="isEnabled"> |
| | | <el-radio-group v-model="form.isEnabled"> |
| | | <el-radio :value="1">æ¯</el-radio> |
| | | <el-radio :value="0">å¦</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥å¤æ³¨" type="textarea" /> |
| | | </el-form-item> |
| | |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | inspectionProject: '', |
| | | isEnabled: 1, |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | inspectionProject: '', |
| | | isEnabled: 1, |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | |
| | | class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | <template #isEnabled="{ row }"> |
| | | <el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'" |
| | | size="small"> |
| | | {{ row.isEnabled == 1 ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </el-card> |
| | |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "inspectionProject", label: "å·¡æ£é¡¹ç®", minWidth: 150 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "isEnabled", |
| | | label: "æ¯å¦å¯ç¨", |
| | | minWidth: 100, |
| | | dataType: "slot", |
| | | slot: "isEnabled", |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { |
| | | prop: "inspectionResult", |
| | | label: "å·¡æ£ç»æ", |
| | | minWidth: 100, |
| | | dataType: "tag", |
| | | formatData: val => { |
| | | return val == 1 ? "æ£å¸¸" : "å¼å¸¸"; |
| | | }, |
| | | formatType: val => { |
| | | return val == 1 ? "success" : "danger"; |
| | | }, |
| | | }, |
| | | { prop: "abnormalDescription", label: "å¼å¸¸æè¿°", minWidth: 100 }, |
| | | ]); |
| | | |
| | | // æä½åé
ç½® |
| | |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["viewFile"]); |
| | | // 宿¶ä»»å¡è®°å½ä¸å±ç¤º"æ¯å¦å¯ç¨"å |
| | | const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...taskColumns, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["viewFile"]; |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®è£
ä½ç½®ï¼" prop="instationLocation"> |
| | | <el-input |
| | | v-model="form.instationLocation" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£å®åä½ï¼" prop="unit"> |
| | | <el-input |
| | | v-model="form.unit" |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä¹¦ç¼å·ï¼" prop="model"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä¹¦ç¼å·ï¼" prop="model"> |
| | | <el-input |
| | | v-model="form.model" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææ°é´å®æ¥æï¼" prop="mostDate"> |
| | | <el-date-picker |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææ¥æ(天)ï¼" prop="valid"> |
| | | <el-input |
| | |
| | | > |
| | | <template #append>æ¥</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£å®å¨æï¼" prop="cycle"> |
| | | <el-input |
| | | v-model="form.cycle" |
| | | placeholder="请è¾å
¥æ£å®å¨æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | form: { |
| | | code: "", |
| | | name: "", |
| | | instationLocation: "", |
| | | mostDate:"", |
| | | model: "", |
| | | cycle:"", |
| | | validDate: "", |
| | | nextDate: "", |
| | | userId: "", |
| | |
| | | nextDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | userId: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | recordDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | instationLocation: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | mostDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | cycle: [{required: true, message: "è¯·éæ©", trigger: "blur"}], |
| | | valid: [ |
| | | {required: true, message: "请è¾å
¥", trigger: "blur"}, |
| | | { |
| | |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :dbRowClick="dbRowClick" |
| | | :rowClassName="rowClassName" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | |
| | | align: "center", |
| | | }, |
| | | { |
| | | label: "å®è£
ä½ç½®", |
| | | prop: "instationLocation", |
| | | width: 150, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "æ£å®åä½", |
| | | prop: "unit", |
| | | width: 200, |
| | |
| | | width: 130, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "æ£å®å¨æ(天)", |
| | | prop: "cycle", |
| | | width: 130, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | |
| | | |
| | | const dbRowClick = (row)=>{ |
| | | rowClickData.value?.openDialog(row) |
| | | } |
| | | |
| | | // è¡æ ·å¼ï¼å¿«å°æï¼7天å
ï¼æé¾ææ 红 |
| | | const rowClassName = ({ row }) => { |
| | | console.log('rowClassName called:', row); |
| | | // valid æ¯ææå¤©æ°ï¼mostDate æ¯ææ°æ£å®æ¥æ |
| | | if (row.valid && row.mostDate) { |
| | | const mostDate = new Date(row.mostDate); |
| | | // 计ç®å°ææ¥æ = æ£å®æ¥æ + ææå¤©æ° |
| | | const validDays = parseInt(row.valid) || 0; |
| | | const expireDate = new Date(mostDate); |
| | | expireDate.setDate(expireDate.getDate() + validDays); |
| | | |
| | | const now = new Date(); |
| | | const diffDays = Math.ceil((expireDate - now) / (1000 * 60 * 60 * 24)); |
| | | console.log('row:', row.code, 'validDays:', validDays, 'expireDate:', expireDate, 'diffDays:', diffDays); |
| | | // 7天å
å°ææå·²é¾æé½æ 红 |
| | | if (diffDays <= 7) { |
| | | console.log('return warning-row'); |
| | | return 'warning-row'; |
| | | } |
| | | } else { |
| | | console.log('row missing valid or mostDate:', row.valid, row.mostDate); |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | :deep(.el-table .warning-row) { |
| | | background-color: #fef0f0 !important; |
| | | } |
| | | :deep(.el-table .warning-row:hover > td) { |
| | | background-color: #f9d5d5 !important; |
| | | } |
| | | :deep(.el-table .el-table__body tr.warning-row td) { |
| | | background-color: #fef0f0 !important; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="visible" |
| | | title="éªæ¶å®¡æ¹" |
| | | width="500px" |
| | | @confirm="submitForm" |
| | | @cancel="handleCancel" |
| | | @close="handleCancel" |
| | | > |
| | | <el-form :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="éªæ¶äºº" prop="acceptanceName"> |
| | | <el-select |
| | | v-model="form.acceptanceName" |
| | | placeholder="è¯·éæ©éªæ¶äºº" |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.nickName" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="éªæ¶æ¶é´" prop="acceptanceTime"> |
| | | <el-date-picker |
| | | v-model="form.acceptanceTime" |
| | | type="datetime" |
| | | placeholder="è¯·éæ©éªæ¶æ¶é´" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="éªæ¶å¤æ³¨" prop="acceptanceRemark"> |
| | | <el-input |
| | | v-model="form.acceptanceRemark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥éªæ¶å¤æ³¨" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { repairAcceptance } from "@/api/equipmentManagement/repair"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | defineOptions({ |
| | | name: "éªæ¶å®¡æ¹å¼¹çª", |
| | | }); |
| | | |
| | | const emits = defineEmits(["ok"]); |
| | | |
| | | const visible = ref(false); |
| | | const loading = ref(false); |
| | | const repairId = ref(null); |
| | | const userList = ref([]); |
| | | |
| | | const form = reactive({ |
| | | acceptanceName: undefined, |
| | | acceptanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | acceptanceRemark: undefined, |
| | | }); |
| | | |
| | | const rules = { |
| | | acceptanceName: [ |
| | | { required: true, message: "è¯·éæ©éªæ¶äºº", trigger: "change" }, |
| | | ], |
| | | acceptanceTime: [ |
| | | { required: true, message: "è¯·éæ©éªæ¶æ¶é´", trigger: "change" }, |
| | | ], |
| | | acceptanceRemark: [ |
| | | { required: true, message: "请è¾å
¥éªæ¶å¤æ³¨", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | const loadUserList = async () => { |
| | | const { data } = await userListNoPageByTenantId(); |
| | | userList.value = data; |
| | | }; |
| | | |
| | | // æå¼å¼¹çª |
| | | const open = async (row) => { |
| | | repairId.value = row.id; |
| | | visible.value = true; |
| | | // é置表å |
| | | form.acceptanceName = undefined; |
| | | form.acceptanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.acceptanceRemark = undefined; |
| | | await loadUserList(); |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | if (!form.acceptanceName) { |
| | | ElMessage.warning("è¯·éæ©éªæ¶äºº"); |
| | | return; |
| | | } |
| | | if (!form.acceptanceTime) { |
| | | ElMessage.warning("è¯·éæ©éªæ¶æ¶é´"); |
| | | return; |
| | | } |
| | | if (!form.acceptanceRemark) { |
| | | ElMessage.warning("请è¾å
¥éªæ¶å¤æ³¨"); |
| | | return; |
| | | } |
| | | |
| | | loading.value = true; |
| | | try { |
| | | const { code } = await repairAcceptance({ |
| | | id: repairId.value, |
| | | acceptanceName: form.acceptanceName, |
| | | acceptanceTime: form.acceptanceTime, |
| | | acceptanceRemark: form.acceptanceRemark, |
| | | }); |
| | | if (code === 200) { |
| | | ElMessage.success("éªæ¶éè¿"); |
| | | visible.value = false; |
| | | emits("ok"); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | visible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®"> |
| | | <el-input v-model="form.machineryCategory" placeholder="请è¾å
¥é¡¹ç®" /> |
| | | <el-form-item label="æ¥ä¿®æ¥ä¿®é¡¹ç®"> |
| | | <el-input v-model="form.machineryCategory" placeholder="请è¾å
¥æ¥ä¿®æ¥ä¿®é¡¹ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="维修人"> |
| | | <el-input v-model="form.maintenanceName" placeholder="请è¾å
¥ç»´ä¿®äººå§å" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="id"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä¿®ç¶æ"> |
| | | <el-select v-model="form.status"> |
| | | <el-select v-model="form.status" disabled> |
| | | <el-option label="å¾
ç»´ä¿®" :value="0"></el-option> |
| | | <el-option label="å®ç»" :value="1"></el-option> |
| | | <el-option label="å·²éªæ¶" :value="1"></el-option> |
| | | <el-option label="失败" :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- éªæ¶ä¿¡æ¯å±ç¤º --> |
| | | <el-row v-if="id && form.status === 1"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éªæ¶äºº"> |
| | | <el-input v-model="form.acceptanceName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éªæ¶æ¶é´"> |
| | | <el-input v-model="form.acceptanceTime" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éªæ¶å¤æ³¨"> |
| | | <el-input v-model="form.acceptanceRemark" type="textarea" :rows="2" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | status: 0, // æ¥ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenanceName: undefined, // 维修人 |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | form.maintenanceName = data.maintenanceName; |
| | | form.acceptanceName = data.acceptanceName; |
| | | form.acceptanceTime = data.acceptanceTime; |
| | | form.acceptanceRemark = data.acceptanceRemark; |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-if="row.status === 1" type="success">å®ç»</el-tag> |
| | | <el-tag v-if="row.status === 3" type="info">å¾
éªæ¶</el-tag> |
| | | <el-tag v-if="row.status === 0" type="warning">å¾
ç»´ä¿®</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status === 1 || row.status === 3" |
| | | @click="editRepair(row.id)" |
| | | > |
| | | ç¼è¾ |
| | |
| | | <el-button |
| | | type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status !== 0" |
| | | @click="addMaintain(row)" |
| | | > |
| | | ç»´ä¿® |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | link |
| | | :disabled="row.status !== 3" |
| | | @click="openAcceptance(row)" |
| | | > |
| | | éªæ¶ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | :disabled="row.status === 1" |
| | | :disabled="row.status === 1 || row.status === 3" |
| | | @click="delRepairByIds(row.id)" |
| | | > |
| | | å é¤ |
| | |
| | | </div> |
| | | <RepairModal ref="repairModalRef" @ok="getTableData"/> |
| | | <MaintainModal ref="maintainModalRef" @ok="getTableData"/> |
| | | <AcceptanceModal ref="acceptanceModalRef" @ok="getTableData"/> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" :record-type="'device_repair'" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import MaintainModal from "./Modal/MaintainModal.vue"; |
| | | import AcceptanceModal from "./Modal/AcceptanceModal.vue"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | |
| | | // æ¨¡ææ¡å®ä¾ |
| | | const repairModalRef = ref(); |
| | | const maintainModalRef = ref(); |
| | | const acceptanceModalRef = ref(); |
| | | |
| | | // è¡¨æ ¼å¤éæ¡éä¸é¡¹ |
| | | const multipleList = ref([]); |
| | |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "项ç®", |
| | | label: "æ¥ä¿®é¡¹ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | }, |
| | |
| | | align: "center", |
| | | prop: "maintenanceTime", |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | { |
| | | label: "éªæ¶äºº", |
| | | align: "center", |
| | | prop: "acceptanceName", |
| | | }, |
| | | { |
| | | label: "éªæ¶æ¶é´", |
| | | align: "center", |
| | | prop: "acceptanceTime", |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : ""), |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | // æå¼éªæ¶å¼¹çª |
| | | const openAcceptance = (row) => { |
| | | acceptanceModalRef.value.open(row); |
| | | }; |
| | | |
| | | const changePage = ({page, limit}) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="visible" |
| | | :title="'设å¤ä¿å
»'" |
| | | width="500px" |
| | | @confirm="sendForm" |
| | | @cancel="handleCancel" |
| | | @close="handleClose" |
| | | > |
| | | <el-form :model="form" label-width="100px"> |
| | | <FormDialog v-model="visible" |
| | | :title="'设å¤ä¿å
»'" |
| | | width="500px" |
| | | @confirm="sendForm" |
| | | @cancel="handleCancel" |
| | | @close="handleClose"> |
| | | <el-form :model="form" |
| | | label-width="100px"> |
| | | <el-form-item label="å®é
ä¿å
»äºº"> |
| | | <el-input |
| | | v-model="form.maintenanceActuallyName" |
| | | placeholder="请è¾å
¥å®é
ä¿å
»äºº" |
| | | ></el-input> |
| | | <el-input v-model="form.maintenanceActuallyName" |
| | | placeholder="请è¾å
¥å®é
ä¿å
»äºº"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å®é
ä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | | v-model="form.maintenanceActuallyTime" |
| | | placeholder="è¯·éæ©å®é
ä¿å
»æ¥æ" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | type="datetime" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | <el-date-picker v-model="form.maintenanceActuallyTime" |
| | | placeholder="è¯·éæ©å®é
ä¿å
»æ¥æ" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | type="datetime" |
| | | clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
»ç¶æ"> |
| | | <el-select v-model="form.status"> |
| | | <el-option label="å¾
ä¿å
»" :value="0"></el-option> |
| | | <el-option label="å®ç»" :value="1"></el-option> |
| | | <el-option label="失败" :value="2"></el-option> |
| | | <el-option label="å¾
ä¿å
»" |
| | | :value="0"></el-option> |
| | | <el-option label="å®ç»" |
| | | :value="1"></el-option> |
| | | <el-option label="失败" |
| | | :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
ȍȾ"> |
| | | <el-input |
| | | v-model="form.maintenanceResult" |
| | | placeholder="请è¾å
¥ä¿å
ȍȾ" |
| | | type="text" /> |
| | | <el-input v-model="form.maintenanceResult" |
| | | placeholder="请è¾å
¥ä¿å
ȍȾ" |
| | | type="text" /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤å¤ä»¶"> |
| | | <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="è¯·éæ©è®¾å¤å¤ä»¶" multiple filterable> |
| | | <el-option |
| | | v-for="item in sparePartOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | <el-select v-model="form.sparePartsIds" |
| | | :loading="loadingSparePartOptions" |
| | | placeholder="è¯·éæ©è®¾å¤å¤ä»¶" |
| | | multiple |
| | | filterable> |
| | | <el-option v-for="item in sparePartOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="selectedSpareParts.length" label="é¢ç¨æ°é"> |
| | | <el-form-item v-if="selectedSpareParts.length" |
| | | label="é¢ç¨æ°é"> |
| | | <div style="width: 100%"> |
| | | <div |
| | | v-for="item in selectedSpareParts" |
| | | :key="item.id" |
| | | style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;" |
| | | > |
| | | <div v-for="item in selectedSpareParts" |
| | | :key="item.id" |
| | | style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;"> |
| | | <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
| | | {{ item.name }} |
| | | <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;"> |
| | | <span v-if="item.quantity !== null && item.quantity !== undefined" |
| | | style="color: #909399;"> |
| | | ï¼åºåï¼{{ item.quantity }}ï¼ |
| | | </span> |
| | | </div> |
| | | <el-input-number |
| | | v-model="sparePartQtyMap[item.id]" |
| | | :min="1" |
| | | :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 180px" |
| | | /> |
| | | <el-input-number v-model="sparePartQtyMap[item.id]" |
| | | :min="1" |
| | | :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 180px" /> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="éä»¶"> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { addMaintenance } from "@/api/equipmentManagement/upkeep"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import dayjs from "dayjs"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { ElMessage } from "element-plus"; |
| | | import {computed, ref} from "vue"; |
| | | import {getSparePartsList} from "@/api/equipmentManagement/spareParts.js"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { addMaintenance } from "@/api/equipmentManagement/upkeep"; |
| | | import useFormData from "@/hooks/useFormData"; |
| | | import dayjs from "dayjs"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { computed, ref, nextTick, getCurrentInstance } from "vue"; |
| | | import { getSparePartsList } from "@/api/equipmentManagement/spareParts.js"; |
| | | |
| | | defineOptions({ |
| | | name: "ä¿å
»æ¨¡ææ¡", |
| | | }); |
| | | defineOptions({ |
| | | name: "ä¿å
»æ¨¡ææ¡", |
| | | }); |
| | | |
| | | const emits = defineEmits(["ok"]); |
| | | const emits = defineEmits(["ok"]); |
| | | |
| | | // ä¿å计åä¿å
»è®°å½çid |
| | | const planId = ref(); |
| | | const visible = ref(false); |
| | | const loading = ref(false); |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | // ä¿å计åä¿å
»è®°å½çid |
| | | const planId = ref(); |
| | | const visible = ref(false); |
| | | const loading = ref(false); |
| | | const userStore = useUserStore(); |
| | | |
| | | const { form, resetForm } = useFormData({ |
| | | maintenanceActuallyName: undefined, // å®é
ä¿å
»äºº |
| | | maintenanceActuallyTime: undefined, // å®é
ä¿å
»æ¥æ |
| | | maintenanceResult: undefined, // ä¿å
ȍȾ |
| | | status: 0, // ä¿å
»ç¶æ |
| | | sparePartsIds: [], |
| | | }); |
| | | const { form, resetForm } = useFormData({ |
| | | maintenanceActuallyName: undefined, // å®é
ä¿å
»äºº |
| | | maintenanceActuallyTime: undefined, // å®é
ä¿å
»æ¥æ |
| | | maintenanceResult: undefined, // ä¿å
ȍȾ |
| | | status: 0, // ä¿å
»ç¶æ |
| | | sparePartsIds: [], |
| | | storageBlobDTOs: [], |
| | | }); |
| | | |
| | | const sparePartOptions = ref([]) |
| | | const loadingSparePartOptions = ref(true) |
| | | const sparePartQtyMap = ref({}) |
| | | const sparePartOptions = ref([]); |
| | | const loadingSparePartOptions = ref(true); |
| | | const sparePartQtyMap = ref({}); |
| | | |
| | | const selectedSpareParts = computed(() => { |
| | | const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; |
| | | const set = new Set(ids.map((i) => String(i))); |
| | | return (sparePartOptions.value || []).filter((p) => set.has(String(p.id))); |
| | | }); |
| | | const selectedSpareParts = computed(() => { |
| | | const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; |
| | | const set = new Set(ids.map(i => String(i))); |
| | | return (sparePartOptions.value || []).filter(p => set.has(String(p.id))); |
| | | }); |
| | | |
| | | const setForm = (data) => { |
| | | form.maintenanceActuallyName = |
| | | data.maintenanceActuallyName ?? userStore.nickName; |
| | | form.maintenanceActuallyTime = |
| | | data.maintenanceActuallyTime |
| | | const setForm = data => { |
| | | form.maintenanceActuallyName = |
| | | data.maintenanceActuallyName ?? userStore.nickName; |
| | | form.maintenanceActuallyTime = data.maintenanceActuallyTime |
| | | ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss") |
| | | : dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.status = 1; // é»è®¤ç¶æä¸ºå®ç» |
| | | // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" |
| | | if (Array.isArray(data?.sparePartsIds)) { |
| | | form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "string") { |
| | | form.sparePartsIds = data.sparePartsIds |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.status = 1; // é»è®¤ç¶æä¸ºå®ç» |
| | | // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" |
| | | if (Array.isArray(data?.sparePartsIds)) { |
| | | form.sparePartsIds = data.sparePartsIds |
| | | .map(v => Number(v)) |
| | | .filter(v => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "string") { |
| | | form.sparePartsIds = data.sparePartsIds |
| | | .split(",") |
| | | .map((s) => Number(String(s).trim())) |
| | | .filter((v) => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "number") { |
| | | form.sparePartsIds = [data.sparePartsIds]; |
| | | } else { |
| | | form.sparePartsIds = []; |
| | | } |
| | | }; |
| | | .map(s => Number(String(s).trim())) |
| | | .filter(v => Number.isFinite(v)); |
| | | } else if (typeof data?.sparePartsIds === "number") { |
| | | form.sparePartsIds = [data.sparePartsIds]; |
| | | } else { |
| | | form.sparePartsIds = []; |
| | | } |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | }; |
| | | |
| | | /** |
| | | * @desc ä¿åä¿å
» |
| | | */ |
| | | const sendForm = async () => { |
| | | loading.value = true; |
| | | try { |
| | | // é¢ç¨æ°éæ ¡éª |
| | | if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { |
| | | for (const partId of form.sparePartsIds) { |
| | | const qty = Number(sparePartQtyMap.value?.[partId]); |
| | | if (!Number.isFinite(qty) || qty <= 0) { |
| | | proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); |
| | | return; |
| | | } |
| | | const part = sparePartOptions.value.find((p) => String(p.id) === String(partId)); |
| | | const stock = part?.quantity; |
| | | if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) { |
| | | if (qty > Number(stock)) { |
| | | proxy?.$modal?.msgError?.(`å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶
è¿åºåï¼${stock}ï¼`); |
| | | /** |
| | | * @desc ä¿åä¿å
» |
| | | */ |
| | | const sendForm = async () => { |
| | | loading.value = true; |
| | | try { |
| | | // é¢ç¨æ°éæ ¡éª |
| | | if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { |
| | | for (const partId of form.sparePartsIds) { |
| | | const qty = Number(sparePartQtyMap.value?.[partId]); |
| | | if (!Number.isFinite(qty) || qty <= 0) { |
| | | proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); |
| | | return; |
| | | } |
| | | const part = sparePartOptions.value.find( |
| | | p => String(p.id) === String(partId) |
| | | ); |
| | | const stock = part?.quantity; |
| | | if ( |
| | | stock !== null && |
| | | stock !== undefined && |
| | | Number.isFinite(Number(stock)) |
| | | ) { |
| | | if (qty > Number(stock)) { |
| | | proxy?.$modal?.msgError?.( |
| | | `å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶
è¿åºåï¼${stock}ï¼` |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | const data = { |
| | | id: planId.value, |
| | | ...form, |
| | | sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", |
| | | sparePartsQty: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",") |
| | | const data = { |
| | | id: planId.value, |
| | | ...form, |
| | | sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", |
| | | sparePartsQty: form.sparePartsIds |
| | | ? form.sparePartsIds |
| | | .map(id => sparePartQtyMap.value?.[id] ?? 1) |
| | | .join(",") |
| | | : "", |
| | | sparePartsUseList: form.sparePartsIds |
| | | ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 })) |
| | | sparePartsUseList: form.sparePartsIds |
| | | ? form.sparePartsIds.map(id => ({ |
| | | id, |
| | | quantity: sparePartQtyMap.value?.[id] ?? 1, |
| | | })) |
| | | : [], |
| | | }; |
| | | const { code } = await addMaintenance(data); |
| | | if (code == 200) { |
| | | ElMessage.success("ä¿å
»æå"); |
| | | emits("ok"); |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | const { code } = await addMaintenance(data); |
| | | if (code == 200) { |
| | | ElMessage.success("ä¿å
»æå"); |
| | | emits("ok"); |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const fetchSparePartOptions = () => { |
| | | loadingSparePartOptions.value = true; |
| | | // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records |
| | | getSparePartsList({ current: 1, size: 1000 }) |
| | | .then((res) => { |
| | | const fetchSparePartOptions = () => { |
| | | loadingSparePartOptions.value = true; |
| | | // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records |
| | | getSparePartsList({ current: 1, size: 1000 }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | sparePartOptions.value = res?.data?.records || []; |
| | | } else { |
| | |
| | | .finally(() => { |
| | | loadingSparePartOptions.value = false; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | const handleCancel = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | const handleClose = () => { |
| | | resetForm(); |
| | | sparePartQtyMap.value = {}; |
| | | visible.value = false; |
| | | }; |
| | | |
| | | const open = async (id, row) => { |
| | | planId.value = id; // ä¿å计åä¿å
»è®°å½çid |
| | | visible.value = true; |
| | | await nextTick(); |
| | | fetchSparePartOptions() |
| | | setForm(row); |
| | | }; |
| | | const open = async (id, row) => { |
| | | planId.value = id; // ä¿å计åä¿å
»è®°å½çid |
| | | visible.value = true; |
| | | await nextTick(); |
| | | fetchSparePartOptions(); |
| | | setForm(row); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®"> |
| | | <el-form-item label="ä¿å
»é¡¹ç®"> |
| | | <el-input |
| | | v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥é¡¹ç®" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å½å
¥äºº"> |
| | |
| | | <el-option label="å®ç»" :value="1"></el-option> |
| | | <el-option label="失败" :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
»äºº"> |
| | | <el-input |
| | | v-model="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äººå§å" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="计åä¿å
»æ¥æ"> |
| | | <el-date-picker |
| | |
| | | status: 0, //ä¿ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.createUser = Number(data.createUser); |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.maintenancePerson = data.maintenancePerson; |
| | | if (data.maintenancePlanTime) { |
| | | form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤é¡¹ç®" prop="machineryCategory"> |
| | | <el-form-item label="ä¿å
»é¡¹ç®" prop="machineryCategory"> |
| | | <el-input |
| | | v-model.trim="form.machineryCategory" |
| | | placeholder="请è¾å
¥è®¾å¤é¡¹ç®" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿å
»äºº" prop="maintenancePerson"> |
| | | <el-input |
| | | v-model.trim="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äººå§å" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | |
| | | week: '', |
| | | time: '', |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | registrationDate: '' |
| | | registrationDate: '', |
| | | maintenancePerson: '' // ä¿å
»äºº |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], |
| | | inspector: [{ required: true, message: "è¯·éæ©å½å
¥äºº", trigger: "blur" },], |
| | | registrationDate: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }], |
| | | machineryCategory: [{ required: true, message: "请è¾å
¥è®¾å¤é¡¹ç®", trigger: "blur" }] |
| | | machineryCategory: [{ required: true, message: "请è¾å
¥ä¿å
»é¡¹ç®", trigger: "blur" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | |
| | | week: '', |
| | | time: '', |
| | | deviceModel: undefined, |
| | | registrationDate: '' |
| | | registrationDate: '', |
| | | maintenancePerson: '' |
| | | } |
| | | } |
| | | |
| | |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <!-- 宿¶ä»»å¡ç®¡çtab --> |
| | | <el-tab-pane label="宿¶ä»»å¡ç®¡ç" |
| | | <!-- ä¿å
»ä»»å¡tab --> |
| | | <el-tab-pane label="ä¿å
»ä»»å¡" |
| | | name="scheduled"> |
| | | <div class="search_form"> |
| | | <el-form :model="scheduledFilters" |
| | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <el-text class="mx-1" |
| | | size="large">宿¶ä»»å¡ç®¡ç</el-text> |
| | | size="large">ä¿å
»ä»»å¡</el-text> |
| | | <div> |
| | | <el-button type="primary" |
| | | icon="Plus" |
| | |
| | | </PIMTable> |
| | | </div> |
| | | </el-tab-pane> |
| | | <!-- ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ --> |
| | | <el-tab-pane label="ä»»å¡è®°å½" |
| | | <!-- ä¿å
»è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ --> |
| | | <el-tab-pane label="ä¿å
»è®°å½" |
| | | name="record"> |
| | | <div class="search_form"> |
| | | <el-form :model="filters" |
| | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <el-text class="mx-1" |
| | | size="large">ä»»å¡è®°å½</el-text> |
| | | size="large">ä¿å
»è®°å½</el-text> |
| | | <div> |
| | | <el-button type="success" |
| | | icon="Van" |
| | |
| | | const fileDialogVisible = ref(false); |
| | | const currentMaintenanceTaskId = ref(null); |
| | | |
| | | // ä»»å¡è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | // ä¿å
»è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | | deviceName: "", |
| | | maintenancePlanTime: "", |
| | |
| | | }); |
| | | const multipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çtabç¸å
³åé |
| | | // ä¿å
»ä»»å¡tabç¸å
³åé |
| | | const scheduledFilters = reactive({ |
| | | taskName: "", |
| | | status: "", |
| | |
| | | }); |
| | | const scheduledMultipleList = ref([]); |
| | | |
| | | // 宿¶ä»»å¡ç®¡çè¡¨æ ¼åé
ç½® |
| | | // ä¿å
»ä»»å¡è¡¨æ ¼åé
ç½® |
| | | const scheduledColumns = ref([ |
| | | { prop: "taskName", label: "设å¤åç§°" }, |
| | | { |
| | |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "设å¤é¡¹ç®", |
| | | label: "ä¿å
»é¡¹ç®", |
| | | prop: "machineryCategory", |
| | | minWidth: 120, |
| | | formatData: cell => cell || "--", |
| | |
| | | ); |
| | | }, |
| | | }, |
| | | { prop: "maintenancePerson", label: "ä¿å
»äºº", minWidth: 100 }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "registrationDate", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | { |
| | |
| | | }, |
| | | ]); |
| | | |
| | | // ä»»å¡è®°å½è¡¨æ ¼åé
ç½®ï¼å设å¤ä¿å
»è¡¨æ ¼åï¼ |
| | | // ä¿å
»è®°å½è¡¨æ ¼åé
ç½®ï¼å设å¤ä¿å
»è¡¨æ ¼åï¼ |
| | | const columns = ref([ |
| | | { |
| | | label: "设å¤åç§°", |
| | |
| | | prop: "createUserName", |
| | | }, |
| | | { |
| | | label: "设å¤é¡¹ç®", |
| | | label: "ä¿å
»é¡¹ç®", |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | formatData: cell => cell || "--", |
| | |
| | | } |
| | | }; |
| | | |
| | | // 宿¶ä»»å¡ç®¡çç¸å
³æ¹æ³ |
| | | // ä¿å
»ä»»å¡ç¸å
³æ¹æ³ |
| | | const getScheduledTableData = async () => { |
| | | try { |
| | | const params = { |
| | |
| | | ElMessage.info("导åºå®æ¶ä»»å¡åè½å¾
å®ç°"); |
| | | }; |
| | | |
| | | // ä»»å¡è®°å½ç¸å
³æ¹æ³ï¼å设å¤ä¿å
»é¡µé¢æ¹æ³ï¼ |
| | | // ä¿å
»è®°å½ç¸å
³æ¹æ³ï¼å设å¤ä¿å
»é¡µé¢æ¹æ³ï¼ |
| | | const getTableData = async () => { |
| | | try { |
| | | const params = { |
| | |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢èµäº§</el-button> |
| | | <el-button type="warning" @click="handleDepreciation" icon="Money">ææ§è®¡æ</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢èµäº§</el-button> |
| | | <el-button type="warning" @click="handleAmortization" icon="Money">æé计æ</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | |
| | | <el-button type="primary" |
| | | @click="add" |
| | | icon="Plus">æ°å¢</el-button> |
| | | <el-button @click="handleOut" |
| | | icon="Download">导åº</el-button> |
| | | <!-- <el-button @click="handleOut" |
| | | icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <el-table ref="tableRef" |
| | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">æå°</el-button> |
| | | <!-- <el-button @click="handlePrint" icon="Printer">æå°</el-button>--> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">æå°</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <!-- <el-button @click="handlePrint" icon="Printer">æå°</el-button>--> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="voucherNo" label="åè¯åå·" width="120" /> |
| | | <el-table-column prop="summary" label="æè¦" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="åæ¹" width="150"> |
| | | <el-table-column prop="date" label="æ¥æ"/> |
| | | <!-- <el-table-column prop="voucherNo" label="åè¯åå·" width="120" /> --> |
| | | <!-- <el-table-column prop="summary" label="æè¦" min-width="200" show-overflow-tooltip /> --> |
| | | <el-table-column prop="debit" label="åæ¹"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">Â¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="è´·æ¹" width="150"> |
| | | <el-table-column prop="credit" label="è´·æ¹"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ¹å" width="80"> |
| | | <el-table-column label="æ¹å"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === 'å' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä½é¢" width="150"> |
| | | <el-table-column label="ä½é¢"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">Â¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="åæ¹å计" :value="totalDebit" precision="2" prefix="Â¥" /> |
| | | <el-statistic title="è´·æ¹å计" :value="totalCredit" precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | <el-statistic title="åæ¹å计" :value="totalDebit" :precision="2" prefix="Â¥" /> |
| | | <el-statistic title="è´·æ¹å计" :value="totalCredit" :precision="2" prefix="Â¥" style="margin-left: 30px;" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢åè¯</el-button> |
| | | <el-button @click="handleImport" icon="Upload">导å
¥</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <!-- <el-button @click="handleImport" icon="Upload">导å
¥</el-button> --> |
| | | <!-- <el-button @click="handleOut" icon="Download">导åº</el-button> --> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | |
| | | <span class="label">åè¯åï¼</span> |
| | | <el-select v-model="form.voucherPrefix" :disabled="isViewMode" style="width: 70px;"> |
| | | <el-option label="è®°" value="è®°" /> |
| | | <el-option label="ç°" value="ç°" /> |
| | | <el-option label="é¶" value="é¶" /> |
| | | <el-option label="转" value="转" /> |
| | | <el-option label="æ¶" value="æ¶" /> |
| | | <el-option label="ä»" value="ä»" /> |
| | | </el-select> |
| | | <el-input v-model="form.voucherNum" :disabled="isViewMode" style="width: 60px;" /> |
| | | <span class="label" style="margin-left: 5px;">å·</span> |
| | |
| | | <span class="label">éä»¶ï¼</span> |
| | | <el-input-number v-model="form.attachmentCount" :disabled="isViewMode" :min="0" :controls="false" style="width: 60px;" /> |
| | | <span class="label" style="margin-left: 5px;">å¼ </span> |
| | | <el-button type="primary" link :disabled="isViewMode" style="margin-left: 10px;">ä¸ä¼ æä»¶</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="voucher-table"> |
| | |
| | | @change="(val) => handleSubjectChange(val, rowIndex)" |
| | | @focus="selectRow(rowIndex)" |
| | | /> |
| | | <div class="subject-name">{{ entry.subjectName }}</div> |
| | | <!-- <div class="subject-name">{{ entry.subjectName }}</div> --> |
| | | </td> |
| | | <!-- åæ¹11å --> |
| | | <template v-if="editingCell.row === rowIndex && editingCell.type === 'debit'"> |
| | | <td colspan="11" class="debit-input-cell"> |
| | | <el-input-number ref="amountInputRef" v-model="entry.debit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | <el-input-number ref="amountInputRef" v-model="entry.debit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" :value-on-clear="undefined" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | </td> |
| | | </template> |
| | | <template v-else> |
| | |
| | | <!-- è´·æ¹11å --> |
| | | <template v-if="editingCell.row === rowIndex && editingCell.type === 'credit'"> |
| | | <td colspan="11" class="credit-input-cell"> |
| | | <el-input-number ref="amountInputRef" v-model="entry.credit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | <el-input-number ref="amountInputRef" v-model="entry.credit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" :value-on-clear="undefined" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | </td> |
| | | </template> |
| | | <template v-else> |
| | |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <!-- ç¼è¾æ¨¡å¼ï¼ä½¿ç¨ AttachmentUploadFile ä¸ä¼ ç»ä»¶ --> |
| | | <div class="voucher-attachment-upload" v-if="!isViewMode"> |
| | | <div class="attachment-label">éä»¶ä¸ä¼ ï¼</div> |
| | | <AttachmentUploadFile |
| | | v-model:fileList="form.attachments" |
| | | :disabled="isViewMode" |
| | | :limit="10" |
| | | :fileSize="50" |
| | | buttonText="ç¹å»ä¸ä¼ éä»¶" |
| | | @change="handleAttachmentChange" |
| | | /> |
| | | </div> |
| | | </el-form> |
| | | <!-- æ¥ç模å¼ï¼å±ç¤ºéä»¶åè¡¨ï¼æ¾å¨ el-form å¤é¢ï¼é¿å
被 disabledï¼ --> |
| | | <div class="voucher-attachment-upload" v-if="isViewMode && form.attachments?.length"> |
| | | <div class="attachment-label">éä»¶å表ï¼</div> |
| | | <el-table :data="form.attachments" border class="attachment-table"> |
| | | <el-table-column label="éä»¶åç§°" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || 'æªå½åæä»¶' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="previewFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="primary" size="small" @click="downloadFile(scope.row)">ä¸è½½</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div> |
| | |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | | <!-- æä»¶é¢è§ç»ä»¶ --> |
| | | <FilePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref, reactive, onMounted, computed, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import AttachmentUploadFile from "@/components/AttachmentUpload/file/index.vue"; |
| | | import FileList from "@/components/Dialog/FileList.vue"; |
| | | import FilePreview from "@/components/filePreview/index.vue"; |
| | | import download from "@/plugins/download.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { listAccountSubject } from "@/api/financialManagement/accountSubject"; |
| | |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | const isViewMode = computed(() => dialogMode.value === "view"); |
| | | const filePreviewRef = ref(null); |
| | | |
| | | const fallbackSubjectTree = [ |
| | | { subjectCode: "1001", subjectName: "åºåç°é", balanceDirection: "åæ¹", children: [] }, |
| | |
| | | subjectName: "", |
| | | balanceDirection: "", |
| | | summary: "", |
| | | debit: 0, |
| | | credit: 0, |
| | | debit: undefined, |
| | | credit: undefined, |
| | | }); |
| | | |
| | | const createDefaultForm = () => ({ |
| | |
| | | voucherNum: "", |
| | | voucherDate: "", |
| | | attachmentCount: 0, |
| | | attachments: [], |
| | | entries: [createEmptyEntry(), createEmptyEntry()], |
| | | creator: getDefaultCreator(), |
| | | remark: "", |
| | |
| | | form.entries.push(createEmptyEntry()); |
| | | }; |
| | | |
| | | const handleAttachmentChange = (fileList) => { |
| | | form.attachmentCount = fileList?.length || 0; |
| | | }; |
| | | |
| | | // 使ç¨é¡¹ç®å°è£
ç filePreview ç»ä»¶é¢è§æä»¶ |
| | | const previewFile = (row) => { |
| | | const url = row.previewURL || row.previewUrl || row.url; |
| | | if (url && filePreviewRef.value) { |
| | | filePreviewRef.value.open(url); |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³é¢è§'); |
| | | } |
| | | }; |
| | | |
| | | // 使ç¨é¡¹ç®å°è£
ç download æä»¶ä¸è½½æä»¶ |
| | | const downloadFile = (row) => { |
| | | const url = row.downloadURL || row.downloadUrl || row.url; |
| | | if (url) { |
| | | const filename = row.originalFilename || row.name || row.fileName || 'download'; |
| | | download.byUrl(url, filename); |
| | | } else { |
| | | ElMessage.warning('æä»¶å°åæ æï¼æ æ³ä¸è½½'); |
| | | } |
| | | }; |
| | | |
| | | const selectRow = (index) => { |
| | | selectedRowIndex.value = index; |
| | | }; |
| | |
| | | const { data } = await getVoucherDetail(row.id); |
| | | const detail = data || row; |
| | | const parts = (detail.voucherNo || "").split("-"); |
| | | Object.assign(form, createDefaultForm(), detail, { |
| | | const attachments = detail.storageBlobVOList || detail.storageBlobDTOs || detail.attachments || []; |
| | | Object.assign(form, createDefaultForm(), { |
| | | ...detail, |
| | | voucherPrefix: parts[0] || "è®°", |
| | | voucherNum: parts[1] || "", |
| | | creator: detail.creator || getDefaultCreator(), |
| | | attachments, |
| | | entries: |
| | | detail.entries?.map(item => ({ |
| | | subjectCode: item.subjectCode || "", |
| | |
| | | remark: form.remark, |
| | | debit: totalDebitEntry.value, |
| | | credit: totalCreditEntry.value, |
| | | storageBlobDTOs: form.attachments || [], |
| | | entries: validEntries.map(entry => ({ |
| | | subjectCode: entry.subjectCode, |
| | | subjectName: entry.subjectName, |
| | |
| | | } |
| | | } |
| | | |
| | | .voucher-attachment-upload { |
| | | margin-top: 15px; |
| | | padding: 0 10px; |
| | | |
| | | .attachment-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .attachment-table { |
| | | border-radius: 4px; |
| | | } |
| | | } |
| | | |
| | | .voucher-table { |
| | | border: 1px solid #dcdfe6; |
| | | border-right: none; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog |
| | | v-model="isShow" |
| | | title="åºå详æ
" |
| | | width="90%" |
| | | top="3vh" |
| | | class="batch-no-qty-detail-dialog" |
| | | @close="closeModal" |
| | | > |
| | | <div class="detail-content"> |
| | | <div class="detail-table-wrapper"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%" |
| | | height="100%" |
| | | > |
| | | <el-table-column |
| | | label="产ååç§°" |
| | | prop="productName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" show-overflow-tooltip /> |
| | | <el-table-column label="æ¹å·" prop="batchNo" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="åæ ¼åºåæ°é" |
| | | prop="qualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸åæ ¼åºåæ°é" |
| | | prop="unQualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åæ ¼å»ç»æ°é" |
| | | prop="qualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸åæ ¼å»ç»æ°é" |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column fixed="right" label="æä½" min-width="180" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="handleSubtract(scope.row)" |
| | | :disabled=" |
| | | (scope.row.qualifiedUnLockedQuantity || 0) + |
| | | (scope.row.qualifiedPendingOutQuantity || 0) <= |
| | | 0 && |
| | | (scope.row.unQualifiedUnLockedQuantity || 0) + |
| | | (scope.row.unQualifiedPendingOutQuantity || 0) <= |
| | | 0 |
| | | " |
| | | >é¢ç¨</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | v-if=" |
| | | scope.row.unQualifiedUnLockedQuantity > 0 || |
| | | scope.row.qualifiedUnLockedQuantity > 0 |
| | | " |
| | | @click="handleFrozen(scope.row)" |
| | | >å»ç»</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | v-if=" |
| | | scope.row.qualifiedLockedQuantity > 0 || |
| | | scope.row.unQualifiedLockedQuantity > 0 |
| | | " |
| | | @click="handleThaw(scope.row)" |
| | | >è§£å»</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { computed, reactive, ref, watch } from "vue"; |
| | | import { getStockInventoryBatchNoQty } from "@/api/inventoryManagement/stockInventory.js"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | | required: true, |
| | | }, |
| | | record: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["update:visible", "subtract", "frozen", "thaw"]); |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | | return props.visible; |
| | | }, |
| | | set(val) { |
| | | emit("update:visible", val); |
| | | }, |
| | | }); |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | }); |
| | | |
| | | const getList = () => { |
| | | if (!props.record?.productId || !props.record?.productModelId) { |
| | | tableData.value = []; |
| | | total.value = 0; |
| | | return; |
| | | } |
| | | |
| | | tableLoading.value = true; |
| | | getStockInventoryBatchNoQty({ |
| | | current: page.current, |
| | | size: page.size, |
| | | productId: props.record.productId, |
| | | productModelId: props.record.productModelId, |
| | | }) |
| | | .then((res) => { |
| | | tableData.value = res.data?.records || []; |
| | | total.value = res.data?.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const handleSubtract = (row) => { |
| | | emit("subtract", row); |
| | | }; |
| | | |
| | | const handleFrozen = (row) => { |
| | | emit("frozen", row); |
| | | }; |
| | | |
| | | const handleThaw = (row) => { |
| | | emit("thaw", row); |
| | | }; |
| | | |
| | | const closeModal = () => { |
| | | isShow.value = false; |
| | | page.current = 1; |
| | | page.size = 20; |
| | | tableData.value = []; |
| | | total.value = 0; |
| | | }; |
| | | |
| | | watch( |
| | | () => props.visible, |
| | | (visible) => { |
| | | if (!visible) { |
| | | return; |
| | | } |
| | | page.current = 1; |
| | | getList(); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .detail-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: calc(100vh - 170px); |
| | | min-height: 520px; |
| | | } |
| | | |
| | | .detail-table-wrapper { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | :deep(.batch-no-qty-detail-dialog .el-dialog) { |
| | | max-width: calc(100vw - 48px); |
| | | } |
| | | |
| | | :deep(.batch-no-qty-detail-dialog .el-dialog__body) { |
| | | padding-top: 12px; |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form mb10"> |
| | | <div> |
| | | <span class="search_title ml10">产å大类ï¼</span> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable/> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="isShowNewModal = true">æ°å¢åºå</el-button> |
| | | <el-button type="info" plain icon="Upload" @click="isShowImportModal = true"> |
| | | <el-button type="primary" @click="isShowNewModal = true" |
| | | >æ°å¢åºå</el-button |
| | | > |
| | | <el-button |
| | | type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="isShowImportModal = true" |
| | | > |
| | | 导å
¥åºå |
| | | </el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="(row, index) => index" style="width: 100%" |
| | | :row-class-name="tableRowClassName" height="calc(100vh - 18.5em)"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row, index) => index" |
| | | style="width: 100%" |
| | | :row-class-name="tableRowClassName" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="产å大类" prop="productName" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="产ååç§°" |
| | | prop="productName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" show-overflow-tooltip /> |
| | | <el-table-column label="æ¹å·" prop="batchNo" show-overflow-tooltip /> |
| | | <el-table-column label="åæ ¼åºåæ°é" prop="qualifiedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="ä¸åæ ¼åºåæ°é" prop="unQualifiedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="åæ ¼å»ç»æ°é" prop="qualifiedLockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="ä¸åæ ¼å»ç»æ°é" prop="unQualifiedLockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" min-width="90" align="center"> |
| | | <el-table-column |
| | | label="åæ ¼åºåæ°é" |
| | | prop="qualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸åæ ¼åºåæ°é" |
| | | prop="unQualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åæ ¼å»ç»æ°é" |
| | | prop="qualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸åæ ¼å»ç»æ°é" |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="80" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="((scope.row.qualifiedUnLockedQuantity || 0) + (scope.row.qualifiedPendingOutQuantity || 0) <= 0) && ((scope.row.unQualifiedUnLockedQuantity || 0) + (scope.row.unQualifiedPendingOutQuantity || 0) <= 0)">é¢ç¨</el-button> |
| | | <el-button link type="primary" v-if="scope.row.unQualifiedUnLockedQuantity > 0 || scope.row.qualifiedUnLockedQuantity > 0" @click="showFrozenModal(scope.row)">å»ç»</el-button> |
| | | <el-button link type="primary" v-if="scope.row.qualifiedLockedQuantity > 0 || scope.row.unQualifiedLockedQuantity > 0" @click="showThawModal(scope.row)">è§£å»</el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="showDetailModal(scope.row)" |
| | | >详æ
</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | <new-stock-inventory v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | :top-product-parent-id="props.productId" |
| | | @completed="handleQuery" /> |
| | | <batch-no-qty-detail |
| | | v-if="isShowDetailModal" |
| | | v-model:visible="isShowDetailModal" |
| | | :record="record" |
| | | @subtract="handleDetailSubtract" |
| | | @frozen="handleDetailFrozen" |
| | | @thaw="handleDetailThaw" |
| | | /> |
| | | <new-stock-inventory |
| | | v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | :top-product-parent-id="props.productId" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <subtract-stock-inventory v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" /> |
| | | <subtract-stock-inventory |
| | | v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" |
| | | /> |
| | | <!-- 导å
¥åºå--> |
| | | <import-stock-inventory v-if="isShowImportModal" |
| | | v-model:visible="isShowImportModal" |
| | | type="qualified" |
| | | @uploadSuccess="handleQuery" /> |
| | | <import-stock-inventory |
| | | v-if="isShowImportModal" |
| | | v-model:visible="isShowImportModal" |
| | | type="qualified" |
| | | @uploadSuccess="handleQuery" |
| | | /> |
| | | <!-- å»ç»/è§£å»åºå--> |
| | | <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" /> |
| | | <frozen-and-thaw-stock-inventory |
| | | v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue' |
| | | import {ElMessage, ElMessageBox} from "element-plus"; |
| | | import { |
| | | getStockInventoryListPageCombined |
| | | } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js"; |
| | | const props = defineProps({ |
| | | productId: { |
| | | type: Number, |
| | | required: true, |
| | | default: 0 |
| | | } |
| | | default: 0, |
| | | }, |
| | | }); |
| | | |
| | | const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue")); |
| | | const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue")); |
| | | const ImportStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Import.vue")); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue")); |
| | | const { proxy } = getCurrentInstance() |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const record = ref({}) |
| | | const tableLoading = ref(false) |
| | | const NewStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/New.vue") |
| | | ); |
| | | const SubtractStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Subtract.vue") |
| | | ); |
| | | const ImportStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Import.vue") |
| | | ); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue") |
| | | ); |
| | | const BatchNoQtyDetail = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const record = ref({}); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | const total = ref(0) |
| | | }); |
| | | const total = ref(0); |
| | | // æ¯å¦æ¾ç¤ºæ°å¢å¼¹æ¡ |
| | | const isShowNewModal = ref(false) |
| | | const isShowNewModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ |
| | | const isShowSubtractModal = ref(false) |
| | | const isShowSubtractModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºå»ç»/è§£å»å¼¹æ¡ |
| | | const isShowFrozenAndThawModal = ref(false) |
| | | const isShowFrozenAndThawModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºè¯¦æ
å¼¹æ¡ |
| | | const isShowDetailModal = ref(false); |
| | | // æä½ç±»å |
| | | const operationType = ref('frozen') |
| | | const operationType = ref("frozen"); |
| | | // æ¯å¦æ¾ç¤ºå¯¼å
¥å¼¹æ¡ |
| | | const isShowImportModal = ref(false) |
| | | const isShowImportModal = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: '', |
| | | productName: "", |
| | | topParentProductId: props.productId, |
| | | } |
| | | }) |
| | | const { searchForm } = toRefs(data) |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList() |
| | | } |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | getStockInventoryListPageCombined({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false |
| | | tableData.value = res.data.records |
| | | total.value = res.data.total |
| | | // æ°æ®å è½½å®æåæ£æ¥åºå |
| | | // checkStockAndCreatePurchase(); |
| | | }).catch(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | tableLoading.value = true; |
| | | getStockInventoryListPageCombined({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | // æ°æ®å è½½å®æåæ£æ¥åºå |
| | | // checkStockAndCreatePurchase(); |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleFileSuccess = (response) => { |
| | | const { code, msg } = response; |
| | |
| | | |
| | | // ç¹å»é¢ç¨ |
| | | const showSubtractModal = (row) => { |
| | | record.value = row |
| | | isShowSubtractModal.value = true |
| | | } |
| | | record.value = row; |
| | | isShowSubtractModal.value = true; |
| | | }; |
| | | |
| | | // ç¹å»è¯¦æ
|
| | | const showDetailModal = (row) => { |
| | | if (!row?.productId || !row?.productModelId) { |
| | | proxy.$modal.msgError("å½åæ°æ®ç¼ºå°äº§åIDæè§æ ¼åå·ID"); |
| | | return; |
| | | } |
| | | record.value = row; |
| | | isShowDetailModal.value = true; |
| | | }; |
| | | |
| | | const handleDetailSubtract = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showSubtractModal(row); |
| | | }; |
| | | |
| | | const handleDetailFrozen = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showFrozenModal(row); |
| | | }; |
| | | |
| | | const handleDetailThaw = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showThawModal(row); |
| | | }; |
| | | |
| | | // ç¹å»å»ç» |
| | | const showFrozenModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'frozen' |
| | | } |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "frozen"; |
| | | }; |
| | | |
| | | // ç¹å»è§£å» |
| | | const showThawModal = (row) => { |
| | | record.value = row |
| | | isShowFrozenAndThawModal.value = true |
| | | operationType.value = 'thaw' |
| | | } |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "thaw"; |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | console.log('selection', selectedRows.value) |
| | | } |
| | | const expandedRowKeys = ref([]) |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | // è¡¨æ ¼è¡ç±»å |
| | | const tableRowClassName = ({ row }) => { |
| | | const stock = Number(row?.qualifiedUnLockedQuantity ?? 0); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | return ''; |
| | | return ""; |
| | | } |
| | | return stock < warn ? 'row-low-stock' : ''; |
| | | return stock < warn ? "row-low-stock" : ""; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm( |
| | | 'æ¯å¦ç¡®è®¤å¯¼åºï¼', |
| | | '导åº', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | } |
| | | ).then(() => { |
| | | proxy.download("/stockInventory/exportStockInventory", {topParentProductId: props.productId}, 'åºåä¿¡æ¯.xlsx') |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶") |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | } |
| | | .then(() => { |
| | | proxy.download( |
| | | "/stockInventory/exportStockInventory", |
| | | { topParentProductId: props.productId }, |
| | | "åºåä¿¡æ¯.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <renew-contract |
| | | v-if="isShowRenewContractModal" |
| | | v-model:visible="isShowRenewContractModal" |
| | | :id="id" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <!-- åå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref } from "vue"; |
| | | import { onMounted, ref, defineAsyncComponent } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | const RenewContract = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/RenewContract.vue")); |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import dayjs from "dayjs"; |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | width: 160, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç»ç¾åå", |
| | | type: "text", |
| | | showHide: row => row.staffState === 1, |
| | | clickFun: (row) => { |
| | | isShowRenewContractModal.value = true; |
| | | id.value = row.id; |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | const isShowRenewContractModal = ref(false); |
| | | const id = ref(0); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | > |
| | | <template #positiveDate="{ row }"> |
| | | <span :class="getPositiveDateClass(row.positiveDate)">{{ row.positiveDate }}</span> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <show-form-dia ref="formDia" @close="handleQuery"></show-form-dia> |
| | | <new-or-edit-form-dia ref="formDiaNewOrEditFormDia" @close="handleQuery"></new-or-edit-form-dia> |
| | | <renew-contract |
| | | v-if="isShowRenewContractModal" |
| | | v-model:visible="isShowRenewContractModal" |
| | | :id="id" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <!-- 导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> |
| | |
| | | |
| | | const NewOrEditFormDia = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue")); |
| | | const ShowFormDia = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/Show.vue")); |
| | | const RenewContract = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/RenewContract.vue")); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | deptOptions: [], |
| | | }); |
| | | const { searchForm, deptOptions } = toRefs(data); |
| | | const isShowRenewContractModal = ref(false); |
| | | const id = ref(0); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "è½¬æ£æ¥æ", |
| | | prop: "positiveDate", |
| | | width: 120, |
| | | dataType: "slot", |
| | | slot: "positiveDate", |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | |
| | | openFormNewOrEditFormDia("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç»ç¾åå", |
| | | type: "text", |
| | | showHide: row => row.staffState === 1, |
| | | clickFun: (row) => { |
| | | isShowRenewContractModal.value = true; |
| | | id.value = row.id; |
| | | }, |
| | | }, |
| | | // { |
| | | // name: "详æ
", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // openForm("edit", row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import" |
| | | }) |
| | | |
| | | // å¤æè½¬æ£æ¥ææ¯å¦å¨7天å
|
| | | const getPositiveDateClass = (positiveDate) => { |
| | | if (!positiveDate) return ''; |
| | | const today = new Date(); |
| | | today.setHours(0, 0, 0, 0); |
| | | const positive = new Date(positiveDate); |
| | | positive.setHours(0, 0, 0, 0); |
| | | const diffTime = positive - today; |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); |
| | | // 7天å
转æ£ï¼å
æ¬ä»å¤©ï¼æ¾ç¤ºè¦åè² |
| | | if (diffDays >= 0 && diffDays <= 7) { |
| | | return 'positive-date-warning'; |
| | | } |
| | | return ''; |
| | | }; |
| | | |
| | | const fetchDeptOptions = () => { |
| | | deptTreeSelect().then(response => { |
| | |
| | | .search_title2 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .positive-date-warning { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| | |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const recordId = ref(); |
| | | const fileListDialogVisible = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | @change="handleUnitQuantityChange(row)" |
| | | @change="handleUnitQuantityChange" |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> |
| | | :disabled="true" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const toQuantityNumber = value => { |
| | | const numberValue = Number(value); |
| | | if (!Number.isFinite(numberValue)) { |
| | | return 0; |
| | | } |
| | | return Number(numberValue.toFixed(2)); |
| | | }; |
| | | |
| | | const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => { |
| | | items.forEach(item => { |
| | | if (parentDemandedQuantity !== null) { |
| | | item.demandedQuantity = toQuantityNumber( |
| | | parentDemandedQuantity * toQuantityNumber(item.unitQuantity) |
| | | ); |
| | | } |
| | | |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | syncDemandedQuantityTree( |
| | | item.children, |
| | | toQuantityNumber(item.demandedQuantity) |
| | | ); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const recalculateDemandedQuantities = () => { |
| | | if (pageType.value !== "order") { |
| | | return; |
| | | } |
| | | |
| | | const rootDemandedQuantity = routeInfo.value.quantity; |
| | | if ( |
| | | rootDemandedQuantity === undefined || |
| | | rootDemandedQuantity === null || |
| | | rootDemandedQuantity === "" |
| | | ) { |
| | | syncDemandedQuantityTree(bomDataValue.value.dataList); |
| | | return; |
| | | } |
| | | |
| | | syncDemandedQuantityTree( |
| | | bomDataValue.value.dataList, |
| | | toQuantityNumber(rootDemandedQuantity) |
| | | ); |
| | | }; |
| | | |
| | | const processChange = value => { |
| | | processOptions.value.forEach(item => { |
| | | if (item.id == value) { |
| | |
| | | ); |
| | | bomDataValue.value.dataList = data || []; |
| | | normalizeTreeData(bomDataValue.value.dataList); |
| | | recalculateDemandedQuantities(); |
| | | } catch (err) { |
| | | console.error("è·åBOMæ°æ®å¤±è´¥ï¼", err); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | const handleUnitQuantityChange = row => { |
| | | if (routeInfo.value.quantity && routeInfo.value.quantity !== 0) { |
| | | row.demandedQuantity = (row.unitQuantity || 0) * routeInfo.value.quantity; |
| | | } |
| | | const handleUnitQuantityChange = () => { |
| | | recalculateDemandedQuantities(); |
| | | }; |
| | | |
| | | const addchildItem = (item, tempId) => { |
| | |
| | | "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: |
| | | routeInfo.value.quantity && routeInfo.value.quantity !== 0 |
| | | ? 1 * routeInfo.value.quantity |
| | | : 0, |
| | | demandedQuantity: 0, |
| | | children: [], |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | |
| | | "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: |
| | | routeInfo.value.quantity && routeInfo.value.quantity !== 0 |
| | | ? 1 * routeInfo.value.quantity |
| | | : 0, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | |
| | | console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList"); |
| | | |
| | | normalizeTreeData(bomDataValue.value.dataList); |
| | | recalculateDemandedQuantities(); |
| | | |
| | | const valid = validateAllBom(); |
| | | if (valid) { |
| | |
| | | .then(() => { |
| | | ElMessage.success("BOMä¿åæå"); |
| | | bomDataValue.value.isEdit = false; |
| | | fetchBomData(); |
| | | refreshCurrentPage(); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("BOMä¿å失败"); |
| | |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | const refreshCurrentPage = () => { |
| | | getRouteInfo(); |
| | | getList(); |
| | | getProcessList(); |
| | | fetchBomData(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | refreshCurrentPage(); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | @change="handleUnitQuantityChange" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :disabled="true" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | }); |
| | | }; |
| | | |
| | | const toQuantityNumber = (value: any) => { |
| | | const numberValue = Number(value); |
| | | if (!Number.isFinite(numberValue)) { |
| | | return 0; |
| | | } |
| | | return Number(numberValue.toFixed(2)); |
| | | }; |
| | | |
| | | const syncDemandedQuantityTree = ( |
| | | items: any[], |
| | | parentDemandedQuantity: number | null = null |
| | | ) => { |
| | | items.forEach((item: any) => { |
| | | if (parentDemandedQuantity !== null) { |
| | | item.demandedQuantity = toQuantityNumber( |
| | | parentDemandedQuantity * toQuantityNumber(item.unitQuantity) |
| | | ); |
| | | } |
| | | |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | syncDemandedQuantityTree( |
| | | item.children, |
| | | toQuantityNumber(item.demandedQuantity) |
| | | ); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const recalculateDemandedQuantities = () => { |
| | | if (!isOrderPage.value) { |
| | | return; |
| | | } |
| | | |
| | | syncDemandedQuantityTree(dataValue.dataList); |
| | | }; |
| | | |
| | | const buildSubmitTree = (items: any[]) => { |
| | | return items.map((item: any) => { |
| | | const current = { ...item }; |
| | |
| | | }); |
| | | }; |
| | | |
| | | const findSiblings = (items: any[], tempId: string): any[] | null => { |
| | | if (!items || items.length === 0) return null; |
| | | // æ£æ¥å½åå±çº§ |
| | | if (items.some(item => item.tempId === tempId)) { |
| | | return items; |
| | | } |
| | | // é彿¥æ¾å级 |
| | | for (const item of items) { |
| | | if (item.children && item.children.length > 0) { |
| | | const result = findSiblings(item.children, tempId); |
| | | if (result) return result; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const handleProcessChange = (row: any, value: any) => { |
| | | if (value) { |
| | | const siblings = findSiblings(dataValue.dataList, row.tempId); |
| | | if (siblings) { |
| | | const isDuplicate = siblings.some( |
| | | s => s.tempId !== row.tempId && s.processId === value |
| | | ); |
| | | if (isDuplicate) { |
| | | const option = getProcessOptionById(value); |
| | | const processName = option?.name || "该工åº"; |
| | | ElMessage.warning(`åä¸å±çº§ä¸ä¸è½éæ©éå¤çæ¶èå·¥åºï¼${processName}`); |
| | | row.processId = ""; |
| | | syncProcessOperationFields(row); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | row.processId = value || ""; |
| | | syncProcessOperationFields(row); |
| | | }; |
| | | |
| | | const handleUnitQuantityChange = () => { |
| | | recalculateDemandedQuantities(); |
| | | }; |
| | | |
| | | const tableData = reactive([ |
| | |
| | | const { data } = await listProcessBom({ orderId: routeOrderId.value }); |
| | | dataValue.dataList = (data as any) || []; |
| | | normalizeTreeData(dataValue.dataList); |
| | | recalculateDemandedQuantities(); |
| | | } else { |
| | | // é订åæ
åµï¼ä½¿ç¨åæ¥çæ¥å£ |
| | | const { data } = await queryList(routeId.value); |
| | |
| | | const validateAll = () => { |
| | | let isValid = true; |
| | | |
| | | // æ ¡éªä¸ç»å
å¼èç¹çå·¥åºæ¯å¦å¯ä¸ |
| | | const checkProcessUniqueness = (items: any[]) => { |
| | | if (!items || items.length === 0 || !isValid) return; |
| | | |
| | | const processIds = new Set(); |
| | | for (const item of items) { |
| | | if (item.processId) { |
| | | if (processIds.has(item.processId)) { |
| | | const option = getProcessOptionById(item.processId); |
| | | const processName = option?.name || item.processName || "æªç¥å·¥åº"; |
| | | ElMessage.error( |
| | | `产åã${item.productName}ãçæ¶èå·¥åºã${processName}ãå¨å½åå±çº§å·²åå¨ï¼è¯·å¿éå¤è®¾ç½®` |
| | | ); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | processIds.add(item.processId); |
| | | } |
| | | } |
| | | |
| | | // é彿 ¡éªå级çå
å¼èç¹ |
| | | for (const item of items) { |
| | | if (item.children && item.children.length > 0) { |
| | | checkProcessUniqueness(item.children); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // æ ¡éªå½æ° |
| | | const validateItem = (item: any, isTopLevel = false) => { |
| | | if (!isValid) return; |
| | | // æ ¡éªå½å项çå¿
å¡«åæ®µ |
| | | if (!item.model) { |
| | | ElMessage.error("è¯·éæ©è§æ ¼"); |
| | |
| | | // return; |
| | | // } |
| | | |
| | | // é彿 ¡éªå项 |
| | | // é彿 ¡éªå项忮µ |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(child => { |
| | | validateItem(child, false); |
| | |
| | | } |
| | | }; |
| | | |
| | | // éåææé¡¶å±é¡¹ |
| | | // 1. é¦å
æ ¡éªåä¸ç¶çº§ä¸çå屿¶èå·¥åºæ¯å¦å¯ä¸ |
| | | checkProcessUniqueness(dataValue.dataList); |
| | | if (!isValid) return false; |
| | | |
| | | // 2. ç¶åéåæ ¡éªææé¡¶å±é¡¹çåæ®µå¿
å¡«æ
åµ |
| | | dataValue.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | |
| | | const submit = () => { |
| | | dataValue.loading = true; |
| | | normalizeTreeData(dataValue.dataList); |
| | | recalculateDemandedQuantities(); |
| | | |
| | | // å
è¿è¡è¡¨åæ ¡éª |
| | | const valid = validateAll(); |
| | |
| | | |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | }); |
| | | </script> |
| | | </script> |
| | |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | :loading="materialSaving" |
| | | :disabled="isSaveDisabled" |
| | | @click="handleMaterialSave">ä¿å</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | |
| | | const materialTableLoading = ref(false); |
| | | const materialSaving = ref(false); |
| | | const materialTableData = ref([]); |
| | | |
| | | const isSaveDisabled = computed(() => { |
| | | if (materialTableData.value.length === 0) return true; |
| | | return !materialTableData.value.some(row => { |
| | | // æ£æ¥æ¯å¦æä»»ä½ç¨æ·è¾å
¥å
容 |
| | | const hasBatch = Array.isArray(row.batchNo) && row.batchNo.length > 0; |
| | | const hasPickQty = |
| | | row.pickQty !== null && row.pickQty !== undefined && row.pickQty !== 0; |
| | | |
| | | if (row.bom) { |
| | | // å¯¹äºæ¥èªBOMçè¡ï¼è¾å
¥æ¡åªæâæ¹å·âåâé¢ç¨æ°éâ |
| | | return hasBatch || hasPickQty; |
| | | } else { |
| | | // å¯¹äºæ°å¢è¡ï¼è¾å
¥æ¡å
æ¬âå·¥åºâãâåæâãâéæ±æ°éâãâæ¹å·âåâé¢ç¨æ°éâ |
| | | const hasOperation = !!row.operationName; |
| | | const hasMaterial = !!row.materialName; |
| | | const hasDemanded = |
| | | row.demandedQuantity !== null && |
| | | row.demandedQuantity !== undefined && |
| | | row.demandedQuantity !== 0; |
| | | return ( |
| | | hasBatch || hasPickQty || hasOperation || hasMaterial || hasDemanded |
| | | ); |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | const processOptions = ref([]); |
| | | const currentMaterialSelectRowIndex = ref(-1); |
| | | let materialTempId = 0; |
| | |
| | | <div class="card-body"> |
| | | <!-- <div class="process-name">{{ process.name }}</div> --> |
| | | <div class="process-desc">{{ process.remark || 'ææ æè¿°' }}</div> |
| | | <div class="process-device">å
³è设å¤: {{ deviceOptions.find(item => item.id === Number(process.deviceLedgerId))?.deviceName|| 'æªå
³è' }}</div> |
| | | <div class="process-device">å
³è设å¤: {{ (deviceOptions.find(item => item.id === Number(process.deviceLedgerId))?.deviceName) || 'æªå
³è' }}</div> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="status-tag"> |
| | |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼"> |
| | | <el-input v-model="selectedParam.standardValue" |
| | | @input="val => onStandardValueInput(val, selectedParam)" |
| | | placeholder="请è¾å
¥é»è®¤å¼" /> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <el-form-item label="æ åå¼" |
| | | prop="standardValue"> |
| | | <el-input v-model="editParamForm.standardValue" |
| | | @input="val => onStandardValueInput(val, editParamForm)" |
| | | placeholder="请è¾å
¥æ åå¼" /> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | technologyParamId: null, |
| | | paramName: "", |
| | | standardValue: null, |
| | | paramType: null, |
| | | }); |
| | | |
| | | const onStandardValueInput = (val, target) => { |
| | | const data = target.value || target; |
| | | const type = data.paramType; |
| | | if (type === 1) { |
| | | // æ°å¼æ ¼å¼ï¼ä¸è½è¾å
¥ä¸ææè±æå符 |
| | | data.standardValue = val.replace(/[a-zA-Z\u4e00-\u9fa5]/g, ""); |
| | | } |
| | | }; |
| | | |
| | | const editParamRules = { |
| | | standardValue: [ |
| | | { |
| | |
| | | if (value === null || value === undefined || value === "") { |
| | | callback(new Error("请è¾å
¥æ åå¼")); |
| | | } else { |
| | | const type = editParamForm.paramType; |
| | | if (type === 1 && value) { |
| | | if (/[a-zA-Z\u4e00-\u9fa5]/.test(value)) { |
| | | return callback(new Error("æ°å¼æ ¼å¼ä¸è½å
å«ä¸è±æå符")); |
| | | } |
| | | } |
| | | callback(); |
| | | } |
| | | }, |
| | |
| | | processForm.isQuality = !!process.isQuality; |
| | | processForm.isProduction = !!process.isProduction; |
| | | processForm.remark = process.remark || ""; |
| | | processForm.deviceLedgerId = Number(process.deviceLedgerId); |
| | | // å¦æè®¾å¤ ID 为 0 æè
å¨è®¾å¤åè¡¨ä¸æ¾ä¸å°ï¼ååæ¾ä¸ºç©ºï¼nullï¼ |
| | | const deviceId = Number(process.deviceLedgerId); |
| | | const hasDevice = deviceOptions.value.some(item => item.id === deviceId); |
| | | processForm.deviceLedgerId = deviceId && hasDevice ? deviceId : null; |
| | | processForm.type = process.type; |
| | | processDialogVisible.value = true; |
| | | }; |
| | |
| | | editParamForm.technologyParamId = row.technologyParamId; |
| | | editParamForm.paramName = row.paramName; |
| | | editParamForm.standardValue = row.standardValue; |
| | | editParamForm.paramType = row.paramType; |
| | | editParamDialogVisible.value = true; |
| | | }; |
| | | |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="ç产订å" /> |
| | | <el-card style="height:82vh;overflow:auto;"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | |
| | | } |
| | | } |
| | | currentReportRowData.value = row; |
| | | reportForm.planQuantity = row.planQuantity; |
| | | const planQuantity = Number(row.planQuantity || 0); |
| | | const completeQuantity = Number(row.completeQuantity || 0); |
| | | const remainingQuantity = Math.max(0, planQuantity - completeQuantity); |
| | | reportForm.planQuantity = remainingQuantity; |
| | | reportForm.quantity = |
| | | row.quantity !== undefined && row.quantity !== null ? row.quantity : null; |
| | | reportForm.productProcessRouteItemId = row.productProcessRouteItemId; |
| | |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢åºåæ£éª' : 'ç¼è¾åºåæ£éª'" |
| | | :title="operationType === 'add' ? 'æ°å¢åºåæ£éª' : operationType === 'view' ? 'æ¥çåºåæ£éª' : 'ç¼è¾åºåæ£éª'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | :disabled="isViewMode || operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="isViewMode || operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææ éæ©ï¼" prop="testStandardId"> |
| | | <el-select |
| | |
| | | clearable |
| | | @change="handleTestStandardChange" |
| | | style="width: 100%" |
| | | :disabled="isViewMode" |
| | | > |
| | | <el-option |
| | | v-for="item in testStandardOptions" |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ°éï¼" prop="quantity"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请è¾å
¥" clearable :precision="2" :disabled="quantityDisabled"/> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请è¾å
¥" clearable :precision="2" :disabled="isViewMode || processQuantityDisabled"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ ¼æ°éï¼" |
| | | prop="qualifiedQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.qualifiedQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | @change="handleQualifiedQuantityChange" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸åæ ¼æ°éï¼" |
| | | prop="unqualifiedQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.unqualifiedQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | @change="handleUnqualifiedQuantityChange" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµåä½ï¼" prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" placeholder="请è¾å
¥" clearable/> |
| | | <el-input v-model="form.checkCompany" placeholder="请è¾å
¥" clearable :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµç»æï¼" prop="checkResult"> |
| | | <el-select v-model="form.checkResult"> |
| | | <el-select v-model="form.checkResult" :disabled="isViewMode"> |
| | | <el-option label="åæ ¼" value="åæ ¼" /> |
| | | <el-option label="ä¸åæ ¼" value="ä¸åæ ¼" /> |
| | | <el-option label="é¨ååæ ¼" value="é¨ååæ ¼" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable :disabled="isViewMode"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isViewMode" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" clearable/> |
| | | <el-input v-model="row.testValue" clearable :disabled="isViewMode"/> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | <template v-if="!isViewMode"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </template> |
| | | <el-button v-else @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const operationType = ref(""); |
| | | const data = reactive({ |
| | | form: { |
| | | checkTime: "", |
| | |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | qualifiedQuantity: "", |
| | | unqualifiedQuantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | | checkTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | process: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], |
| | | testStandardId: [{ required: false, message: "è¯·éæ©ææ ", trigger: "change" }], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | qualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unqualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkResult: [{ required: true, message: "请è¾å
¥", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åæ°éç½®ç° |
| | | const quantityDisabled = computed(() => { |
| | | // æ¯å¦ä¸ºæ¥çæ¨¡å¼ |
| | | const isViewMode = computed(() => operationType.value === 'view'); |
| | | // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åå·¥åºãæ°éç½®ç° |
| | | const processQuantityDisabled = computed(() => { |
| | | const v = form.value || {}; |
| | | return !!(v.productMainId != null || v.purchaseLedgerId != null); |
| | | }); |
| | |
| | | // å
æ¸
空表åéªè¯ç¶æï¼é¿å
éªç |
| | | await nextTick(); |
| | | proxy.$refs.formRef?.clearValidate(); |
| | | |
| | | |
| | | // å¹¶è¡å è½½åºç¡æ°æ® |
| | | const [userListsRes] = await Promise.all([ |
| | | userListNoPage(), |
| | |
| | | }) |
| | | ]); |
| | | userList.value = userListsRes.data; |
| | | |
| | | |
| | | form.value = {} |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | |
| | | if (operationType.value === 'edit') { |
| | | |
| | | if (operationType.value === 'edit' || operationType.value === 'view') { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // å
è®¾ç½®è¡¨åæ°æ®ï¼ä½ææ¶æ¸
空 testStandardIdï¼çé项å è½½å®æåå设置 |
| | |
| | | nextTick(() => { |
| | | proxy.$refs.formRef?.clearValidate(); |
| | | }); |
| | | |
| | | |
| | | // ç¼è¾æ¨¡å¼ä¸ï¼å¹¶è¡å è½½è§æ ¼åå·åææ é项 |
| | | if (currentProductId.value) { |
| | | // 设置产ååç§° |
| | | form.value.productName = findNodeById(productOptions.value, currentProductId.value); |
| | | |
| | | |
| | | // å¹¶è¡å è½½è§æ ¼åå·åææ é项 |
| | | const params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 2 |
| | | }; |
| | | |
| | | |
| | | Promise.all([ |
| | | modelList({ id: currentProductId.value }), |
| | | qualityInspectDetailByProductId(params) |
| | |
| | | form.value.unit = selectedModel.unit || ''; |
| | | } |
| | | } |
| | | |
| | | |
| | | // è®¾ç½®ææ é项 |
| | | testStandardOptions.value = testStandardRes.data || []; |
| | | |
| | | |
| | | // 设置 testStandardId å¹¶å è½½åæ°å表 |
| | | nextTick(() => { |
| | | if (savedTestStandardId) { |
| | | // ç¡®ä¿ç±»åå¹é
ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | |
| | | form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; |
| | | } |
| | | |
| | | const handleQualifiedQuantityChange = (value) => { |
| | | if (value === null || value === undefined) { |
| | | form.value.qualifiedQuantity = 0; |
| | | return; |
| | | } |
| | | const quantity = parseFloat(form.value.quantity) || 0; |
| | | const qualified = parseFloat(value) || 0; |
| | | form.value.qualifiedQuantity = qualified > quantity?quantity:qualified; |
| | | form.value.unqualifiedQuantity = Math.max(0, quantity - qualified); |
| | | }; |
| | | |
| | | const handleUnqualifiedQuantityChange = (value) => { |
| | | if (value === null || value === undefined) { |
| | | form.value.unqualifiedQuantity = 0; |
| | | return; |
| | | } |
| | | const quantity = parseFloat(form.value.quantity) || 0; |
| | | const unqualified = parseFloat(value) || 0; |
| | | form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified; |
| | | form.value.qualifiedQuantity = Math.max(0, quantity - unqualified); |
| | | }; |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 2 |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id |
| | | }) |
| | | } |
| | | const data = {...form.value, qualityInspectParams: tableData.value} |
| | | form.value.inspectType = 2; |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id; |
| | | }); |
| | | } |
| | | const data = { ...form.value, qualityInspectParams: tableData.value }; |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | }); |
| | | } else { |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | } |
| | | const getList = () => { |
| | | if (!currentProductId.value) { |
| | |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 2 |
| | | } |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | // ä¿å䏿æ¡éé¡¹æ°æ® |
| | | testStandardOptions.value = res.data || []; |
| | | // æ¸
ç©ºè¡¨æ ¼æ°æ®ï¼çå¾
ç¨æ·éæ©ææ |
| | | tableData.value = []; |
| | | // æ¸
ç©ºææ éæ© |
| | | form.value.testStandardId = ''; |
| | | }) |
| | | }; |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | // ä¿å䏿æ¡éé¡¹æ°æ® |
| | | testStandardOptions.value = res.data || []; |
| | | // æ¸
ç©ºè¡¨æ ¼æ°æ®ï¼çå¾
ç¨æ·éæ©ææ |
| | | tableData.value = []; |
| | | // æ¸
ç©ºææ éæ© |
| | | form.value.testStandardId = ''; |
| | | }); |
| | | } |
| | | |
| | | // ææ éæ©ååå¤ç |
| | |
| | | tableLoading.value = true; |
| | | getQualityTestStandardParamByTestStandardId(testStandardId).then(res => { |
| | | tableData.value = res.data || []; |
| | | tableData.value = tableData.value.map(item => ({ |
| | | ...item, |
| | | id: null |
| | | })); |
| | | }).catch(error => { |
| | | console.error('è·åæ ååæ°å¤±è´¥:', error); |
| | | tableData.value = []; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }); |
| | | } |
| | | const getQualityInspectParamList = (id) => { |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }); |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | |
| | | testStandardOptions.value = []; |
| | | form.value.testStandardId = ''; |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | emit('close'); |
| | | } |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | | </style> |
| | |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | label: "æ»æ°é", |
| | | prop: "quantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "åæ ¼æ°é", |
| | | prop: "qualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼æ°é", |
| | | prop: "unqualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | |
| | | } else if (params == 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | return 'danger'; |
| | | } |
| | | }, |
| | | }, |
| | |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("view", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="model"> |
| | | <el-select v-model="form.model" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | productId: "", |
| | | model: "", |
| | | unit: "", |
| | | quantity: "", |
| | | quantity: undefined, |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | inspectType: '', |
| | |
| | | dealResult: '', |
| | | dealName: '', |
| | | dealTime: '', |
| | | productModelId: undefined, |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: false, message: "请è¾å
¥", trigger: "blur" },], |
| | |
| | | productId: '', |
| | | model: '', |
| | | unit: '', |
| | | quantity: '', |
| | | quantity: undefined, |
| | | productName: '', |
| | | productModelId: undefined, |
| | | }; |
| | | } else { |
| | | form.value = {}; |
| | |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | }; |
| | | const handleChangeModel = (value) => { |
| | | const selectedModel = modelOptions.value.find(item => item.id === value); |
| | | if (selectedModel) { |
| | | form.value.model = selectedModel.model; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | | </style> |
| | |
| | | } else if (params == 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | return 'danger'; |
| | | } |
| | | }, |
| | | }, |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢è¿ç¨æ£éª' : 'ç¼è¾è¿ç¨æ£éª'" |
| | | :title="operationType === 'add' ? 'æ°å¢è¿ç¨æ£éª' : operationType === 'view' ? 'æ¥çè¿ç¨æ£éª' : 'ç¼è¾è¿ç¨æ£éª'" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | |
| | | <el-select v-model="form.process" |
| | | placeholder="è¯·éæ©å·¥åº" |
| | | clearable |
| | | :disabled="processQuantityDisabled" |
| | | :disabled="isViewMode || processQuantityDisabled" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in processList" |
| | | :key="item.name" |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | :disabled="isViewMode || operationType === 'edit'" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-select v-model="form.productModelId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="operationType === 'edit'" |
| | | :disabled="isViewMode || operationType === 'edit'" |
| | | filterable |
| | | readonly |
| | | @change="handleChangeModel"> |
| | |
| | | placeholder="è¯·éæ©ææ " |
| | | clearable |
| | | @change="handleTestStandardChange" |
| | | style="width: 100%"> |
| | | style="width: 100%" |
| | | :disabled="isViewMode"> |
| | | <el-option v-for="item in testStandardOptions" |
| | | :key="item.id" |
| | | :label="item.standardName || item.standardNo" |
| | |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | :disabled="processQuantityDisabled" /> |
| | | :disabled="isViewMode || processQuantityDisabled" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ ¼æ°éï¼" |
| | | prop="qualifiedQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.qualifiedQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | @change="handleQualifiedQuantityChange" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸åæ ¼æ°éï¼" |
| | | prop="unqualifiedQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.unqualifiedQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | @change="handleUnqualifiedQuantityChange" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµç»æï¼" |
| | | prop="checkResult"> |
| | | <el-select v-model="form.checkResult"> |
| | | <el-select v-model="form.checkResult" :disabled="isViewMode"> |
| | | <el-option label="åæ ¼" |
| | | value="åæ ¼" /> |
| | | <el-option label="ä¸åæ ¼" |
| | | value="ä¸åæ ¼" /> |
| | | <el-option label="é¨ååæ ¼" |
| | | value="é¨ååæ ¼" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | prop="checkName"> |
| | | <el-select v-model="form.checkName" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | clearable |
| | | :disabled="isViewMode"> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" /> |
| | | style="width: 100%" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | height="400"> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | | clearable /> |
| | | clearable |
| | | :disabled="isViewMode" /> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | <template v-if="!isViewMode"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </template> |
| | | <el-button v-else @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import qualified from "@/views/inventoryManagement/stockManagement/Qualified.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | |
| | |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | qualifiedQuantity: "", |
| | | unqualifiedQuantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | }, |
| | |
| | | checkName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | testStandardId: [ |
| | | { required: false, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | ], |
| | | testStandardId: [{ required: false, message: "è¯·éæ©ææ ", trigger: "change" }], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | qualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unqualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkResult: [{ required: true, message: "请è¾å
¥", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const userList = ref([]); |
| | | const { form, rules } = toRefs(data); |
| | | // æ¯å¦ä¸ºæ¥çæ¨¡å¼ |
| | | const isViewMode = computed(() => operationType.value === 'view'); |
| | | // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åå·¥åºãæ°éç½®ç° |
| | | const processQuantityDisabled = computed(() => { |
| | | const v = form.value || {}; |
| | |
| | | tableData.value = []; |
| | | // å
ç¡®ä¿äº§åæ å·²å è½½ï¼å¦åç¼è¾æ¶äº§å/è§æ ¼åå·æ æ³åæ¾ |
| | | await getProductOptions(); |
| | | if (operationType.value === "edit") { |
| | | if (operationType.value === "edit" || operationType.value === "view") { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // å
è®¾ç½®è¡¨åæ°æ®ï¼ä½ææ¶æ¸
空 testStandardIdï¼çé项å è½½å®æåå设置 |
| | |
| | | modelOptions.value.find(item => item.id == value)?.unit || ""; |
| | | }; |
| | | |
| | | const handleQualifiedQuantityChange = (value) => { |
| | | if (value === null || value === undefined) { |
| | | form.value.qualifiedQuantity = 0; |
| | | return; |
| | | } |
| | | const quantity = parseFloat(form.value.quantity) || 0; |
| | | const qualified = parseFloat(value) || 0; |
| | | form.value.qualifiedQuantity = qualified > quantity?quantity:qualified; |
| | | form.value.unqualifiedQuantity = Math.max(0, quantity - qualified); |
| | | }; |
| | | |
| | | const handleUnqualifiedQuantityChange = (value) => { |
| | | if (value === null || value === undefined) { |
| | | form.value.unqualifiedQuantity = 0; |
| | | return; |
| | | } |
| | | const quantity = parseFloat(form.value.quantity) || 0; |
| | | const unqualified = parseFloat(value) || 0; |
| | | form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified; |
| | | form.value.qualifiedQuantity = Math.max(0, quantity - unqualified); |
| | | }; |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | |
| | | delete item.id; |
| | | }); |
| | | } |
| | | // ç¡®ä¿æ°éä¸ä¸ºnull |
| | | const quantity = parseFloat(form.value.quantity) || 0; |
| | | const qualified = parseFloat(form.value.qualifiedQuantity) || 0; |
| | | const unqualified = parseFloat(form.value.unqualifiedQuantity) || 0; |
| | | |
| | | // éªè¯æ°éå
³ç³» |
| | | if (qualified + unqualified !== quantity) { |
| | | proxy.$modal.msgError("åæ ¼æ°éä¸ä¸åæ ¼æ°éä¹åå¿
é¡»çäºæ»æ°é"); |
| | | return; |
| | | } |
| | | |
| | | const data = { |
| | | ...form.value, |
| | | process: processName, // ä¿ç process åæ®µä»¥å
¼å®¹å端 |
| | |
| | | getQualityTestStandardParamByTestStandardId(testStandardId) |
| | | .then(res => { |
| | | tableData.value = res.data || []; |
| | | tableData.value = tableData.value.map(item => ({ |
| | | ...item, |
| | | id: null |
| | | })); |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åæ ååæ°å¤±è´¥:", error); |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | </style> |
| | | </style> |
| | |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | label: "æ»æ°é", |
| | | prop: "quantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "åæ ¼æ°é", |
| | | prop: "qualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼æ°é", |
| | | prop: "unqualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | |
| | | } else if (params == 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | return 'danger'; |
| | | } |
| | | }, |
| | | }, |
| | |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("view", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'è¿ç¨æ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢åæææ£éª' : 'ç¼è¾åæææ£éª'" |
| | | :title="operationType === 'add' ? 'æ°å¢åæææ£éª' : operationType === 'view' ? 'æ¥çåæææ£éª' : 'ç¼è¾åæææ£éª'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | |
| | | v-model="form.supplier" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="supplierQuantityDisabled" |
| | | :disabled="isViewMode || supplierQuantityDisabled" |
| | | > |
| | | <el-option |
| | | v-for="item in supplierList" |
| | |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | :disabled="isViewMode || operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'edit'" |
| | | <el-select v-model="form.productModelId" placeholder="è¯·éæ©" clearable :disabled="isViewMode || operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | |
| | | clearable |
| | | @change="handleTestStandardChange" |
| | | style="width: 100%" |
| | | :disabled="isViewMode" |
| | | > |
| | | <el-option |
| | | v-for="item in testStandardOptions" |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ°éï¼" prop="quantity"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请è¾å
¥" |
| | | clearable :precision="2" :disabled="supplierQuantityDisabled"/> |
| | | clearable :precision="2" :disabled="isViewMode || supplierQuantityDisabled"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ ¼æ°éï¼" prop="qualifiedQuantity"> |
| | | <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%" |
| | | v-model="form.qualifiedQuantity" placeholder="请è¾å
¥" :precision="2" |
| | | @change="onQualifiedChange" :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸åæ ¼æ°éï¼" prop="unqualifiedQuantity"> |
| | | <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%" |
| | | v-model="form.unqualifiedQuantity" placeholder="请è¾å
¥" :precision="2" |
| | | @change="onUnqualifiedChange" :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµåä½ï¼" prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" placeholder="请è¾å
¥" clearable/> |
| | | <el-input v-model="form.checkCompany" placeholder="请è¾å
¥" clearable :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµç»æï¼" prop="checkResult"> |
| | | <el-select v-model="form.checkResult"> |
| | | <el-select v-model="form.checkResult" :disabled="isViewMode"> |
| | | <el-option label="åæ ¼" value="åæ ¼"/> |
| | | <el-option label="ä¸åæ ¼" value="ä¸åæ ¼"/> |
| | | <el-option label="é¨ååæ ¼" value="é¨ååæ ¼"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable style="width: 100%"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable style="width: 100%" :disabled="isViewMode"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isViewMode" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" clearable/> |
| | | <el-input v-model="row.testValue" clearable :disabled="isViewMode"/> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | <template v-if="!isViewMode"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </template> |
| | | <el-button v-else @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], |
| | | unit: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | quantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | qualifiedQuantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | unqualifiedQuantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | checkCompany: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | checkResult: [{required: true, message: "è¯·éæ©æ£æµç»æ", trigger: "change"}], |
| | | }, |
| | |
| | | const testStandardOptions = ref([]); // ææ éæ©ä¸ææ¡æ°æ® |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); // æ£éªå䏿å表 |
| | | |
| | | // æ¯å¦ä¸ºæ¥çæ¨¡å¼ |
| | | const isViewMode = computed(() => operationType.value === 'view'); |
| | | |
| | | // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åä¾åºåãæ°éç½®ç° |
| | | const supplierQuantityDisabled = computed(() => { |
| | |
| | | tableData.value = []; |
| | | // å
ç¡®ä¿äº§åæ å·²å è½½ï¼å¦åç¼è¾æ¶äº§å/è§æ ¼åå·æ æ³åæ¾ |
| | | await getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | if (operationType.value === 'edit' || operationType.value === 'view') { |
| | | // å
ä¿å testStandardIdï¼é¿å
被æ¸
空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | form.value = {...row} |
| | |
| | | // 妿ç¼è¾æ°æ®ä¸æ testStandardIdï¼å设置并å 载对åºçåæ° |
| | | if (savedTestStandardId) { |
| | | // ç¡®ä¿ç±»åå¹é
ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | |
| | | tableLoading.value = true; |
| | | getQualityTestStandardParamByTestStandardId(testStandardId).then(res => { |
| | | tableData.value = res.data || []; |
| | | tableData.value = tableData.value.map(item => ({ |
| | | ...item, |
| | | id: null |
| | | })); |
| | | }).catch(error => { |
| | | console.error('è·åæ ååæ°å¤±è´¥:', error); |
| | | tableData.value = []; |
| | |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | // èªå¨è®¡ç®åæ ¼æ°éååæ¶çä¸åæ ¼æ°é |
| | | const onQualifiedChange = (value) => { |
| | | if (form.value.quantity !== undefined && form.value.quantity !== null) { |
| | | const maxUnqualified = form.value.quantity - value; |
| | | if (maxUnqualified >= 0) { |
| | | form.value.unqualifiedQuantity = maxUnqualified; |
| | | } else { |
| | | form.value.qualifiedQuantity = form.value.quantity; |
| | | form.value.unqualifiedQuantity = 0; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // èªå¨è®¡ç®ä¸åæ ¼æ°éååæ¶çåæ ¼æ°é |
| | | const onUnqualifiedChange = (value) => { |
| | | if (form.value.quantity !== undefined && form.value.quantity !== null) { |
| | | const maxQualified = form.value.quantity - value; |
| | | if (maxQualified >= 0) { |
| | | form.value.qualifiedQuantity = maxQualified; |
| | | } else { |
| | | form.value.unqualifiedQuantity = form.value.quantity; |
| | | form.value.qualifiedQuantity = 0; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | | </style> |
| | |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | label: "æ»æ°é", |
| | | prop: "quantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "åæ ¼æ°é", |
| | | prop: "qualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼æ°é", |
| | | prop: "unqualifiedQuantity", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "æ£æµåä½", |
| | | prop: "checkCompany", |
| | | width: 120 |
| | | }, |
| | | { |
| | |
| | | } else if (params === 'åæ ¼') { |
| | | return "success"; |
| | | } else { |
| | | return null; |
| | | return 'danger'; |
| | | } |
| | | }, |
| | | }, |
| | |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("view", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | |
| | | fetchData() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { productTurnoverDays } from '@/api/viewIndex.js' |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | /** ä¸ dataDashboard å
±ç¨æ³¨å
¥åï¼åç»ä»¶ï¼å«å¤ç¨ç right-top/right-bottomï¼æ¯åéå·æ° */ |
| | | const DASHBOARD_REFRESH_MS = 60 * 1000 |
| | | const dataDashboardRefreshTick = ref(0) |
| | | provide('dataDashboardRefreshTick', dataDashboardRefreshTick) |
| | | let dashboardPollTimer = null |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | |
| | | dashboardPollTimer = setInterval(() => { |
| | | dataDashboardRefreshTick.value++ |
| | | }, DASHBOARD_REFRESH_MS) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dashboardPollTimer) { |
| | | clearInterval(dashboardPollTimer) |
| | | dashboardPollTimer = null |
| | | } |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, computed, inject, watch } from 'vue' |
| | | import { deptStaffDistribution } from '@/api/viewIndex.js' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | getDeptStaffDistribution() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDeptStaffDistribution() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, inject, watch } from 'vue' |
| | | import { homeTodos, summaryStatistics } from '@/api/viewIndex.js' |
| | | import { getLedgerPage } from '@/api/equipmentManagement/ledger.js' |
| | | import { getRepairPage } from '@/api/equipmentManagement/repair.js' |
| | |
| | | }) |
| | | } |
| | | |
| | | const destroyTodoListScroll = () => { |
| | | const todoListEl = refTodoList.value |
| | | if (todoListEl) { |
| | | if (todoListEl._animationFrame) { |
| | | cancelAnimationFrame(todoListEl._animationFrame) |
| | | todoListEl._animationFrame = null |
| | | } |
| | | if (todoListEl._pauseTimer) { |
| | | clearInterval(todoListEl._pauseTimer) |
| | | todoListEl._pauseTimer = null |
| | | } |
| | | } |
| | | } |
| | | |
| | | // åå§åå¾
åäºé¡¹å表æ»å¨åè½ |
| | | const initTodoListScroll = () => { |
| | | destroyTodoListScroll() |
| | | const todoListEl = refTodoList.value |
| | | // 强å¶å¯ç¨æ»å¨ï¼ä¸æ£æ¥ä»»ä½æ¡ä»¶ |
| | | if (todoListEl) { |
| | |
| | | |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | destroyTodoListScroll() |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | // å¨è·åå°å¾
åäºé¡¹æ°æ®åï¼åå§åæ»å¨åè½ |
| | |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | const refreshCenterTopData = () => { |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | refreshCenterTopData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | refreshCenterTopData() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | // æ¸
çå¾
åäºé¡¹å表çå¨ç»å宿¶å¨ |
| | | const todoListEl = refTodoList.value |
| | | if (todoListEl) { |
| | | if (todoListEl._animationFrame) { |
| | | cancelAnimationFrame(todoListEl._animationFrame) |
| | | todoListEl._animationFrame = null |
| | | } |
| | | if (todoListEl._pauseTimer) { |
| | | clearInterval(todoListEl._pauseTimer) |
| | | todoListEl._pauseTimer = null |
| | | } |
| | | } |
| | | destroyTodoListScroll() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | |
| | | getCustomerRevenueAnalysis() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | getCustomerRevenueAnalysis() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerOptions() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import { productCategoryDistribution } from '@/api/viewIndex.js' |
| | |
| | | } |
| | | |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, computed, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | |
| | | fetchCustomerRanking() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchCustomerRanking() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchCustomerRanking() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, computed, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import DateTypeSwitch from '../DateTypeSwitch.vue' |
| | |
| | | fetchSupplierRanking() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchSupplierRanking() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchSupplierRanking() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftTop from './components/basic/left-top.vue' |
| | | import LeftBottom from './components/basic/left-bottom.vue' |
| | |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // 大屿¥å£è½®è¯¢é´é |
| | | const DASHBOARD_REFRESH_MS = 60 * 1000 |
| | | const dataDashboardRefreshTick = ref(0) |
| | | provide('dataDashboardRefreshTick', dataDashboardRefreshTick) |
| | | let dashboardPollTimer = null |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | |
| | | dashboardPollTimer = setInterval(() => { |
| | | dataDashboardRefreshTick.value++ |
| | | }, DASHBOARD_REFRESH_MS) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dashboardPollTimer) { |
| | | clearInterval(dashboardPollTimer) |
| | | dashboardPollTimer = null |
| | | } |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, inject, watch } from 'vue' |
| | | import { getProgressStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | |
| | | }, 150) |
| | | } |
| | | |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | const stopProgressTableScroll = () => { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (tableContainer._pauseTimer) { |
| | | const tableContainer = progressTableRef.value |
| | | if (tableContainer?._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | tableContainer._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | stopProgressTableScroll() |
| | | const tbody = tableContainer.querySelector('tbody') |
| | | if (!tbody) return |
| | | const originalCount = progressTableData.value.length |
| | |
| | | const progressStatisticsInfo = () => { |
| | | getProgressStatistics() |
| | | .then((res) => { |
| | | stopProgressTableScroll() |
| | | if (!res || !res.data) return |
| | | const obj = { |
| | | totalOrderCount: res.data.totalOrderCount || 0, |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | progressStatisticsInfo() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | progressStatisticsInfo() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | } |
| | | stopProgressTableScroll() |
| | | if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value) |
| | | const tableContainer = progressTableRef.value |
| | | if (tableContainer?._pauseTimer) { |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { inputOutputAnalysis } from '@/api/viewIndex.js' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { orderCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | workInProcessTurnoverInfo() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | workInProcessTurnoverInfo() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue' |
| | | import { processOutputAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | |
| | | fetchData() |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { productionAccountingAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, inject, watch } from 'vue' |
| | | import { workOrderEfficiencyAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | |
| | | }) |
| | | } |
| | | |
| | | const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null) |
| | | if (dataDashboardRefreshTick) { |
| | | watch(dataDashboardRefreshTick, () => { |
| | | fetchData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | /** ä¸å
¶å®é©¾é©¶è±å
±ç¨æ³¨å
¥åï¼åç»ä»¶æ¯åéå·æ°æ¥å£æ°æ® */ |
| | | const DASHBOARD_REFRESH_MS = 60 * 1000 |
| | | const dataDashboardRefreshTick = ref(0) |
| | | provide('dataDashboardRefreshTick', dataDashboardRefreshTick) |
| | | let dashboardPollTimer = null |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | |
| | | dashboardPollTimer = setInterval(() => { |
| | | dataDashboardRefreshTick.value++ |
| | | }, DASHBOARD_REFRESH_MS) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dashboardPollTimer) { |
| | | clearInterval(dashboardPollTimer) |
| | | dashboardPollTimer = null |
| | | } |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | storageBlobDTOs: [], |
| | | storageBlobDTOS: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( |
| | | res => { |
| | | form.value = { ...res.data }; |
| | | form.value.storageBlobDTOs = res.data.storageBlobVOS; |
| | | form.value.storageBlobDTOS = res.data.storageBlobVOS; |
| | | // 忾审æ¹äºº |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(","); |
| | |
| | | return; |
| | | } |
| | | } |
| | | form.value.storageBlobDTOs = fileList.value; |
| | | form.value.storageBlobDTOS = fileList.value; |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | |
| | | selectedQuotation.value = null; |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | listCustomer({ current: -1, size: -1 }).then(res => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then(res => { |
| | | customerOption.value = res.data.records; |
| | | }); |
| | | form.value.entryPerson = userStore.id; |
| | |
| | | taxExclusiveTotalPrice: taxExclusiveTotalPrice, |
| | | invoiceType: "墿®ç¥¨", |
| | | isProduction: true, |
| | | productId: p.productId, |
| | | productModelId: p.productModelId |
| | | }; |
| | | }); |
| | | |
| | |
| | | |
| | | // åè´§ç¶æå¿
é¡»æ¯"å¾
åè´§"æ"å®¡æ ¸æç»" |
| | | const statusStr = shippingStatus ? String(shippingStatus).trim() : ""; |
| | | return statusStr === "å¾
åè´§" || statusStr === "å®¡æ ¸æç»"; |
| | | return statusStr === "å¾
åè´§" || statusStr === "å®¡æ ¸æç»" || statusStr === "é¨ååè´§"; |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> |
| | | <el-form-item :prop="`products.${scope.$index}.productModelId`" class="product-table-form-item"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | v-model="scope.row.productModelId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" |
| | | :rows="4" |
| | | maxlength="500" |
| | | show-word-limit |
| | |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | |
| | | <div style="margin: 20px 0;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentQuotation.products" border style="width: 100%"> |
| | |
| | | |
| | | const productRowRules = { |
| | | productId: [{ required: true, message: 'è¯·éæ©äº§ååç§°', trigger: 'change' }], |
| | | specificationId: [{ required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change' }], |
| | | productModelId: [{ required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change' }], |
| | | unit: [{ required: true, message: '请填ååä½', trigger: 'blur' }], |
| | | unitPrice: [{ required: true, message: '请填ååä»·', trigger: 'change' }] |
| | | } |
| | |
| | | const r = { ...baseRules } |
| | | ;(form.products || []).forEach((_, i) => { |
| | | r[`products.${i}.productId`] = productRowRules.productId |
| | | r[`products.${i}.specificationId`] = productRowRules.specificationId |
| | | r[`products.${i}.productModelId`] = productRowRules.productModelId |
| | | r[`products.${i}.unit`] = productRowRules.unit |
| | | r[`products.${i}.unitPrice`] = productRowRules.unitPrice |
| | | }) |
| | |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | |
| | | row.productId = ''; |
| | | row.product = ''; |
| | | row.modelOptions = []; |
| | | row.specificationId = ''; |
| | | row.productModelId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | |
| | | if (!row) return; |
| | | // 妿æ¸
ç©ºéæ©ï¼åæ¸
空ç¸å
³å段 |
| | | if (!value) { |
| | | row.specificationId = ''; |
| | | row.productModelId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // æ´æ° specificationIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.specificationId = value; |
| | | // æ´æ° productModelIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.productModelId = value; |
| | | const modelOptions = row.modelOptions || []; |
| | | const index = modelOptions.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | |
| | | products: row.products ? row.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |
| | | specificationId: product.specificationId || '', |
| | | productModelId: product.productModelId || '', |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | |
| | | const resolvedProductId = product.productId |
| | | ? Number(product.productId) |
| | | : findNodeIdByLabel(productOptions.value, productName) || '' |
| | | |
| | | |
| | | // 妿æäº§åIDï¼å 载对åºçè§æ ¼åå·å表 |
| | | let modelOptions = []; |
| | | let resolvedSpecificationId = product.specificationId || ''; |
| | | |
| | | let resolvedProductModelId = product.productModelId || ''; |
| | | |
| | | if (resolvedProductId) { |
| | | try { |
| | | const res = await modelList({ id: resolvedProductId }); |
| | | modelOptions = res || []; |
| | | |
| | | // 妿è¿åçæ°æ®æ²¡æ specificationIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID |
| | | if (!resolvedSpecificationId && product.specification) { |
| | | |
| | | // 妿è¿åçæ°æ®æ²¡æ productModelIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID |
| | | if (!resolvedProductModelId && product.specification) { |
| | | const foundModel = modelOptions.find(item => item.model === product.specification); |
| | | if (foundModel) { |
| | | resolvedSpecificationId = foundModel.id; |
| | | resolvedProductModelId = foundModel.id; |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½è§æ ¼åå·å¤±è´¥:', error); |
| | | } |
| | | } |
| | | |
| | | |
| | | return { |
| | | productId: resolvedProductId, |
| | | product: productName, |
| | | specificationId: resolvedSpecificationId, |
| | | productModelId: resolvedProductModelId, |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | |
| | | productId: '', |
| | | product: '', |
| | | productName: '', |
| | | specificationId: '', |
| | | specification: '', |
| | | productModelId: '', |
| | | quantity: 1, |
| | | unit: '', |
| | | unitPrice: 0, |
| | |
| | | products: item.products ? item.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |
| | | specificationId: product.specificationId || '', |
| | | productModelId: product.productModelId || '', |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | |
| | | padding: 10px 0; |
| | | max-height: calc(100vh - 200px); |
| | | overflow-y: auto; |
| | | |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | height: 6px; |
| | | } |
| | | |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | |
| | | |
| | | &:hover { |
| | | background: #a8a8a8; |
| | | } |
| | |
| | | margin-bottom: 24px; |
| | | border-radius: 8px; |
| | | transition: all 0.3s ease; |
| | | |
| | | |
| | | &:hover { |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important; |
| | | } |
| | | |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | |
| | | .card-icon { |
| | | font-size: 18px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | |
| | | .card-title { |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | color: #303133; |
| | | flex: 1; |
| | | } |
| | | |
| | | |
| | | .header-btn { |
| | | margin-left: auto; |
| | | } |
| | |
| | | .product-table { |
| | | :deep(.el-table__header) { |
| | | background-color: #f5f7fa; |
| | | |
| | | |
| | | th { |
| | | background-color: #f5f7fa !important; |
| | | color: #606266; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | |
| | | :deep(.el-table__row) { |
| | | &:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | } |
| | | |
| | | |
| | | :deep(.el-table__cell) { |
| | | padding: 12px 0; |
| | | } |