Merge branch 'master' of https://git.evo-techina.com/sgc/mes-vue
This commit is contained in:
commit
57c7eb92eb
@ -44,7 +44,12 @@ export interface OrderItem {
|
|||||||
assingWorkOperationTime?: string
|
assingWorkOperationTime?: string
|
||||||
|
|
||||||
starter?: number
|
starter?: number
|
||||||
|
|
||||||
|
orderCode?: string
|
||||||
|
|
||||||
|
routeCode?: string
|
||||||
|
|
||||||
|
proWorkshop?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生产订单 API
|
// 生产订单 API
|
||||||
@ -93,11 +98,23 @@ export const orderItemApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
export(params?: { ids?: string[]; code?: string; projectId?: number; beginTime?: string; endTime?: string }) {
|
export(params?: {
|
||||||
|
ids?: string[]
|
||||||
|
code?: string
|
||||||
|
projectName?: string
|
||||||
|
orderCode?: string
|
||||||
|
routeCode?: string
|
||||||
|
proWorkshop?: string
|
||||||
|
beginTime?: string
|
||||||
|
endTime?: string
|
||||||
|
}) {
|
||||||
const p: Record<string, any> = {}
|
const p: Record<string, any> = {}
|
||||||
if (params?.ids?.length) p.ids = params.ids.join(',')
|
if (params?.ids?.length) p.ids = params.ids.join(',')
|
||||||
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
if (params?.code !== undefined && params?.code !== null) p.code = params.code
|
||||||
if (params?.projectId !== undefined && params?.projectId !== null) p.projectId = params.projectId
|
if (params?.projectName !== undefined && params?.projectName !== null) p.projectName = params.projectName
|
||||||
|
if (params?.orderCode !== undefined && params?.orderCode !== null) p.orderCode = params.orderCode
|
||||||
|
if (params?.routeCode !== undefined && params?.routeCode !== null) p.routeCode = params.routeCode
|
||||||
|
if (params?.proWorkshop !== undefined && params?.proWorkshop !== null) p.proWorkshop = params.proWorkshop
|
||||||
if (params?.beginTime !== undefined && params?.beginTime !== null) p.beginTime = params.beginTime
|
if (params?.beginTime !== undefined && params?.beginTime !== null) p.beginTime = params.beginTime
|
||||||
if (params?.endTime !== undefined && params?.endTime !== null) p.endTime = params.endTime
|
if (params?.endTime !== undefined && params?.endTime !== null) p.endTime = params.endTime
|
||||||
return request({ url: `/biz/orderItem/export`, method: 'get', params: p, responseType: 'blob' })
|
return request({ url: `/biz/orderItem/export`, method: 'get', params: p, responseType: 'blob' })
|
||||||
|
|||||||
@ -69,18 +69,57 @@ export interface KingdeePrdMo {
|
|||||||
|
|
||||||
/** 金蝶工艺路线工序(树子节点) */
|
/** 金蝶工艺路线工序(树子节点) */
|
||||||
export interface KingdeeProcessRoute {
|
export interface KingdeeProcessRoute {
|
||||||
|
/** 工序号 FOperNumber */
|
||||||
operNumber: number | null
|
operNumber: number | null
|
||||||
|
/** 工作中心 FWorkCenterId.FName */
|
||||||
workCenterName: string | null
|
workCenterName: string | null
|
||||||
|
/** 生产车间 FDepartmentId.FName */
|
||||||
departmentName: string | null
|
departmentName: string | null
|
||||||
processProperty: string | null
|
/** 工序名称 FProcessProperty */
|
||||||
|
processName: string | null
|
||||||
|
/** 工序说明 FOperDescription */
|
||||||
operDescription: string | null
|
operDescription: string | null
|
||||||
|
/** 活动单位 FActivity1UnitID.FName */
|
||||||
activityUnit: string | null
|
activityUnit: string | null
|
||||||
|
/** 工序控制码 FOptCtrlCodeId.FName */
|
||||||
optCtrlCodeName: string | null
|
optCtrlCodeName: string | null
|
||||||
|
/** 活动量 FActivity1Qty */
|
||||||
activityQty: number | null
|
activityQty: number | null
|
||||||
|
/** 计划开始时间 yyyy-MM-dd HH:mm:ss */
|
||||||
planStartTime: string | null
|
planStartTime: string | null
|
||||||
|
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */
|
||||||
planFinishTime: string | null
|
planFinishTime: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解析工序名称,兼容旧字段及后端误映射 */
|
||||||
|
export function resolveProcessName(route: Partial<KingdeeProcessRoute> & { processProperty?: string | null }) {
|
||||||
|
const legacyName = typeof route.processProperty === 'string' ? route.processProperty.trim() : ''
|
||||||
|
const processName = route.processName?.trim() || legacyName || ''
|
||||||
|
const operDescription = route.operDescription?.trim() || ''
|
||||||
|
|
||||||
|
if (processName) return processName
|
||||||
|
// 兼容:FProcessProperty 被写入 operDescription 而 processName 为空
|
||||||
|
if (operDescription) return operDescription
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 归一化金蝶工序字段,保证 processName / operDescription 各归其位 */
|
||||||
|
export function normalizeKingdeeProcessRoute(
|
||||||
|
route: KingdeeProcessRoute & { processProperty?: string | null }
|
||||||
|
): KingdeeProcessRoute {
|
||||||
|
const legacyName = typeof route.processProperty === 'string' ? route.processProperty.trim() : ''
|
||||||
|
const processName = route.processName?.trim() || legacyName || ''
|
||||||
|
const operDescription = route.operDescription?.trim() || ''
|
||||||
|
|
||||||
|
if (processName) {
|
||||||
|
return { ...route, processName, operDescription: operDescription || null }
|
||||||
|
}
|
||||||
|
if (operDescription) {
|
||||||
|
return { ...route, processName: operDescription, operDescription: null }
|
||||||
|
}
|
||||||
|
return { ...route, processName: null, operDescription: null }
|
||||||
|
}
|
||||||
|
|
||||||
// 项目表 API
|
// 项目表 API
|
||||||
export const orderProjectApi = {
|
export const orderProjectApi = {
|
||||||
// 分页查询
|
// 分页查询
|
||||||
|
|||||||
36
src/components/SiteLogoMark.vue
Normal file
36
src/components/SiteLogoMark.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
站点 Logo 展示:优先显示图片,无 URL 或加载失败时回退为站点名称首字母。
|
||||||
|
用于 layout、login、register 等页面;src 不传则使用 siteStore.siteLogo。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
v-if="showSiteLogoImg"
|
||||||
|
:src="siteLogo"
|
||||||
|
:class="imgClass"
|
||||||
|
alt="Logo"
|
||||||
|
@error="handleLogoError"
|
||||||
|
/>
|
||||||
|
<div v-else :class="iconClass" :style="iconStyle">{{ fallbackLetter }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSiteLogo } from '@/composables/useSiteLogo'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
/** 自定义 Logo 地址,默认取 siteStore.siteLogo */
|
||||||
|
src?: string
|
||||||
|
/** 图片模式下的 class */
|
||||||
|
imgClass?: string
|
||||||
|
/** 首字母 fallback 模式下的 class */
|
||||||
|
iconClass?: string
|
||||||
|
/** 首字母 fallback 的内联样式 */
|
||||||
|
iconStyle?: Record<string, string>
|
||||||
|
}>(), {
|
||||||
|
imgClass: 'logo-img',
|
||||||
|
iconClass: 'logo-icon',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { siteLogo, showSiteLogoImg, fallbackLetter, handleLogoError } = useSiteLogo(
|
||||||
|
() => props.src
|
||||||
|
)
|
||||||
|
</script>
|
||||||
30
src/composables/useSiteLogo.ts
Normal file
30
src/composables/useSiteLogo.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { useSiteStore } from '@/stores/site'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点 Logo:图片加载失败时回退为站点名称首字母
|
||||||
|
*/
|
||||||
|
export function useSiteLogo(src?: () => string | undefined) {
|
||||||
|
const siteStore = useSiteStore()
|
||||||
|
const siteName = computed(() => siteStore.siteName || 'MES系统')
|
||||||
|
const siteLogo = computed(() => src?.() ?? siteStore.siteLogo)
|
||||||
|
const logoLoadFailed = ref(false)
|
||||||
|
const showSiteLogoImg = computed(() => !!siteLogo.value && !logoLoadFailed.value)
|
||||||
|
const fallbackLetter = computed(() => siteName.value.charAt(0) || 'M')
|
||||||
|
|
||||||
|
watch(siteLogo, () => {
|
||||||
|
logoLoadFailed.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleLogoError() {
|
||||||
|
logoLoadFailed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
siteName,
|
||||||
|
siteLogo,
|
||||||
|
showSiteLogoImg,
|
||||||
|
fallbackLetter,
|
||||||
|
handleLogoError,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,8 +17,11 @@
|
|||||||
>
|
>
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="logo" :class="{ 'logo-collapsed': collapsed, 'logo-primary': themeStore.headerUsePrimaryColor }" :style="themeStore.headerUsePrimaryColor ? { background: themeStore.primaryColor, borderBottomColor: themeStore.primaryColor } : {}">
|
<div class="logo" :class="{ 'logo-collapsed': collapsed, 'logo-primary': themeStore.headerUsePrimaryColor }" :style="themeStore.headerUsePrimaryColor ? { background: themeStore.primaryColor, borderBottomColor: themeStore.primaryColor } : {}">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" :style="{ background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor, color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff' }">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="logoIconStyle"
|
||||||
|
/>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<span v-if="!collapsed" class="logo-text">{{ siteName }}</span>
|
<span v-if="!collapsed" class="logo-text">{{ siteName }}</span>
|
||||||
<!-- <span v-if="!collapsed" class="logo-text">伊特智造MES</span> -->
|
<!-- <span v-if="!collapsed" class="logo-text">伊特智造MES</span> -->
|
||||||
@ -43,8 +46,11 @@
|
|||||||
<n-layout-header bordered class="layout-header" :class="[`theme-${layoutConfig.theme}`, { 'header-primary': themeStore.headerUsePrimaryColor }]" :style="headerStyle">
|
<n-layout-header bordered class="layout-header" :class="[`theme-${layoutConfig.theme}`, { 'header-primary': themeStore.headerUsePrimaryColor }]" :style="headerStyle">
|
||||||
<!-- 顶部菜单模式下的Logo -->
|
<!-- 顶部菜单模式下的Logo -->
|
||||||
<div v-if="layoutConfig.siderPosition === 'top'" class="header-logo">
|
<div v-if="layoutConfig.siderPosition === 'top'" class="header-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" :style="{ background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor, color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff' }">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="logoIconStyle"
|
||||||
|
/>
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -373,6 +379,7 @@ import ProfileModal from '@/components/ProfileModal.vue'
|
|||||||
import PasswordModal from '@/components/PasswordModal.vue'
|
import PasswordModal from '@/components/PasswordModal.vue'
|
||||||
import MessageNotification from '@/components/MessageNotification.vue'
|
import MessageNotification from '@/components/MessageNotification.vue'
|
||||||
import TabBar from '@/components/TabBar.vue'
|
import TabBar from '@/components/TabBar.vue'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
import { noticeApi, chatApi, type SysNotice, type ChatMessage } from '@/api/message'
|
import { noticeApi, chatApi, type SysNotice, type ChatMessage } from '@/api/message'
|
||||||
import { iconMap as externalIconMap } from '@/utils/icons'
|
import { iconMap as externalIconMap } from '@/utils/icons'
|
||||||
|
|
||||||
@ -387,7 +394,10 @@ const themeStore = useThemeStore()
|
|||||||
|
|
||||||
// 站点配置
|
// 站点配置
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
const logoIconStyle = computed(() => ({
|
||||||
|
background: themeStore.headerUsePrimaryColor ? '#fff' : themeStore.primaryColor,
|
||||||
|
color: themeStore.headerUsePrimaryColor ? themeStore.primaryColor : '#fff',
|
||||||
|
}))
|
||||||
|
|
||||||
// 注册全局message
|
// 注册全局message
|
||||||
window.$message = message
|
window.$message = message
|
||||||
|
|||||||
@ -141,6 +141,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/biz/order/index.vue'),
|
component: () => import('@/views/biz/order/index.vue'),
|
||||||
meta: { title: '订单管理', icon: 'ListOutline' }
|
meta: { title: '订单管理', icon: 'ListOutline' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/orderProject',
|
||||||
|
name: 'orderProject',
|
||||||
|
component: () => import('@/views/biz/orderProject/index.vue'),
|
||||||
|
meta: { title: '项目管理', icon: 'ListOutline' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'biz/orderProject/gantt',
|
||||||
|
name: 'OrderProjectGantt',
|
||||||
|
component: () => import('@/views/biz/orderProject/components/GanttSchedule.vue'),
|
||||||
|
meta: { title: '甘特图排产', icon: 'CalendarOutline', activeMenu: '/biz/orderProject' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'biz/code',
|
path: 'biz/code',
|
||||||
name: 'code',
|
name: 'code',
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { configGroupApi } from '@/api/org'
|
|||||||
*/
|
*/
|
||||||
export const useSiteStore = defineStore('site', () => {
|
export const useSiteStore = defineStore('site', () => {
|
||||||
// 站点名称
|
// 站点名称
|
||||||
const siteName = ref('CSY Admin')
|
const siteName = ref('MES系统')
|
||||||
// 站点描述
|
// 站点描述
|
||||||
const siteDescription = ref('伊特智造MES')
|
const siteDescription = ref('伊特智造MES')
|
||||||
// 站点 Logo
|
// 站点 Logo
|
||||||
@ -33,7 +33,7 @@ export const useSiteStore = defineStore('site', () => {
|
|||||||
try {
|
try {
|
||||||
const config = await configGroupApi.getPublicConfig()
|
const config = await configGroupApi.getPublicConfig()
|
||||||
if (config.system) {
|
if (config.system) {
|
||||||
siteName.value = config.system.siteName || 'CSY Admin'
|
siteName.value = config.system.siteName || 'MES系统'
|
||||||
siteDescription.value = config.system.siteDescription || '伊特智造MES'
|
siteDescription.value = config.system.siteDescription || '伊特智造MES'
|
||||||
siteLogo.value = config.system.siteLogo || ''
|
siteLogo.value = config.system.siteLogo || ''
|
||||||
copyright.value = config.system.copyright || ''
|
copyright.value = config.system.copyright || ''
|
||||||
|
|||||||
@ -176,6 +176,21 @@
|
|||||||
<n-input v-else v-model:value="formData.quantity" placeholder="请输入生产数量" disabled />
|
<n-input v-else v-model:value="formData.quantity" placeholder="请输入生产数量" disabled />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="订单编号" path="orderCode">
|
||||||
|
<n-input v-model:value="formData.orderCode" placeholder="订单编号" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="工艺路线编码" path="routeCode">
|
||||||
|
<n-input v-model:value="formData.routeCode" placeholder="工艺路线编码" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-form-item label="生产车间" path="proWorkshop">
|
||||||
|
<n-input v-model:value="formData.proWorkshop" placeholder="生产车间" :disabled="!formData.id" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="是否半成品" path="sfProduct">
|
<n-form-item label="是否半成品" path="sfProduct">
|
||||||
@ -425,6 +440,9 @@ const hasPermission = (permission: string) => userStore.hasPermission(permission
|
|||||||
const searchForm = reactive<any>({
|
const searchForm = reactive<any>({
|
||||||
code: '',
|
code: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
|
orderCode: '',
|
||||||
|
routeCode: '',
|
||||||
|
proWorkshop: '',
|
||||||
beginTime: null as number | null,
|
beginTime: null as number | null,
|
||||||
endTime: null as number | null,
|
endTime: null as number | null,
|
||||||
})
|
})
|
||||||
@ -471,6 +489,9 @@ const defaultFormData = {
|
|||||||
assingWorkOperationId: '',
|
assingWorkOperationId: '',
|
||||||
assingWorkOperationTime: null,
|
assingWorkOperationTime: null,
|
||||||
starter: '',
|
starter: '',
|
||||||
|
orderCode: '',
|
||||||
|
routeCode: '',
|
||||||
|
proWorkshop: '',
|
||||||
}
|
}
|
||||||
const formData = reactive<any>({ ...defaultFormData })
|
const formData = reactive<any>({ ...defaultFormData })
|
||||||
|
|
||||||
@ -503,6 +524,24 @@ const columns = [
|
|||||||
title: '物料编码',
|
title: '物料编码',
|
||||||
key: 'code'
|
key: 'code'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '订单编号',
|
||||||
|
key: 'orderCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '工艺路线编码',
|
||||||
|
key: 'routeCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align:'center',
|
||||||
|
minWidth: 150,
|
||||||
|
title: '生产车间',
|
||||||
|
key: 'proWorkshop',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
align:'center',
|
align:'center',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
@ -642,7 +681,10 @@ async function loadData() {
|
|||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
code: searchForm.code,
|
code: searchForm.code,
|
||||||
projectId: searchForm.projectId,
|
projectName: searchForm.projectName,
|
||||||
|
orderCode: searchForm.orderCode,
|
||||||
|
routeCode: searchForm.routeCode,
|
||||||
|
proWorkshop: searchForm.proWorkshop,
|
||||||
beginTime: searchForm.beginTime,
|
beginTime: searchForm.beginTime,
|
||||||
endTime: searchForm.endTime
|
endTime: searchForm.endTime
|
||||||
})
|
})
|
||||||
@ -662,13 +704,12 @@ function handleSearch() {
|
|||||||
// 重置
|
// 重置
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchForm.code = ''
|
searchForm.code = ''
|
||||||
|
searchForm.projectName = ''
|
||||||
searchForm.projectId = ''
|
searchForm.orderCode = ''
|
||||||
|
searchForm.routeCode = ''
|
||||||
|
searchForm.proWorkshop = ''
|
||||||
searchForm.beginTime = null
|
searchForm.beginTime = null
|
||||||
|
|
||||||
searchForm.endTime = null
|
searchForm.endTime = null
|
||||||
|
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,14 +737,20 @@ let planList = ref<any>([])
|
|||||||
if(formData.code){
|
if(formData.code){
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
orderItemApi.preCreation({
|
orderItemApi.preCreation({
|
||||||
//productionCode:formData.productionCode
|
|
||||||
code:formData.code,
|
code:formData.code,
|
||||||
projectCode:formData.projectCode
|
projectCode:formData.projectCode
|
||||||
}).then((rps:any) => {
|
}).then((rps:any) => {
|
||||||
planList.value = rps.planList?rps.planList:[]
|
planList.value = rps.planList ? rps.planList : []
|
||||||
|
formData.orderCode = rps.orderCode ?? ''
|
||||||
|
formData.routeCode = rps.routeCode ?? ''
|
||||||
|
formData.proWorkshop = rps.proWorkshop ?? ''
|
||||||
|
if (rps.quantity != null) formData.quantity = rps.quantity
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
|
formData.orderCode = ''
|
||||||
|
formData.routeCode = ''
|
||||||
|
formData.proWorkshop = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,6 +807,9 @@ function xmselupdate(v:any,opt:any) {
|
|||||||
planList.value.splice(0)
|
planList.value.splice(0)
|
||||||
wllist.value.splice(0)
|
wllist.value.splice(0)
|
||||||
formData.code = ''
|
formData.code = ''
|
||||||
|
formData.orderCode = ''
|
||||||
|
formData.routeCode = ''
|
||||||
|
formData.proWorkshop = ''
|
||||||
formData.projectName = opt.label
|
formData.projectName = opt.label
|
||||||
if(v){
|
if(v){
|
||||||
wlhand(opt.value)
|
wlhand(opt.value)
|
||||||
@ -1000,7 +1050,10 @@ async function handleExport() {
|
|||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
if (selectedIds.value.length > 0) params.ids = selectedIds.value
|
||||||
if (searchForm.code) params.code = searchForm.code
|
if (searchForm.code) params.code = searchForm.code
|
||||||
if (searchForm.projectId != null) params.projectId = searchForm.projectId
|
if (searchForm.projectName) params.projectName = searchForm.projectName
|
||||||
|
if (searchForm.orderCode) params.orderCode = searchForm.orderCode
|
||||||
|
if (searchForm.routeCode) params.routeCode = searchForm.routeCode
|
||||||
|
if (searchForm.proWorkshop) params.proWorkshop = searchForm.proWorkshop
|
||||||
if (searchForm.beginTime != null) params.beginTime = searchForm.beginTime
|
if (searchForm.beginTime != null) params.beginTime = searchForm.beginTime
|
||||||
if (searchForm.endTime != null) params.endTime = searchForm.endTime
|
if (searchForm.endTime != null) params.endTime = searchForm.endTime
|
||||||
const blob = await orderItemApi.export(params)
|
const blob = await orderItemApi.export(params)
|
||||||
|
|||||||
@ -1,25 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gantt-schedule-page">
|
<div class="gantt-schedule-page">
|
||||||
<div class="gantt-header">
|
<div class="gantt-header">
|
||||||
<n-space justify="space-between" align="center">
|
<div class="gantt-header-left">
|
||||||
<n-h2 style="margin: 0">甘特图排产</n-h2>
|
<h1 class="page-title">甘特图排产</h1>
|
||||||
<n-space>
|
<p class="page-desc">拖拽工序条块调整计划时间,完成后请保存排产草稿</p>
|
||||||
<n-button type="primary" :loading="saving" @click="handleSaveDraft">保存排产草稿</n-button>
|
</div>
|
||||||
<n-button @click="handleBack">返回</n-button>
|
<n-space>
|
||||||
</n-space>
|
<n-button type="primary" :loading="saving" @click="handleSaveDraft">保存排产草稿</n-button>
|
||||||
|
<n-button @click="handleBack">返回</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="gantt-container" ref="ganttContainer"></div>
|
|
||||||
|
<div class="gantt-meta">
|
||||||
|
<span>生产订单 {{ scheduleStats.moCount }} 条</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span>工序 {{ scheduleStats.routeCount }} 道</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span>今日 {{ todayLabel }}</span>
|
||||||
|
<span class="meta-divider">|</span>
|
||||||
|
<span class="meta-legend"><i class="legend-today-line" />今日时间轴</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gantt-panel">
|
||||||
|
<div class="gantt-panel-head">
|
||||||
|
<span class="panel-title">排产明细</span>
|
||||||
|
<span class="panel-tip">主产品行展示物料编码与名称,子行展示工序号与工序名称</span>
|
||||||
|
</div>
|
||||||
|
<div class="gantt-container" ref="ganttContainer" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, toRaw } from 'vue'
|
import { ref, onMounted, onUnmounted, toRaw, computed } from 'vue'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { gantt } from 'dhtmlx-gantt'
|
import { gantt } from 'dhtmlx-gantt'
|
||||||
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
||||||
import { orderProjectApi, type KingdeePrdMo } from '@/api/orderProject'
|
import { orderProjectApi, normalizeKingdeeProcessRoute, resolveProcessName, type KingdeePrdMo } from '@/api/orderProject'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const ganttContainer = ref<HTMLElement | null>(null)
|
const ganttContainer = ref<HTMLElement | null>(null)
|
||||||
@ -30,6 +48,42 @@ const tableData = ref<any[]>([])
|
|||||||
const projectId = ref<number | null>(null)
|
const projectId = ref<number | null>(null)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
|
const PROCESS_COLORS = [
|
||||||
|
'#409eff', '#67c23a', '#e6a23c', '#909399', '#f56c6c',
|
||||||
|
'#36cfc9', '#597ef7', '#9254de', '#73d13d',
|
||||||
|
]
|
||||||
|
|
||||||
|
const WEEKDAY_LABELS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
|
||||||
|
function getRouteColorIndex(index: number) {
|
||||||
|
return index % PROCESS_COLORS.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRouteColor(index: number) {
|
||||||
|
return PROCESS_COLORS[getRouteColorIndex(index)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDayScale(date: Date) {
|
||||||
|
return `${date.getDate()}日 ${WEEKDAY_LABELS[date.getDay()]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleStats = computed(() => {
|
||||||
|
let routeCount = 0
|
||||||
|
tableData.value.forEach((mo) => {
|
||||||
|
routeCount += mo.processRoutes?.length ?? 0
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
moCount: tableData.value.length,
|
||||||
|
routeCount,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const todayLabel = computed(() => {
|
||||||
|
const d = new Date()
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||||
|
})
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
@ -39,22 +93,17 @@ async function handleSaveDraft() {
|
|||||||
message.warning('缺少项目ID,无法保存')
|
message.warning('缺少项目ID,无法保存')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
|
||||||
const draftData = tableData.value.map(mo => {
|
const draftData = tableData.value.map(mo => {
|
||||||
const { processRoutes, ...rest } = mo
|
const { processRoutes, ...rest } = mo
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
})
|
})
|
||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderDraft(projectId.value, draftData)
|
await orderProjectApi.saveKingdeeOrderDraft(projectId.value, draftData)
|
||||||
message.success('排产草稿保存成功')
|
message.success('排产草稿保存成功')
|
||||||
|
|
||||||
// 更新 session 里的暂存,以防返回页面时不一致
|
|
||||||
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(tableData.value))
|
sessionStorage.setItem('gantt_schedule_data', JSON.stringify(tableData.value))
|
||||||
} catch (error) {
|
|
||||||
// 错误拦截器已处理
|
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@ -62,25 +111,26 @@ async function handleSaveDraft() {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
initGantt()
|
initGantt()
|
||||||
|
|
||||||
|
|
||||||
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
const stateStr = sessionStorage.getItem('gantt_schedule_data')
|
||||||
const idStr = sessionStorage.getItem('gantt_project_id')
|
const idStr = sessionStorage.getItem('gantt_project_id')
|
||||||
|
|
||||||
if (idStr) {
|
if (idStr) {
|
||||||
projectId.value = Number(idStr)
|
projectId.value = Number(idStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateStr) {
|
if (stateStr) {
|
||||||
const parsedData = JSON.parse(stateStr)
|
const parsedData = JSON.parse(stateStr)
|
||||||
tableData.value = parsedData
|
tableData.value = (Array.isArray(parsedData) ? parsedData : []).map((mo: any) => ({
|
||||||
|
...mo,
|
||||||
|
processRoutes: (mo.processRoutes ?? []).map(normalizeKingdeeProcessRoute),
|
||||||
|
}))
|
||||||
renderGanttData()
|
renderGanttData()
|
||||||
} else {
|
} else {
|
||||||
message.warning('未获取到排产数据')
|
message.warning('未获取到排产数据')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('加载排产数据失败')
|
message.error('加载排产数据失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -90,204 +140,283 @@ function formatGanttDate(date: Date) {
|
|||||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatGridDate(value: Date | string | undefined) {
|
||||||
|
if (!value) return ''
|
||||||
|
const date = value instanceof Date ? value : new Date(String(value).replace(' ', 'T'))
|
||||||
|
if (isNaN(date.getTime())) return ''
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoMaterialLabel(mo: any) {
|
||||||
|
const code = mo.materialCode?.trim() || ''
|
||||||
|
const name = mo.materialName?.trim() || ''
|
||||||
|
if (code && name) return `${code} ${name}`
|
||||||
|
return code || name
|
||||||
|
}
|
||||||
|
|
||||||
function initGantt() {
|
function initGantt() {
|
||||||
if (!ganttContainer.value) return
|
if (!ganttContainer.value) return
|
||||||
|
|
||||||
// 清理之前的事件绑定
|
|
||||||
ganttEventIds.forEach(id => gantt.detachEvent(id))
|
ganttEventIds.forEach(id => gantt.detachEvent(id))
|
||||||
ganttEventIds = []
|
ganttEventIds = []
|
||||||
|
|
||||||
gantt.clearAll()
|
gantt.clearAll()
|
||||||
|
|
||||||
// 配置甘特图
|
gantt.config.date_format = '%Y-%m-%d %H:%i:%s'
|
||||||
gantt.config.date_format = "%Y-%m-%d %H:%i:%s"
|
gantt.config.scale_height = 60
|
||||||
gantt.config.scale_height = 50
|
gantt.config.row_height = 40
|
||||||
|
gantt.config.bar_height = 26
|
||||||
gantt.config.scales = [
|
gantt.config.scales = [
|
||||||
{ unit: "month", step: 1, format: "%Y年 %m月" },
|
{ unit: 'month', step: 1, format: '%Y年 %m月' },
|
||||||
{ unit: "day", step: 1, format: "%d日" }
|
{ unit: 'day', step: 1, format: formatDayScale },
|
||||||
]
|
]
|
||||||
gantt.config.columns = [
|
gantt.config.columns = [
|
||||||
{ name: "text", label: "单号 / 序号", tree: true, width: 200, resize: true },
|
{
|
||||||
{ name: "materialCode", label: "物料编码", align: "center", width: 140, resize: true },
|
name: 'text',
|
||||||
{ name: "name", label: "名称", align: "center", width: 160, resize: true },
|
label: '工序号 / 物料编码',
|
||||||
{ name: "start_date", label: "计划开始", align: "center", width: 130 },
|
tree: true,
|
||||||
{ name: "end_date", label: "计划结束", align: "center", width: 130 },
|
width: 200,
|
||||||
|
resize: true,
|
||||||
|
template(task: any) {
|
||||||
|
if (task._type === 'route') return task.operNumber ?? ''
|
||||||
|
return task.materialCode ?? ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'routeProcessName',
|
||||||
|
label: '工序名称 / 物料名称',
|
||||||
|
align: 'center',
|
||||||
|
width: 168,
|
||||||
|
resize: true,
|
||||||
|
template(task: any) {
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return task.routeProcessName ?? resolveProcessName(task._raw ?? {}) ?? ''
|
||||||
|
}
|
||||||
|
return task.materialName ?? ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'start_date',
|
||||||
|
label: '计划开始',
|
||||||
|
align: 'center',
|
||||||
|
width: 118,
|
||||||
|
template(task: any) {
|
||||||
|
return formatGridDate(task.start_date)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'end_date',
|
||||||
|
label: '计划结束',
|
||||||
|
align: 'center',
|
||||||
|
width: 118,
|
||||||
|
template(task: any) {
|
||||||
|
return formatGridDate(task.end_date)
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
//??
|
|
||||||
gantt.config.layout = {
|
gantt.config.layout = {
|
||||||
css: "gantt_container",
|
css: 'gantt_container',
|
||||||
cols: [
|
cols: [
|
||||||
{
|
{
|
||||||
width: 300,
|
width: 620,
|
||||||
minWidth: 200,
|
minWidth: 420,
|
||||||
maxWidth: 600,
|
maxWidth: 680,
|
||||||
rows: [
|
rows: [
|
||||||
{ view: "grid", scrollX: "gridScroll", scrollable: true,
|
{ view: 'grid', scrollX: 'gridScroll', scrollable: true, scrollY: 'scrollVer' },
|
||||||
scrollY: "scrollVer"
|
{ view: 'scrollbar', id: 'gridScroll', group: 'horizontal' },
|
||||||
},
|
],
|
||||||
{ view: "scrollbar", id: "gridScroll", group: "horizontal" } /*!*/
|
},
|
||||||
]
|
{ resizer: true, width: 1 },
|
||||||
},
|
{
|
||||||
{ resizer: true, width: 1 },
|
rows: [
|
||||||
{
|
{ view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
|
||||||
rows: [
|
{ view: 'scrollbar', id: 'scrollHor', group: 'horizontal' },
|
||||||
{ view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
|
],
|
||||||
{ view: "scrollbar", id: "scrollHor", group: "horizontal" } /*!*/
|
},
|
||||||
]
|
{ view: 'scrollbar', id: 'scrollVer' },
|
||||||
},
|
],
|
||||||
{ view: "scrollbar", id: "scrollVer" }
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
//??
|
|
||||||
|
|
||||||
// 行为配置
|
gantt.config.drag_progress = false
|
||||||
gantt.config.drag_progress = false // 禁用进度拖拽
|
gantt.config.drag_links = false
|
||||||
gantt.config.drag_links = false // 禁用连线拖拽(视需求而定)
|
// 使用自然日历(含周六日),不按工作日历自动顺延
|
||||||
gantt.config.work_time = true // 启用工作时间计算
|
gantt.config.work_time = false
|
||||||
// 注意:gantt.config.auto_scheduling 是商业版功能,开源版不能开启,否则会报错
|
gantt.config.correct_work_time = false
|
||||||
|
gantt.config.skip_off_time = false
|
||||||
gantt.i18n.setLocale("cn")
|
gantt.config.duration_unit = 'day'
|
||||||
|
gantt.config.smart_rendering = true
|
||||||
|
|
||||||
// 启用 marker 插件(今日时间轴)
|
gantt.i18n.setLocale('cn')
|
||||||
gantt.plugins({
|
gantt.plugins({ marker: true })
|
||||||
marker: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 图表条块文字自定义
|
gantt.templates.task_text = function(_start, _end, task) {
|
||||||
gantt.templates.task_text = function(start, end, task) {
|
|
||||||
if (task._type === 'route') {
|
if (task._type === 'route') {
|
||||||
return task.name; // 工序在图上显示工序名称
|
return task.routeProcessName || resolveProcessName(task._raw ?? {}) || ''
|
||||||
}
|
}
|
||||||
return task.text;
|
return task.materialName || task.materialLabel || ''
|
||||||
};
|
}
|
||||||
|
|
||||||
|
gantt.templates.task_class = function(_start, _end, task) {
|
||||||
|
if (task._type === 'mo') return 'gantt-task-mo'
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return `gantt-task-route gantt-route-c-${task.colorIndex ?? 0}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
gantt.templates.grid_row_class = function(_start, _end, task) {
|
||||||
|
if (task._type === 'mo') return 'gantt-row-mo'
|
||||||
|
if (task._type === 'route') {
|
||||||
|
return `gantt-row-route gantt-route-c-${task.colorIndex ?? 0}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
gantt.templates.timeline_cell_class = function(_task, date) {
|
||||||
|
const day = date.getDay()
|
||||||
|
if (day === 0 || day === 6) return 'gantt-weekend'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
gantt.init(ganttContainer.value)
|
gantt.init(ganttContainer.value)
|
||||||
|
}
|
||||||
|
|
||||||
// 添加今日时间轴标记
|
/** 今日时间轴标记(数据刷新后需重新添加) */
|
||||||
const today = new Date()
|
function addTodayMarker() {
|
||||||
|
const now = new Date()
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 12, 0, 0)
|
||||||
gantt.addMarker({
|
gantt.addMarker({
|
||||||
start_date: today,
|
start_date: today,
|
||||||
css: "today-marker",
|
css: 'today-marker',
|
||||||
text: "今日",
|
text: '今日',
|
||||||
title: "今日: " + formatGanttDate(today)
|
title: `今日: ${formatGanttDate(now)}`,
|
||||||
})
|
})
|
||||||
|
gantt.showDate(today)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRouteProcessName(route: any) {
|
||||||
|
return resolveProcessName(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGanttData() {
|
function renderGanttData() {
|
||||||
gantt.clearAll()
|
gantt.clearAll()
|
||||||
// 转换数据格式
|
|
||||||
const tasks: any[] = []
|
const tasks: any[] = []
|
||||||
|
|
||||||
|
|
||||||
const rawData = toRaw(tableData.value) || []
|
const rawData = toRaw(tableData.value) || []
|
||||||
const processColors = ['#3498db', '#1abc9c', '#9b59b6', '#e67e22', '#e74c3c', '#2ecc71', '#f1c40f', '#34495e', '#7f8c8d']
|
|
||||||
|
|
||||||
rawData.forEach((mo: any, moIndex: number) => {
|
rawData.forEach((mo: any, moIndex: number) => {
|
||||||
const moId = `mo_${moIndex}_${mo.productionOrderNo}_${mo.billNo}`
|
const moId = `mo_${moIndex}_${mo.productionOrderNo}_${mo.billNo}`
|
||||||
// 寻找子工序中最早和最晚的时间作为项目时间(或者使用默认时间)
|
|
||||||
let minDate: Date | null = mo.planStartTime ? new Date(mo.planStartTime.replace(' ', 'T')) : null
|
let minDate: Date | null = mo.planStartTime ? new Date(mo.planStartTime.replace(' ', 'T')) : null
|
||||||
let maxDate: Date | null = mo.planFinishTime ? new Date(mo.planFinishTime.replace(' ', 'T')) : null
|
let maxDate: Date | null = mo.planFinishTime ? new Date(mo.planFinishTime.replace(' ', 'T')) : null
|
||||||
if (!minDate || isNaN(minDate.getTime())) minDate = new Date()
|
if (!minDate || isNaN(minDate.getTime())) minDate = new Date()
|
||||||
if (!maxDate || isNaN(maxDate.getTime())) maxDate = new Date(minDate.getTime() + 86400000)
|
if (!maxDate || isNaN(maxDate.getTime())) maxDate = new Date(minDate.getTime() + 86400000)
|
||||||
if (maxDate <= minDate) maxDate = new Date(minDate.getTime() + 86400000)
|
if (maxDate <= minDate) maxDate = new Date(minDate.getTime() + 86400000)
|
||||||
|
|
||||||
|
const materialLabel = formatMoMaterialLabel(mo)
|
||||||
tasks.push({
|
tasks.push({
|
||||||
id: moId,
|
id: moId,
|
||||||
text: mo.materialCode || '', //mo.productionOrderNo || mo.billNo || '未知单号', ??
|
text: materialLabel,
|
||||||
|
materialLabel,
|
||||||
materialCode: mo.materialCode || '',
|
materialCode: mo.materialCode || '',
|
||||||
name: mo.materialName || '产品',
|
materialName: mo.materialName || '',
|
||||||
|
operNumber: '',
|
||||||
|
processName: '',
|
||||||
start_date: formatGanttDate(minDate),
|
start_date: formatGanttDate(minDate),
|
||||||
end_date: formatGanttDate(maxDate),
|
end_date: formatGanttDate(maxDate),
|
||||||
open: false, // 树形默认关闭
|
open: true,
|
||||||
type: gantt.config.types.project,
|
type: gantt.config.types.project,
|
||||||
_type: 'mo',
|
_type: 'mo',
|
||||||
progress:0.6, //??
|
progress: 0,
|
||||||
_raw: mo
|
_raw: mo,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (mo.processRoutes && mo.processRoutes.length > 0) {
|
if (mo.processRoutes?.length > 0) {
|
||||||
mo.processRoutes.forEach((route: any, index: number) => {
|
mo.processRoutes.forEach((route: any, index: number) => {
|
||||||
const routeId = `route_${moId}_${index}`
|
const routeId = `route_${moId}_${index}`
|
||||||
let rStart = route.planStartTime ? new Date(route.planStartTime.replace(' ', 'T')) : null
|
let rStart = route.planStartTime ? new Date(route.planStartTime.replace(' ', 'T')) : null
|
||||||
let rEnd = route.planFinishTime ? new Date(route.planFinishTime.replace(' ', 'T')) : null
|
let rEnd = route.planFinishTime ? new Date(route.planFinishTime.replace(' ', 'T')) : null
|
||||||
|
|
||||||
if (!rStart || isNaN(rStart.getTime())) rStart = minDate
|
if (!rStart || isNaN(rStart.getTime())) rStart = minDate
|
||||||
if (!rEnd || isNaN(rEnd.getTime())) rEnd = maxDate
|
if (!rEnd || isNaN(rEnd.getTime())) rEnd = maxDate
|
||||||
if (rEnd <= rStart) rEnd = new Date(rStart!.getTime() + 3600000) // 默认工序时长加1小时
|
if (rEnd <= rStart) rEnd = new Date(rStart!.getTime() + 3600000)
|
||||||
|
|
||||||
|
const operNumber = route.operNumber != null ? String(route.operNumber) : String(index + 1)
|
||||||
|
const routeProcessName = getRouteProcessName(route)
|
||||||
|
const colorIndex = getRouteColorIndex(index)
|
||||||
|
const routeColor = getRouteColor(index)
|
||||||
|
|
||||||
tasks.push({
|
tasks.push({
|
||||||
id: routeId,
|
id: routeId,
|
||||||
text:mo.materialCode || '', //route.operNumber != null ? String(route.operNumber) : String(index + 1), ??
|
text: operNumber,
|
||||||
materialCode: '',
|
operNumber,
|
||||||
name: route.processProperty || '工序',
|
routeProcessName,
|
||||||
color: processColors[index % processColors.length],
|
colorIndex,
|
||||||
|
routeColor,
|
||||||
|
color: routeColor,
|
||||||
start_date: formatGanttDate(rStart!),
|
start_date: formatGanttDate(rStart!),
|
||||||
end_date: formatGanttDate(rEnd),
|
end_date: formatGanttDate(rEnd),
|
||||||
parent: moId,
|
parent: moId,
|
||||||
_type: 'route',
|
_type: 'route',
|
||||||
progress:0.6, //??
|
progress: 0,
|
||||||
_raw: route
|
_raw: route,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
gantt.parse({ data: tasks, links: [] })
|
gantt.parse({ data: tasks, links: [] })
|
||||||
|
addTodayMarker()
|
||||||
|
|
||||||
// 限制只能拖拽工序,不能直接拖拽主工单(项目)
|
const evDragId = gantt.attachEvent('onBeforeTaskDrag', (id) => {
|
||||||
const evDragId = gantt.attachEvent("onBeforeTaskDrag", (id, mode, e) => {
|
|
||||||
const task = gantt.getTask(id)
|
const task = gantt.getTask(id)
|
||||||
if (task.type === gantt.config.types.project) {
|
return task.type !== gantt.config.types.project
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
ganttEventIds.push(evDragId)
|
ganttEventIds.push(evDragId)
|
||||||
|
|
||||||
// 绑定拖拽结束事件
|
const evId = gantt.attachEvent('onAfterTaskDrag', (id, mode) => {
|
||||||
const evId = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
|
const task = gantt.getTask(id)
|
||||||
const task = gantt.getTask(id)
|
if (mode === 'move' || mode === 'resize') {
|
||||||
if (mode === 'move' || mode === 'resize') {
|
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
||||||
const start = gantt.date.date_to_str(gantt.config.date_format)(task.start_date)
|
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
||||||
const end = gantt.date.date_to_str(gantt.config.date_format)(task.end_date)
|
|
||||||
|
if (task._type === 'mo') {
|
||||||
// 更新原数据
|
task._raw.planStartTime = start
|
||||||
if (task._type === 'mo') {
|
task._raw.planFinishTime = end
|
||||||
task._raw.planStartTime = start
|
} else if (task._type === 'route') {
|
||||||
task._raw.planFinishTime = end
|
task._raw.planStartTime = start
|
||||||
} else if (task._type === 'route') {
|
task._raw.planFinishTime = end
|
||||||
task._raw.planStartTime = start
|
|
||||||
task._raw.planFinishTime = end
|
const parentId = task.parent
|
||||||
|
if (parentId) {
|
||||||
// 自动汇总更新父级工单的时间
|
const parentTask = gantt.getTask(parentId)
|
||||||
const parentId = task.parent
|
const children = gantt.getChildren(parentId)
|
||||||
if (parentId) {
|
let minD: Date | null = null
|
||||||
const parentTask = gantt.getTask(parentId)
|
let maxD: Date | null = null
|
||||||
if (parentTask) {
|
|
||||||
const children = gantt.getChildren(parentId)
|
children.forEach((childId: string | number) => {
|
||||||
let minDate: Date | null = null
|
const childTask = gantt.getTask(childId)
|
||||||
let maxDate: Date | null = null
|
const cs = childTask.start_date
|
||||||
|
const ce = childTask.end_date
|
||||||
children.forEach((childId: string | number) => {
|
if (cs && (!minD || cs < minD)) minD = cs
|
||||||
const childTask = gantt.getTask(childId)
|
if (ce && (!maxD || ce > maxD)) maxD = ce
|
||||||
if (!minDate || childTask.start_date < minDate) minDate = childTask.start_date
|
})
|
||||||
if (!maxDate || childTask.end_date > maxDate) maxDate = childTask.end_date
|
|
||||||
})
|
if (minD && maxD) {
|
||||||
|
parentTask.start_date = new Date(minD)
|
||||||
if (minDate && maxDate) {
|
parentTask.end_date = new Date(maxD)
|
||||||
parentTask.start_date = new Date(minDate)
|
parentTask._raw.planStartTime = gantt.date.date_to_str(gantt.config.date_format)(minD)
|
||||||
parentTask.end_date = new Date(maxDate)
|
parentTask._raw.planFinishTime = gantt.date.date_to_str(gantt.config.date_format)(maxD)
|
||||||
parentTask._raw.planStartTime = gantt.date.date_to_str(gantt.config.date_format)(minDate)
|
gantt.updateTask(parentId)
|
||||||
parentTask._raw.planFinishTime = gantt.date.date_to_str(gantt.config.date_format)(maxDate)
|
|
||||||
gantt.updateTask(parentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success(`任务 [${task.text}] 计划时间已更新`)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
const label = task._type === 'route'
|
||||||
|
? (task.routeProcessName || task.operNumber)
|
||||||
|
: (task.materialLabel || task.materialName)
|
||||||
|
message.success(`任务 [${label}] 计划时间已更新`)
|
||||||
|
}
|
||||||
|
})
|
||||||
ganttEventIds.push(evId)
|
ganttEventIds.push(evId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,36 +428,249 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.gantt-schedule-page {
|
.gantt-schedule-page {
|
||||||
|
--gs-font: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', sans-serif;
|
||||||
|
--gs-title: #333333;
|
||||||
|
--gs-text: #606266;
|
||||||
|
--gs-border: #e4e7ed;
|
||||||
|
--gs-bg: #f5f7fa;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 120px);
|
||||||
background-color: var(--n-color);
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-family: var(--gs-font);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
background: var(--gs-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gantt-header {
|
.gantt-header {
|
||||||
margin-bottom: 16px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--gs-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-header-left {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-meta {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-divider {
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-legend {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-today-line {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
background: #f56c6c;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--gs-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid var(--gs-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gantt-container {
|
.gantt-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0; /* 必须设置min-height以允许flex子元素内部滚动 */
|
min-height: 0;
|
||||||
border: 1px solid var(--n-border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.today-marker) {
|
/* dhtmlx-gantt:仅表头标题加粗,其余正常字重 */
|
||||||
background-color: #ff5252;
|
.gantt-container :deep(.gantt_container) {
|
||||||
|
font-family: var(--gs-font);
|
||||||
|
font-weight: 400;
|
||||||
|
border: none !important;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
:deep(.today-marker .gantt_marker_content) {
|
|
||||||
background-color: #ff5252;
|
.gantt-container :deep(.gantt_grid_scale),
|
||||||
color: white;
|
.gantt-container :deep(.gantt_task_scale) {
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_scale .gantt_scale_cell) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_grid_scale .gantt_grid_head_cell) {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gs-title);
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工序号文字与进度条同色(无圆点) */
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-0 .gantt_cell:first-child) { color: #409eff; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-1 .gantt_cell:first-child) { color: #67c23a; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-2 .gantt_cell:first-child) { color: #e6a23c; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-3 .gantt_cell:first-child) { color: #909399; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-4 .gantt_cell:first-child) { color: #f56c6c; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-5 .gantt_cell:first-child) { color: #36cfc9; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-6 .gantt_cell:first-child) { color: #597ef7; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-7 .gantt_cell:first-child) { color: #9254de; }
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-route.gantt-route-c-8 .gantt_cell:first-child) { color: #73d13d; }
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-0) { background-color: #409eff !important; border-color: #409eff !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-1) { background-color: #67c23a !important; border-color: #67c23a !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-2) { background-color: #e6a23c !important; border-color: #e6a23c !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-3) { background-color: #909399 !important; border-color: #909399 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-4) { background-color: #f56c6c !important; border-color: #f56c6c !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-5) { background-color: #36cfc9 !important; border-color: #36cfc9 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-6) { background-color: #597ef7 !important; border-color: #597ef7 !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-7) { background-color: #9254de !important; border-color: #9254de !important; }
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-route-c-8) { background-color: #73d13d !important; border-color: #73d13d !important; }
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route .gantt_task_progress) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.18) !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route) {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_grid_data .gantt_cell) {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-text);
|
||||||
|
border-color: #f0f2f5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-mo) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row.gantt-row-mo .gantt_cell) {
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--gs-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_row:hover) {
|
||||||
|
background: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_row.gantt_selected),
|
||||||
|
.gantt-container :deep(.gantt_row.gantt_selected) {
|
||||||
|
background: #ecf5ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt-task-route .gantt_task_content) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_task_line.gantt_project) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_weekend) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_marker.today-marker) {
|
||||||
|
background: #f56c6c;
|
||||||
|
width: 2px;
|
||||||
|
z-index: 4;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.today-marker .gantt_marker_content) {
|
||||||
|
background: #f56c6c;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 0 4px;
|
padding: 2px 8px;
|
||||||
|
box-shadow: 0 1px 4px rgba(245, 108, 108, 0.35);
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.gantt-container :deep(.gantt_layout_content) {
|
||||||
|
border-color: var(--gs-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container :deep(.gantt_hor_scroll),
|
||||||
|
.gantt-container :deep(.gantt_ver_scroll) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -229,7 +229,7 @@
|
|||||||
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, h, computed, onMounted, watch } from 'vue'
|
||||||
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
|
||||||
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
|
||||||
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
|
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute, normalizeKingdeeProcessRoute } from '@/api/orderProject'
|
||||||
import { dictDataApi } from '@/api/org'
|
import { dictDataApi } from '@/api/org'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
@ -400,8 +400,6 @@ const formData = reactive<OrderProject>({ ...defaultFormData })
|
|||||||
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
// 字典选项(下拉框/单选框/复选框关联字典时使用)
|
||||||
const sourceOptions = ref<{ label: string; value: any }[]>([])
|
const sourceOptions = ref<{ label: string; value: any }[]>([])
|
||||||
const statusOptions = ref<{ label: string; value: any }[]>([])
|
const statusOptions = ref<{ label: string; value: any }[]>([])
|
||||||
const prodTypeOptions = ref<{label: string; value:any} []>([])
|
|
||||||
|
|
||||||
|
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
@ -421,23 +419,11 @@ const columns: DataTableColumns<OrderProject> = [
|
|||||||
{ title: '项目来源', key: 'source', width: 110,align: 'center',
|
{ title: '项目来源', key: 'source', width: 110,align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const val = row.source
|
const val = row.source
|
||||||
console.log(sourceOptions.value);
|
|
||||||
|
|
||||||
const opt = sourceOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = sourceOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
|
||||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ title: '生产类型', key: 'prodType', width: 100 ,align: 'center',
|
|
||||||
render(row) {
|
|
||||||
const val = row.prodType
|
|
||||||
const opt = prodTypeOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
|
||||||
if (!opt) return val ?? '-'
|
|
||||||
|
|
||||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ title: '生产类型', key: 'prodType', width: 100 ,align: 'center'},
|
||||||
{ title: 'BOM版本', key: 'bomVersion', width: 100 ,align: 'center'},
|
{ title: 'BOM版本', key: 'bomVersion', width: 100 ,align: 'center'},
|
||||||
{ title: '规格型号', key: 'specModel', width: 140, ellipsis: { tooltip: true } },
|
{ title: '规格型号', key: 'specModel', width: 140, ellipsis: { tooltip: true } },
|
||||||
{ title: '单位', key: 'unit', width: 70 ,align: 'center'},
|
{ title: '单位', key: 'unit', width: 70 ,align: 'center'},
|
||||||
@ -448,9 +434,7 @@ const columns: DataTableColumns<OrderProject> = [
|
|||||||
render(row) {
|
render(row) {
|
||||||
const val = row.status
|
const val = row.status
|
||||||
const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
const opt = statusOptions.value.find(o => o.value === val || String(o.value) === String(val))
|
||||||
if (!opt) return val ?? '-'
|
return opt ? opt.label : (val ?? '-')
|
||||||
|
|
||||||
return h(NTag, { type: opt.class, size: 'small' }, { default: () => opt.label })
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '计划完成日期', key: 'planFinishTime', width: 170 ,align: 'center'},
|
{ title: '计划完成日期', key: 'planFinishTime', width: 170 ,align: 'center'},
|
||||||
@ -486,9 +470,8 @@ const columns: DataTableColumns<OrderProject> = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const pickMtrlStatusMap: Record<string, string> = {
|
const pickMtrlStatusMap: Record<string, string> = {
|
||||||
'1': '未领料',
|
'0': '未领料',
|
||||||
'2': '待定',
|
'1': '已领料',
|
||||||
'3': '已领料',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
||||||
@ -517,13 +500,13 @@ const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
|||||||
{ title: '生产车间', key: 'workShopName', width: 100, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '生产车间', key: 'workShopName', width: 100, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '规格型号', key: 'specModel', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '单位', key: 'unit', width: 60, align: 'center' },
|
{ title: '单位', key: 'unit', width: 60, align: 'center' },
|
||||||
{
|
{
|
||||||
title: '计划开工', key: 'planStartTime', width: 180, align: 'center',
|
title: '计划开工', key: 'planStartTime', width: 180, align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
return renderKingdeePlanPickerMo(row, 'planStartTime')
|
return renderKingdeePlanPickerMo(row, 'planStartTime')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '计划完工', key: 'planFinishTime', width: 180, align: 'center',
|
title: '计划完工', key: 'planFinishTime', width: 180, align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
return renderKingdeePlanPickerMo(row, 'planFinishTime')
|
return renderKingdeePlanPickerMo(row, 'planFinishTime')
|
||||||
@ -533,8 +516,6 @@ const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
|
|||||||
title: '领料状态', key: 'pickMtrlStatus', width: 90, align: 'center',
|
title: '领料状态', key: 'pickMtrlStatus', width: 90, align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const val = row.pickMtrlStatus
|
const val = row.pickMtrlStatus
|
||||||
console.log(val);
|
|
||||||
|
|
||||||
return val != null ? (pickMtrlStatusMap[String(val)] ?? val) : '-'
|
return val != null ? (pickMtrlStatusMap[String(val)] ?? val) : '-'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -553,7 +534,7 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
|||||||
return row.operNumber != null ? String(row.operNumber) : '-'
|
return row.operNumber != null ? String(row.operNumber) : '-'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '工序名称', key: 'processProperty', width: 100, align: 'center' },
|
{ title: '工序名称', key: 'processName', width: 100, align: 'center' },
|
||||||
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
|
||||||
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
|
||||||
@ -576,7 +557,7 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
|
||||||
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
{ title: '工序控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
@ -726,11 +707,13 @@ async function handleKingdeeQuery(row: OrderProject) {
|
|||||||
const data = await orderProjectApi.getKingdeeOrder(row.id)
|
const data = await orderProjectApi.getKingdeeOrder(row.id)
|
||||||
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => {
|
||||||
const { children, ...rest } = mo
|
const { children, ...rest } = mo
|
||||||
const routes = [...(children ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0)).map((r) => ({
|
const routes = [...(children ?? [])]
|
||||||
...r,
|
.sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
|
||||||
planStartTime: r.planStartTime ?? null,
|
.map((r) => normalizeKingdeeProcessRoute({
|
||||||
planFinishTime: r.planFinishTime ?? null,
|
...r,
|
||||||
}))
|
planStartTime: r.planStartTime ?? null,
|
||||||
|
planFinishTime: r.planFinishTime ?? null,
|
||||||
|
}))
|
||||||
return { ...rest, processRoutes: routes }
|
return { ...rest, processRoutes: routes }
|
||||||
})
|
})
|
||||||
if (kingdeeTableData.value.length === 0) {
|
if (kingdeeTableData.value.length === 0) {
|
||||||
@ -744,7 +727,7 @@ async function handleKingdeeQuery(row: OrderProject) {
|
|||||||
// 暂存金蝶生产订单数据(草稿)
|
// 暂存金蝶生产订单数据(草稿)
|
||||||
async function handleSaveDraft() {
|
async function handleSaveDraft() {
|
||||||
if (!kingdeeCurrentProjectId.value) return
|
if (!kingdeeCurrentProjectId.value) return
|
||||||
|
|
||||||
kingdeeLoading.value = true
|
kingdeeLoading.value = true
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
@ -752,7 +735,7 @@ async function handleSaveDraft() {
|
|||||||
const { processRoutes, ...rest } = mo
|
const { processRoutes, ...rest } = mo
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
})
|
})
|
||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
await orderProjectApi.saveKingdeeOrderDraft(kingdeeCurrentProjectId.value, draftData)
|
||||||
message.success('草稿保存成功')
|
message.success('草稿保存成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -765,7 +748,7 @@ async function handleSaveDraft() {
|
|||||||
// 同步订单和计划表
|
// 同步订单和计划表
|
||||||
async function handleSyncOrderAndPlan() {
|
async function handleSyncOrderAndPlan() {
|
||||||
if (!kingdeeCurrentProjectId.value) return
|
if (!kingdeeCurrentProjectId.value) return
|
||||||
|
|
||||||
kingdeeLoading.value = true
|
kingdeeLoading.value = true
|
||||||
try {
|
try {
|
||||||
// 恢复数据结构,将 processRoutes 转换为 children
|
// 恢复数据结构,将 processRoutes 转换为 children
|
||||||
@ -773,7 +756,7 @@ async function handleSyncOrderAndPlan() {
|
|||||||
const { processRoutes, ...rest } = mo
|
const { processRoutes, ...rest } = mo
|
||||||
return { ...rest, children: processRoutes } as KingdeePrdMo
|
return { ...rest, children: processRoutes } as KingdeePrdMo
|
||||||
})
|
})
|
||||||
|
|
||||||
await orderProjectApi.saveKingdeeOrderAndPlan(kingdeeCurrentProjectId.value, draftData)
|
await orderProjectApi.saveKingdeeOrderAndPlan(kingdeeCurrentProjectId.value, draftData)
|
||||||
message.success('同步订单计划成功')
|
message.success('同步订单计划成功')
|
||||||
kingdeeModalVisible.value = false
|
kingdeeModalVisible.value = false
|
||||||
@ -883,16 +866,11 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
|
|||||||
async function loadDictOptions() {
|
async function loadDictOptions() {
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('pro_source')
|
const data = await dictDataApi.listByType('pro_source')
|
||||||
sourceOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
sourceOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||||
} catch {}
|
} catch {}
|
||||||
try {
|
try {
|
||||||
const data = await dictDataApi.listByType('pro_status')
|
const data = await dictDataApi.listByType('pro_status')
|
||||||
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue),class:d.listClass }))
|
statusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
|
||||||
} catch {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await dictDataApi.listByType("prod_type")
|
|
||||||
prodTypeOptions.value = data.map(d=>({label: d.dictLabel,value:(Number(d.dictValue) || d.dictValue),class:d.listClass}))
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,7 @@
|
|||||||
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -111,8 +110,7 @@
|
|||||||
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="login-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -200,8 +198,11 @@
|
|||||||
<div class="login-banner" style="background: transparent;">
|
<div class="login-banner" style="background: transparent;">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark
|
||||||
<div v-else class="logo-icon" style="background: rgba(255, 255, 255, 0.15); color: #fff; border: 1px solid rgba(255, 255, 255, 0.2);">{{ siteName.charAt(0) }}</div>
|
img-class="logo-img"
|
||||||
|
icon-class="logo-icon"
|
||||||
|
:icon-style="glassLogoIconStyle"
|
||||||
|
/>
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title" style="color: #fff;">{{ siteDescription || '后台管理系统' }}</h1>
|
<h1 class="banner-title" style="color: #fff;">{{ siteDescription || '后台管理系统' }}</h1>
|
||||||
@ -350,6 +351,7 @@ import { useSiteStore } from '@/stores/site'
|
|||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import { authApi } from '@/api/auth'
|
import { authApi } from '@/api/auth'
|
||||||
import { configGroupApi } from '@/api/org'
|
import { configGroupApi } from '@/api/org'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -359,10 +361,14 @@ const siteStore = useSiteStore()
|
|||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
|
||||||
// 站点配置(带默认值)
|
// 站点配置(带默认值)
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'MES系统')
|
||||||
const siteDescription = computed(() => siteStore.siteDescription || '伊特智造MES')
|
const siteDescription = computed(() => siteStore.siteDescription || '伊特智造MES')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
|
||||||
const copyright = computed(() => siteStore.copyright || '版权所有 © 河北伊特')
|
const copyright = computed(() => siteStore.copyright || '版权所有 © 河北伊特')
|
||||||
|
const glassLogoIconStyle = {
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||||
|
}
|
||||||
|
|
||||||
// 登录配置
|
// 登录配置
|
||||||
const captchaEnabled = ref(false)
|
const captchaEnabled = ref(false)
|
||||||
|
|||||||
@ -5,8 +5,7 @@
|
|||||||
<div class="register-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
<div class="register-banner" :style="{ background: `linear-gradient(135deg, ${themeStore.primaryColor} 0%, ${adjustColor(themeStore.primaryColor, -30)} 100%)` }">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-logo">
|
<div class="banner-logo">
|
||||||
<img v-if="siteLogo" :src="siteLogo" class="logo-img" alt="Logo" />
|
<SiteLogoMark img-class="logo-img" icon-class="logo-icon" />
|
||||||
<div v-else class="logo-icon">{{ siteName.charAt(0) }}</div>
|
|
||||||
<span class="logo-text">{{ siteName }}</span>
|
<span class="logo-text">{{ siteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="banner-title">加入我们</h1>
|
<h1 class="banner-title">加入我们</h1>
|
||||||
@ -116,6 +115,7 @@ import { useSiteStore } from '@/stores/site'
|
|||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import { authApi } from '@/api/auth'
|
import { authApi } from '@/api/auth'
|
||||||
import { configGroupApi } from '@/api/org'
|
import { configGroupApi } from '@/api/org'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@ -124,7 +124,6 @@ const themeStore = useThemeStore()
|
|||||||
|
|
||||||
// 站点配置
|
// 站点配置
|
||||||
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
const siteName = computed(() => siteStore.siteName || 'CSY Admin')
|
||||||
const siteLogo = computed(() => siteStore.siteLogo)
|
|
||||||
const copyright = computed(() => siteStore.copyright || '版权所有 © 成都火星网络科技有限公司 2025-2030')
|
const copyright = computed(() => siteStore.copyright || '版权所有 © 成都火星网络科技有限公司 2025-2030')
|
||||||
|
|
||||||
// 注册配置
|
// 注册配置
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<n-form-item label="站点Logo">
|
<n-form-item label="站点Logo">
|
||||||
<div class="logo-upload">
|
<div class="logo-upload">
|
||||||
<div class="logo-preview" v-if="configs.system.siteLogo">
|
<div class="logo-preview" v-if="configs.system.siteLogo">
|
||||||
<img :src="configs.system.siteLogo" alt="Logo" />
|
<SiteLogoMark :src="configs.system.siteLogo" />
|
||||||
<n-button size="small" quaternary type="error" class="logo-delete" @click="configs.system.siteLogo = ''">
|
<n-button size="small" quaternary type="error" class="logo-delete" @click="configs.system.siteLogo = ''">
|
||||||
<template #icon><n-icon><CloseOutline /></n-icon></template>
|
<template #icon><n-icon><CloseOutline /></n-icon></template>
|
||||||
</n-button>
|
</n-button>
|
||||||
@ -938,6 +938,7 @@ import { configGroupApi, type SysConfigGroup, type SmsLog } from '@/api/org'
|
|||||||
import { fileApi } from '@/api/system'
|
import { fileApi } from '@/api/system'
|
||||||
import { wechatApi } from '@/api/wechat'
|
import { wechatApi } from '@/api/wechat'
|
||||||
import { useSiteStore } from '@/stores/site'
|
import { useSiteStore } from '@/stores/site'
|
||||||
|
import SiteLogoMark from '@/components/SiteLogoMark.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const siteStore = useSiteStore()
|
const siteStore = useSiteStore()
|
||||||
@ -1812,6 +1813,7 @@ async function loadWechatMpMenu() {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
:deep(.logo-img),
|
||||||
img {
|
img {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
@ -1822,6 +1824,20 @@ async function loadWechatMpMenu() {
|
|||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.logo-icon) {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-delete {
|
.logo-delete {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -8px;
|
top: -8px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user