| | |
| | | import request from '@/utils/request'
|
| | | import { getToken } from '@/utils/auth' |
| | |
|
| | | // 登录方法
|
| | | export function login(username, password, code, uuid) {
|
| | |
| | |
|
| | | // 获取用户详细信息
|
| | | export function getInfo() {
|
| | | const token = getToken() |
| | | return request({
|
| | | url: '/getInfo',
|
| | | headers: token ? { Authorization: `Bearer ${token}` } : {}, |
| | | method: 'get'
|
| | | })
|
| | | }
|
| | |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | export const productionOverview = () => { |
| | | return request({ |
| | | url: "/home/productionOverview", |
| | | method: "get", |
| | | headers: { |
| | | handleAuthError: false, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | export const productionRealtimeBoard = () => { |
| | | return request({ |
| | | url: "/home/productionRealtimeBoard", |
| | | method: "get", |
| | | headers: { |
| | | handleAuthError: false, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | export const productionOrderProgress = (params = {}) => { |
| | | const safePageNum = Math.max(1, Number(params.pageNum || 1)); |
| | | const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10))); |
| | | const safeTab = ["all", "inProgress", "completed", "paused"].includes(params.tab) |
| | | ? params.tab |
| | | : "all"; |
| | | return request({ |
| | | url: "/home/productionOrderProgress", |
| | | method: "get", |
| | | params: { |
| | | ...params, |
| | | tab: safeTab, |
| | | pageNum: safePageNum, |
| | | pageSize: safePageSize, |
| | | }, |
| | | headers: { |
| | | handleAuthError: false, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | export const todayProductionPlan = (params = {}) => { |
| | | const safeLimit = Math.min(20, Math.max(1, Number(params.limit || 4))); |
| | | return request({ |
| | | url: "/home/todayProductionPlan", |
| | | method: "get", |
| | | params: { |
| | | ...params, |
| | | limit: safeLimit, |
| | | }, |
| | | headers: { |
| | | handleAuthError: false, |
| | | }, |
| | | }); |
| | | }; |
| | |
| | | children: [ |
| | | { |
| | | path: ":id", |
| | | component: () => |
| | | import("@/views/projectManagement/Management/projectDetail.vue"), |
| | | component: () => import("@/views/projectManagement/Management/projectDetail.vue"), |
| | | name: "ProjectManagementDetail", |
| | | meta: { |
| | | title: "项目详情", |
| | | activeMenu: "/projectManagement/Management", |
| | | }, |
| | | meta: { title: "项目详情", activeMenu: "/projectManagement/Management" }, |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | const uuid = userInfo.uuid
|
| | | return new Promise((resolve, reject) => {
|
| | | login(username, password, code, uuid).then(res => {
|
| | | setToken(res.token)
|
| | | this.token = res.token
|
| | | const token = res?.token || res?.data?.token |
| | | if (!token) { |
| | | reject(new Error('未获取到登录令牌')) |
| | | return |
| | | } |
| | | setToken(token) |
| | | this.token = token |
| | | resolve()
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | |
| | | getInfo() {
|
| | | return new Promise((resolve, reject) => {
|
| | | getInfo().then(res => {
|
| | | const user = res.user
|
| | | res = res.data |
| | | const user = res.user || {} |
| | | let avatar = user.avatar || ""
|
| | | avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
|
| | | if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
| | |
| | | } else {
|
| | | this.roles = ['ROLE_DEFAULT']
|
| | | }
|
| | | this.id = user.userId
|
| | | this.name = user.userName
|
| | | this.id = user.userId || '' |
| | | this.name = user.userName || '' |
| | | this.avatar = avatar
|
| | | this.currentFactoryName = user.currentFactoryName |
| | | this.nickName = user.nickName |
| | | this.roleName = user.roles[0].roleName |
| | | this.currentDeptId = user.tenantId |
| | | this.currentFactoryName = user.currentFactoryName || '' |
| | | this.nickName = user.nickName || '' |
| | | this.roleName = Array.isArray(user.roles) && user.roles.length > 0 ? (user.roles[0].roleName || '') : '' |
| | | this.currentDeptId = user.tenantId || '' |
| | | this.currentLoginTime = this.getCurrentTime() |
| | | this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0 |
| | | resolve(res) |
| | |
| | | const password = userInfo.password
|
| | | return new Promise((resolve, reject) => {
|
| | | loginCheckFactory(username, password).then(res => {
|
| | | setToken(res.token)
|
| | | this.token = res.token
|
| | | const token = res?.token || res?.data?.token |
| | | if (!token) { |
| | | reject(new Error('未获取到登录令牌')) |
| | | return |
| | | } |
| | | setToken(token) |
| | | this.token = token |
| | | resolve()
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | |
| | | return new Promise((resolve, reject) => {
|
| | | tideLogin(code)
|
| | | .then((res) => {
|
| | | setToken(res.token);
|
| | | this.token = res.token
|
| | | const token = res?.token || res?.data?.token |
| | | if (!token) { |
| | | reject(new Error('未获取到登录令牌')) |
| | | return |
| | | } |
| | | setToken(token); |
| | | this.token = token |
| | | Vue.prototype.uploadHeader = {
|
| | | Authorization: "Bearer " + res.token,
|
| | | Authorization: "Bearer " + token, |
| | | };
|
| | | resolve();
|
| | | })
|
| | |
| | |
|
| | | // request拦截器
|
| | | service.interceptors.request.use(config => {
|
| | | config.headers = config.headers || {} |
| | | // 是否需要设置 token
|
| | | const isToken = (config.headers || {}).isToken === false
|
| | | const isToken = config.headers.isToken === false |
| | | // 是否需要防止数据重复提交
|
| | | const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
| | | const isRepeatSubmit = config.headers.repeatSubmit === false |
| | | if (getToken() && !isToken) {
|
| | | config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
| | | }
|
| | |
| | | return config
|
| | | }, error => {
|
| | | console.log(error)
|
| | | Promise.reject(error)
|
| | | return Promise.reject(error) |
| | | })
|
| | |
|
| | | // 响应拦截器
|
| | | service.interceptors.response.use(res => {
|
| | | // 未设置状态码则默认成功状态
|
| | | const code = res.data.code || 200
|
| | | const handleAuthError = (res.config && res.config.headers && res.config.headers.handleAuthError) !== false |
| | | // 获取错误信息
|
| | | const msg = errorCode[code] || res.data.msg || errorCode['default']
|
| | | // 二进制数据则直接返回
|
| | |
| | | return res.data
|
| | | }
|
| | | if (code === 401) {
|
| | | if (!handleAuthError) { |
| | | return Promise.reject(new Error(msg)) |
| | | } |
| | | if (!isRelogin.show) {
|
| | | isRelogin.show = true
|
| | | ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
| | |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">生产订单进度</div> |
| | | <el-radio-group v-model="orderFilter" size="small"> |
| | | <el-radio-button label="all">全部</el-radio-button> |
| | | <el-radio-button label="in_progress">进行中</el-radio-button> |
| | | <el-radio-button label="completed">已完成</el-radio-button> |
| | | <el-radio-button label="paused">已暂停</el-radio-button> |
| | | <el-radio-button label="all">全部({{ orderProgressMeta.total }})</el-radio-button> |
| | | <el-radio-button label="inProgress">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button> |
| | | <el-radio-button label="completed">已完成({{ orderProgressMeta.completedCount }})</el-radio-button> |
| | | <el-radio-button label="paused">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <el-table :data="filteredOrders" stripe> |
| | |
| | | <el-table-column label="状态" min-width="90"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getOrderStatusType(row.status)" effect="light"> |
| | | {{ getOrderStatusText(row.status) }} |
| | | {{ row.statusLabel || getOrderStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">今日生产计划</div> |
| | | <span class="panel-more">{{ todayPlanList.length }}项</span> |
| | | <span class="panel-more">{{ todayPlanTotal }}项</span> |
| | | </div> |
| | | <ul class="plan-list"> |
| | | <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item"> |
| | |
| | | getBusiness, |
| | | homeTodos, |
| | | processDataProductionStatistics, |
| | | productionOrderProgress, |
| | | productionOverview, |
| | | productionRealtimeBoard, |
| | | qualityInspectionStatistics, |
| | | statisticsReceivablePayable, |
| | | todayProductionPlan, |
| | | } from "@/api/viewIndex.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | |
| | |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }); |
| | | |
| | | const productionOverviewData = ref({ |
| | | totalOutput: 0, |
| | | totalScrap: 0, |
| | | yieldRate: 0, |
| | | }); |
| | | |
| | | const realtimeBoardData = ref({ |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }); |
| | | |
| | | const orderProgressMeta = ref({ |
| | | tab: "all", |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | | }); |
| | | |
| | | const todayPlanList = ref([]); |
| | | const todayPlanTotal = ref(0); |
| | | |
| | | const sum = ref(0); |
| | | const yny = ref(0); |
| | |
| | | key: "production", |
| | | title: "生产总览", |
| | | desc: "累计产出(件)", |
| | | value: formatNumber(processTotals.value.output), |
| | | value: formatNumber(productionOverviewData.value.totalOutput), |
| | | subLabel: "累计报废", |
| | | subValue: formatNumber(processTotals.value.scrap), |
| | | trend: `良率 ${ratioText(processTotals.value.output, processTotals.value.input)}`, |
| | | subValue: formatNumber(productionOverviewData.value.totalScrap), |
| | | trend: `良率 ${Number(productionOverviewData.value.yieldRate || 0).toFixed(2)}%`, |
| | | icon: Operation, |
| | | visible: visibleModules.value.production, |
| | | }, |
| | | ].filter((item) => item.visible)); |
| | | |
| | | const productionOrders = ref([ |
| | | { |
| | | orderNo: "MO-20260518-001", |
| | | productName: "智能控制器", |
| | | planQty: 1000, |
| | | completedQty: 860, |
| | | completionRate: 86, |
| | | deliveryDate: "2026-05-20", |
| | | status: "in_progress", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-002", |
| | | productName: "电源模块", |
| | | planQty: 800, |
| | | completedQty: 640, |
| | | completionRate: 80, |
| | | deliveryDate: "2026-05-22", |
| | | status: "in_progress", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-003", |
| | | productName: "传感器组件", |
| | | planQty: 500, |
| | | completedQty: 150, |
| | | completionRate: 30, |
| | | deliveryDate: "2026-05-25", |
| | | status: "paused", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-004", |
| | | productName: "结构件A", |
| | | planQty: 1200, |
| | | completedQty: 1200, |
| | | completionRate: 100, |
| | | deliveryDate: "2026-05-15", |
| | | status: "completed", |
| | | }, |
| | | ]); |
| | | const productionOrders = ref([]); |
| | | |
| | | const orderFilter = ref("all"); |
| | | const filteredOrders = computed(() => { |
| | | if (orderFilter.value === "all") return productionOrders.value; |
| | | return productionOrders.value.filter((item) => item.status === orderFilter.value); |
| | | }); |
| | | const filteredOrders = computed(() => productionOrders.value); |
| | | |
| | | const todayPlanList = computed(() => |
| | | productionOrders.value |
| | | .slice() |
| | | .sort((a, b) => dayjs(a.deliveryDate).valueOf() - dayjs(b.deliveryDate).valueOf()) |
| | | .slice(0, 5) |
| | | ); |
| | | const getCompareTrend = (value) => { |
| | | const num = Number(value || 0); |
| | | if (num > 0) return "up"; |
| | | if (num < 0) return "down"; |
| | | return "flat"; |
| | | }; |
| | | |
| | | const avgCompletionRate = computed(() => { |
| | | if (!productionOrders.value.length) return 0; |
| | | const total = productionOrders.value.reduce((acc, cur) => acc + Number(cur.completionRate || 0), 0); |
| | | return Number((total / productionOrders.value.length).toFixed(1)); |
| | | }); |
| | | const getCompareText = (value) => { |
| | | const num = Number(value || 0); |
| | | const abs = Math.abs(num).toFixed(2); |
| | | if (num > 0) return `较昨日 ↑ ${abs}%`; |
| | | if (num < 0) return `较昨日 ↓ ${abs}%`; |
| | | return "较昨日 持平"; |
| | | }; |
| | | |
| | | const realtimeBoard = computed(() => { |
| | | const oee = ratioNumber(processTotals.value.output, processTotals.value.input); |
| | | const defectRate = ratioNumber(processTotals.value.scrap, processTotals.value.input); |
| | | const oee = Number(realtimeBoardData.value.deviceOee?.value || 0); |
| | | const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0); |
| | | const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0); |
| | | const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0); |
| | | const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0); |
| | | const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0); |
| | | return [ |
| | | { |
| | | key: "oee", |
| | | label: "设备 OEE", |
| | | percent: clampPercent(oee), |
| | | display: `${oee.toFixed(1)}%`, |
| | | delta: "较昨日 ↑ 4.0%", |
| | | trend: "up", |
| | | display: `${oee.toFixed(2)}%`, |
| | | delta: getCompareText(oeeCompare), |
| | | trend: getCompareTrend(oeeCompare), |
| | | color: "#2d8cff", |
| | | }, |
| | | { |
| | | key: "order", |
| | | label: "订单达成率", |
| | | percent: clampPercent(avgCompletionRate.value), |
| | | display: `${avgCompletionRate.value.toFixed(1)}%`, |
| | | delta: "较昨日 ↑ 2.6%", |
| | | trend: "up", |
| | | percent: clampPercent(orderAchievement), |
| | | display: `${orderAchievement.toFixed(2)}%`, |
| | | delta: getCompareText(orderCompare), |
| | | trend: getCompareTrend(orderCompare), |
| | | color: "#31d2ff", |
| | | }, |
| | | { |
| | | key: "defect", |
| | | label: "不良率", |
| | | percent: clampPercent(defectRate), |
| | | display: `${defectRate.toFixed(1)}%`, |
| | | delta: "较昨日 ↓ 0.5%", |
| | | trend: "down", |
| | | display: `${defectRate.toFixed(2)}%`, |
| | | delta: getCompareText(defectCompare), |
| | | trend: getCompareTrend(defectCompare), |
| | | color: "#f6a23f", |
| | | }, |
| | | ]; |
| | |
| | | |
| | | const getOrderStatusText = (status) => { |
| | | const mapping = { |
| | | in_progress: "进行中", |
| | | completed: "已完成", |
| | | paused: "暂停", |
| | | 1: "待开始", |
| | | 2: "进行中", |
| | | 3: "已完成", |
| | | 4: "已暂停", |
| | | }; |
| | | return mapping[status] || "未知"; |
| | | }; |
| | | |
| | | const getOrderStatusType = (status) => { |
| | | const mapping = { |
| | | in_progress: "success", |
| | | completed: "primary", |
| | | paused: "warning", |
| | | 1: "info", |
| | | 2: "success", |
| | | 3: "primary", |
| | | 4: "warning", |
| | | }; |
| | | return mapping[status] || "info"; |
| | | }; |
| | | |
| | | const formatDueDate = (value) => { |
| | | if (!value) return "--"; |
| | | const date = dayjs(value); |
| | | return date.isValid() ? date.format("YYYY-MM-DD") : "--"; |
| | | }; |
| | | |
| | | const mapOrderProgressRecord = (item = {}) => ({ |
| | | orderNo: item.orderNo || "--", |
| | | productName: item.productName || "--", |
| | | planQty: Number(item.plannedQuantity || 0), |
| | | completedQty: Number(item.completedQuantity || 0), |
| | | completionRate: clampPercent(Number(item.completionRate || 0)), |
| | | deliveryDate: formatDueDate(item.dueDate), |
| | | status: Number(item.status || 0), |
| | | statusLabel: item.statusLabel || "", |
| | | }); |
| | | |
| | | const mapTodayPlanRecord = (item = {}) => ({ |
| | | orderNo: item.orderNo || "--", |
| | | productName: item.productName || "--", |
| | | planQty: Number(item.plannedQuantity || 0), |
| | | deliveryDate: formatDueDate(item.dueDate), |
| | | status: Number(item.status || 0), |
| | | statusLabel: item.statusLabel || "", |
| | | }); |
| | | |
| | | const refreshProductionOverview = async () => { |
| | | try { |
| | | const res = await productionOverview(); |
| | | const data = res?.data || {}; |
| | | productionOverviewData.value = { |
| | | totalOutput: Number(data.totalOutput || 0), |
| | | totalScrap: Number(data.totalScrap || 0), |
| | | yieldRate: Number(data.yieldRate || 0), |
| | | }; |
| | | } catch { |
| | | productionOverviewData.value = { |
| | | totalOutput: 0, |
| | | totalScrap: 0, |
| | | yieldRate: 0, |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | const refreshProductionRealtimeBoard = async () => { |
| | | try { |
| | | const res = await productionRealtimeBoard(); |
| | | const data = res?.data || {}; |
| | | realtimeBoardData.value = { |
| | | deviceOee: { |
| | | value: Number(data.deviceOee?.value || 0), |
| | | compareYesterday: Number(data.deviceOee?.compareYesterday || 0), |
| | | }, |
| | | orderAchievementRate: { |
| | | value: Number(data.orderAchievementRate?.value || 0), |
| | | compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0), |
| | | }, |
| | | defectRate: { |
| | | value: Number(data.defectRate?.value || 0), |
| | | compareYesterday: Number(data.defectRate?.compareYesterday || 0), |
| | | }, |
| | | }; |
| | | } catch { |
| | | realtimeBoardData.value = { |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | const refreshProductionOrderProgress = async () => { |
| | | try { |
| | | const res = await productionOrderProgress({ |
| | | tab: orderFilter.value, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const data = res?.data || {}; |
| | | orderProgressMeta.value = { |
| | | tab: data.tab || orderFilter.value, |
| | | total: Number(data.total || 0), |
| | | pageNum: Number(data.pageNum || 1), |
| | | pageSize: Number(data.pageSize || 10), |
| | | inProgressCount: Number(data.inProgressCount || 0), |
| | | completedCount: Number(data.completedCount || 0), |
| | | pausedCount: Number(data.pausedCount || 0), |
| | | }; |
| | | productionOrders.value = (data.records || []).map(mapOrderProgressRecord); |
| | | } catch { |
| | | orderProgressMeta.value = { |
| | | tab: orderFilter.value, |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | | }; |
| | | productionOrders.value = []; |
| | | } |
| | | }; |
| | | |
| | | const refreshTodayProductionPlan = async () => { |
| | | try { |
| | | const res = await todayProductionPlan({ limit: 4 }); |
| | | const data = res?.data || {}; |
| | | todayPlanTotal.value = Number(data.total || 0); |
| | | todayPlanList.value = (data.records || []).map(mapTodayPlanRecord); |
| | | } catch { |
| | | todayPlanTotal.value = 0; |
| | | todayPlanList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getBusinessData = async () => { |
| | |
| | | router.push(path).catch(() => {}); |
| | | }; |
| | | |
| | | watch(orderFilter, () => { |
| | | if (visiblePanels.value.order) { |
| | | refreshProductionOrderProgress(); |
| | | } |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | updateNowTime(); |
| | | clockTimer = setInterval(updateNowTime, 1000); |
| | | if (dashboardCards.value.length > 0) { |
| | | getBusinessData(); |
| | | } |
| | | if (visibleModules.value.production) { |
| | | refreshProductionOverview(); |
| | | } |
| | | if (visiblePanels.value.contract) { |
| | | analysisCustomer(); |
| | |
| | | if (visiblePanels.value.process) { |
| | | getProcessList(); |
| | | refreshProcessStats(); |
| | | } |
| | | if (visiblePanels.value.order) { |
| | | refreshProductionOrderProgress(); |
| | | } |
| | | if (visiblePanels.value.realtime) { |
| | | refreshProductionRealtimeBoard(); |
| | | } |
| | | if (visiblePanels.value.plan) { |
| | | refreshTodayProductionPlan(); |
| | | } |
| | | }); |
| | | |
| | |
| | | color: #f59e0b; |
| | | } |
| | | |
| | | .realtime-delta.flat { |
| | | color: #64748b; |
| | | } |
| | | |
| | | .warning-list { |
| | | margin-top: 10px; |
| | | display: flex; |
| | |
| | | import EditProcess from "@/views/productionManagement/processRoute/Edit.vue"; |
| | | import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue"; |
| | | import { listPage, del } from "@/api/productionManagement/processRoute.js"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | |
| | | <span class="info-value">{{ routeInfo.quantity || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-item full-width" |
| | | v-if="routeInfo.description"> |
| | | <div class="info-item"> |
| | | <div class="info-label-wrapper"> |
| | | <span class="info-label">描述</span> |
| | | <span class="info-label">备注</span> |
| | | </div> |
| | | <div class="info-value-wrapper"> |
| | | <span class="info-value">{{ routeInfo.description }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <!-- 附件模块 --> |
| | | <div v-if="pageType === 'order'" |
| | | class="section-header"> |
| | | <div class="section-title">附件</div> |
| | | </div> |
| | | <el-card v-if="pageType === 'order'" |
| | | class="attachment-card" |
| | | shadow="hover" |
| | | style="margin-top: 10px; margin-bottom: 20px;"> |
| | | <el-table :data="attachmentTableData" |
| | | border |
| | | class="attachment-table"> |
| | | <el-table-column label="附件名称" |
| | | prop="originalFilename" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="200" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="downloadAttachmentFile(scope.row.downloadURL)"> |
| | | 下载 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | <!-- 表格视图 --> |
| | | <div v-if="viewMode === 'table'" |
| | |
| | | v-model="bomDataValue.showProductDialog" |
| | | :single="true" |
| | | @confirm="handleBomProduct" /> |
| | | <!-- 上传组件弹窗 --> |
| | | <el-dialog v-model="uploadDialogVisible" |
| | | title="上传附件" |
| | | width="50%" |
| | | @close="closeAttachmentUpload"> |
| | | <AttachmentUpload v-model:file-list="newFileList" /> |
| | | <template #footer> |
| | | <el-button @click="saveAttachmentUpload" |
| | | type="primary">保存</el-button> |
| | | <el-button @click="closeAttachmentUpload">关闭</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 新增/编辑弹窗 --> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'" |
| | |
| | | queryList2, |
| | | add2, |
| | | } from "@/api/productionManagement/productStructure.js"; |
| | | import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { |
| | | attachmentList, |
| | | deleteAttachment, |
| | | createAttachment, |
| | | } from "@/api/basicData/storageAttachment.js"; |
| | | |
| | | import { useRoute } from "vue-router"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | |
| | | const orderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const editable = computed(() => route.query.editable !== "false"); |
| | | const technologyRoutingId = computed(() => route.query.technologyRoutingId); |
| | | |
| | | const tableLoading = ref(false); |
| | | const tableData = ref([]); |
| | |
| | | bomNo: "", |
| | | description: "", |
| | | quantity: 0, |
| | | technologyRoutingId: "", |
| | | }); |
| | | |
| | | // 附件相关 |
| | | const attachmentTableData = ref([]); |
| | | const uploadDialogVisible = ref(false); |
| | | const newFileList = ref([]); |
| | | |
| | | const getAttachmentList = () => { |
| | | if (!technologyRoutingId.value) return; |
| | | attachmentList({ |
| | | recordType: "technology_routing", |
| | | recordId: technologyRoutingId.value, |
| | | }).then(res => { |
| | | attachmentTableData.value = (res && res.data) || []; |
| | | }); |
| | | }; |
| | | |
| | | const handleUploadAttachment = () => { |
| | | uploadDialogVisible.value = true; |
| | | }; |
| | | |
| | | const saveAttachmentUpload = async () => { |
| | | if (newFileList.value.length > 0) { |
| | | createAttachment({ |
| | | application: "file", |
| | | recordType: "technology_routing", |
| | | recordId: technologyRoutingId.value, |
| | | storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value], |
| | | }) |
| | | .then(res => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("上传成功"); |
| | | newFileList.value = []; |
| | | getAttachmentList(); |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | uploadDialogVisible.value = false; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const closeAttachmentUpload = () => { |
| | | newFileList.value = []; |
| | | uploadDialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleDeleteAttachment = async row => { |
| | | deleteAttachment([row.storageAttachmentId]).then(res => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("删除成功"); |
| | | getAttachmentList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const downloadAttachmentFile = url => { |
| | | window.open(url, "_blank"); |
| | | }; |
| | | |
| | | const processOptions = ref([]); |
| | | const showProductSelectDialog = ref(false); |
| | |
| | | bomId: route.query.bomId || "", |
| | | description: route.query.description || "", |
| | | quantity: route.query.quantity || 0, |
| | | technologyRoutingId: route.query.technologyRoutingId || "", |
| | | status: !(route.query.status == 1 || route.query.status === "false"), |
| | | }; |
| | | bomTableData.value[0].productName = routeInfo.value.productName; |
| | |
| | | row.processId = value || ""; |
| | | syncProcessOperationFields(row); |
| | | |
| | | // 同一层级只能选一样的工序 |
| | | // 检查同一层级是否已经有其他不同的工序被选中 |
| | | const siblings = findSiblings(bomDataValue.value.dataList, row.tempId); |
| | | if (siblings && value) { |
| | | siblings.forEach(sibling => { |
| | | if (sibling.tempId !== row.tempId) { |
| | | sibling.processId = value; |
| | | syncProcessOperationFields(sibling); |
| | | } |
| | | const hasDifferentProcess = siblings.some(sibling => { |
| | | return ( |
| | | sibling.tempId !== row.tempId && |
| | | sibling.processId && |
| | | sibling.processId !== value |
| | | ); |
| | | }); |
| | | if (hasDifferentProcess) { |
| | | ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | |
| | | } |
| | | }; |
| | | |
| | | // 校验同一层级的工序是否一致 |
| | | const validateProcessConsistency = items => { |
| | | if (!items || items.length === 0) return; |
| | | |
| | | // 检查当前层级 |
| | | const processes = items |
| | | .filter(item => item.processId) |
| | | .map(item => item.processId); |
| | | if (processes.length > 1) { |
| | | const uniqueProcesses = [...new Set(processes)]; |
| | | if (uniqueProcesses.length > 1) { |
| | | ElMessage.error("同一层级的工序必须一致"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 递归检查子级 |
| | | items.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | validateProcessConsistency(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | bomDataValue.value.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | | |
| | | validateProcessConsistency(bomDataValue.value.dataList); |
| | | |
| | | return isValid; |
| | | }; |
| | |
| | | getList(); |
| | | getProcessList(); |
| | | fetchBomData(); |
| | | if (pageType.value === "order") { |
| | | getAttachmentList(); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | row.processId = value || ""; |
| | | syncProcessOperationFields(row); |
| | | |
| | | // 同一层级只能选一样的工序 |
| | | // 检查同一层级是否已经有其他不同的工序被选中 |
| | | const siblings = findSiblings(dataValue.dataList, row.tempId); |
| | | if (siblings && value) { |
| | | siblings.forEach(sibling => { |
| | | if (sibling.tempId !== row.tempId) { |
| | | sibling.processId = value; |
| | | syncProcessOperationFields(sibling); |
| | | } |
| | | const hasDifferentProcess = siblings.some(sibling => { |
| | | return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value; |
| | | }); |
| | | if (hasDifferentProcess) { |
| | | ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | |
| | | bomNo: row.bomNo || "", |
| | | description: data.description || "", |
| | | quantity: row.quantity || 0, |
| | | technologyRoutingId: data.technologyRoutingId, |
| | | orderId, |
| | | type: "order", |
| | | editable: !row.endOrder, |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="销售合同号:" |
| | | prop="salesContractNo"> |
| | | <div style="display: flex; align-items: center; gap: 12px;width: 100%;"> |
| | | <el-checkbox v-model="form.autoGenerateContractNo" v-if="operationType === 'add'">自动生成 |
| | | </el-checkbox> |
| | | <el-input v-model="form.salesContractNo" |
| | | placeholder="自动生成" |
| | | :placeholder="form.autoGenerateContractNo ? '自动生成' : '请输入'" |
| | | clearable |
| | | disabled /> |
| | | :disabled="form.autoGenerateContractNo" /> |
| | | |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | }, |
| | | form: { |
| | | salesContractNo: "", |
| | | autoGenerateContractNo: true, |
| | | salesman: "", |
| | | customerId: "", |
| | | entryPerson: "", |
| | |
| | | form.value.entryDate = getCurrentDate(); |
| | | // 签订日期默认为当天 |
| | | form.value.executionDate = getCurrentDate(); |
| | | // 默认自动生成销售合同号 |
| | | form.value.autoGenerateContractNo = true; |
| | | } else { |
| | | currentId.value = row.id; |
| | | getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => { |
| | |
| | | form.value.entryPerson = Number(res.entryPerson); |
| | | productData.value = form.value.productData; |
| | | fileList.value = form.value.storageBlobVOs; |
| | | // 编辑时设置自动生成为false,允许手动修改 |
| | | form.value.autoGenerateContractNo = false; |
| | | }); |
| | | } |
| | | // let userAll = await userStore.getInfo() |
| | |
| | | } |
| | | form.value.storageBlobDTOs = fileList; |
| | | form.value.type = 1; |
| | | if (form.value.autoGenerateContractNo) { |
| | | form.value.salesContractNo = ''; |
| | | } |
| | | addOrUpdateSalesLedger(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |