| | |
| | | VITE_DROP_CONSOLE = false |
| | | |
| | | # 是否开启 Vue DevTools / Inspector(开启后会增加本地模块转换耗时) |
| | | VITE_ENABLE_VUE_DEVTOOLS = true |
| | | VITE_ENABLE_VUE_DEVTOOLS = false |
| | |
| | | }) |
| | | } |
| | | |
| | | export function fetchSaveFlowStepInstance(params = {}) { |
| | | return request.post({ |
| | | url: '/flowStepInstance/save', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function fetchUpdateFlowStepInstance(params = {}) { |
| | | return request.post({ |
| | | url: '/flowStepInstance/update', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function fetchRemoveFlowStepInstance(id) { |
| | | return request.post({ |
| | | url: `/flowStepInstance/remove/${normalizeIds(id)}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchJumpCurrentFlowStepInstance(id) { |
| | | return request.post({ |
| | | url: `/flowStepInstance/jumpCurrent/${id}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportFlowStepInstanceReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/flowStepInstance/export`, { |
| | | method: 'POST', |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import AppConfig from '@/config' |
| | | import { fetchPublicProjectCopyrightConfig } from '@/api/system-manage' |
| | | import { |
| | | getDefaultProjectCopyright, |
| | | loadPublicProjectCopyright, |
| | | setPublicProjectCopyright |
| | | } from '@/utils/sys/public-project-config' |
| | | |
| | | const PROJECT_COPYRIGHT_UPDATED_EVENT = 'project-copyright-updated' |
| | | let cachedCopyrightText = '' |
| | | let copyrightRequest = null |
| | | |
| | | defineOptions({ name: 'ArtCopyright' }) |
| | | |
| | | const copyrightText = ref(cachedCopyrightText || getDefaultCopyright()) |
| | | |
| | | function getDefaultCopyright() { |
| | | return `版权所有 © ${AppConfig.systemInfo.name}` |
| | | } |
| | | const copyrightText = ref(getDefaultProjectCopyright()) |
| | | |
| | | function normalizeCopyright(value) { |
| | | const normalized = String(value || '').trim() |
| | | return normalized || getDefaultCopyright() |
| | | return normalized || getDefaultProjectCopyright() |
| | | } |
| | | |
| | | async function loadProjectCopyright(force = false) { |
| | | if (cachedCopyrightText && !force) { |
| | | copyrightText.value = cachedCopyrightText |
| | | return |
| | | } |
| | | |
| | | if (!copyrightRequest || force) { |
| | | copyrightRequest = fetchPublicProjectCopyrightConfig() |
| | | .then((response) => normalizeCopyright(response?.val)) |
| | | .catch(() => getDefaultCopyright()) |
| | | .then((resolvedCopyright) => { |
| | | cachedCopyrightText = resolvedCopyright |
| | | return resolvedCopyright |
| | | }) |
| | | } |
| | | |
| | | copyrightText.value = await copyrightRequest |
| | | copyrightText.value = await loadPublicProjectCopyright(force) |
| | | } |
| | | |
| | | function handleProjectCopyrightUpdated(event) { |
| | | const nextCopyright = normalizeCopyright(event?.detail?.text) |
| | | cachedCopyrightText = nextCopyright |
| | | copyrightRequest = Promise.resolve(nextCopyright) |
| | | copyrightText.value = nextCopyright |
| | | copyrightText.value = setPublicProjectCopyright(nextCopyright) |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import defaultLogo from '@imgs/common/logo.webp' |
| | | import { fetchPublicProjectLogoConfig } from '@/api/system-manage' |
| | | import { |
| | | getDefaultProjectLogo, |
| | | loadPublicProjectLogo, |
| | | setPublicProjectLogo |
| | | } from '@/utils/sys/public-project-config' |
| | | |
| | | const PROJECT_LOGO_UPDATED_EVENT = 'project-logo-updated' |
| | | let cachedLogoSrc = '' |
| | | let logoRequest = null |
| | | |
| | | defineOptions({ name: 'ArtLogo' }) |
| | | const props = defineProps({ |
| | | size: { required: false, default: 36 }, |
| | | fill: { type: Boolean, default: false } |
| | | }) |
| | | const logoSrc = ref(cachedLogoSrc || defaultLogo) |
| | | const logoSrc = ref(getDefaultProjectLogo()) |
| | | const wrapperStyle = computed(() => (props.fill ? { width: '100%', height: '100%' } : {})) |
| | | const logoStyle = computed(() => { |
| | | if (props.fill) { |
| | |
| | | |
| | | function normalizeLogoSrc(value) { |
| | | const normalized = String(value || '').trim() |
| | | return normalized || defaultLogo |
| | | return normalized || getDefaultProjectLogo() |
| | | } |
| | | |
| | | function applyDefaultLogo() { |
| | | cachedLogoSrc = defaultLogo |
| | | logoRequest = Promise.resolve(defaultLogo) |
| | | logoSrc.value = defaultLogo |
| | | logoSrc.value = setPublicProjectLogo(getDefaultProjectLogo()) |
| | | } |
| | | |
| | | async function loadProjectLogo(force = false) { |
| | | if (cachedLogoSrc && !force) { |
| | | logoSrc.value = cachedLogoSrc |
| | | return |
| | | } |
| | | |
| | | if (!logoRequest || force) { |
| | | logoRequest = fetchPublicProjectLogoConfig() |
| | | .then((response) => normalizeLogoSrc(response?.val)) |
| | | .catch(() => defaultLogo) |
| | | .then((resolvedLogo) => { |
| | | cachedLogoSrc = resolvedLogo |
| | | return resolvedLogo |
| | | }) |
| | | } |
| | | |
| | | logoSrc.value = await logoRequest |
| | | logoSrc.value = await loadPublicProjectLogo(force) |
| | | } |
| | | |
| | | function handleProjectLogoUpdated(event) { |
| | | const nextLogoSrc = normalizeLogoSrc(event?.detail?.url) |
| | | cachedLogoSrc = nextLogoSrc |
| | | logoRequest = Promise.resolve(nextLogoSrc) |
| | | logoSrc.value = nextLogoSrc |
| | | logoSrc.value = setPublicProjectLogo(nextLogoSrc) |
| | | } |
| | | |
| | | function handleLogoError() { |
| | | if (logoSrc.value === defaultLogo) { |
| | | if (logoSrc.value === getDefaultProjectLogo()) { |
| | | return |
| | | } |
| | | applyDefaultLogo() |
| | |
| | | <!-- 图标组件 --> |
| | | <template> |
| | | <span v-if="icon" v-bind="containerAttrs"> |
| | | <Icon :icon="icon" /> |
| | | <span v-if="resolvedIcon" v-bind="containerAttrs"> |
| | | <Icon :key="renderKey" :icon="resolvedIcon" /> |
| | | </span> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Icon } from '@iconify/vue/offline' |
| | | import { ensureIconRegistered } from '@/utils/ui/iconify-loader' |
| | | defineOptions({ name: 'ArtSvgIcon', inheritAttrs: false }) |
| | | defineProps({ |
| | | const props = defineProps({ |
| | | icon: { required: false } |
| | | }) |
| | | const attrs = useAttrs() |
| | | const resolvedIcon = ref('') |
| | | const renderKey = ref(0) |
| | | const containerAttrs = computed(() => ({ |
| | | ...attrs, |
| | | class: ['art-svg-icon inline-flex shrink-0', attrs.class].filter(Boolean).join(' '), |
| | | style: attrs.style |
| | | })) |
| | | |
| | | let latestTaskId = 0 |
| | | |
| | | watch( |
| | | () => props.icon, |
| | | async (icon) => { |
| | | const taskId = ++latestTaskId |
| | | |
| | | if (!icon) { |
| | | resolvedIcon.value = '' |
| | | renderKey.value += 1 |
| | | return |
| | | } |
| | | |
| | | if (typeof icon !== 'string') { |
| | | resolvedIcon.value = icon |
| | | renderKey.value += 1 |
| | | return |
| | | } |
| | | |
| | | const normalizedIcon = icon.trim() |
| | | if (!normalizedIcon) { |
| | | resolvedIcon.value = '' |
| | | renderKey.value += 1 |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await ensureIconRegistered(normalizedIcon) |
| | | } catch (error) { |
| | | console.warn(`[ArtSvgIcon] Failed to register icon "${normalizedIcon}"`, error) |
| | | } |
| | | |
| | | if (taskId !== latestTaskId) { |
| | | return |
| | | } |
| | | |
| | | resolvedIcon.value = normalizedIcon |
| | | renderKey.value += 1 |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | </script> |
| | |
| | | <template> |
| | | <div class="layout-content" :class="{ 'overflow-auto': isFullPage }" :style="containerStyle"> |
| | | <div id="app-content-header"> |
| | | <!-- 节日滚动 --> |
| | | <ArtFestivalTextScroll v-if="!isFullPage" /> |
| | | |
| | | <!-- 路由信息调试 --> |
| | | <div |
| | | v-if="isOpenRouteInfo === 'true'" |
| | |
| | | <RouterView v-else-if="isRefresh" v-slot="{ Component, route }"> |
| | | <!-- 缓存路由动画 --> |
| | | <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear> |
| | | <div v-if="route.meta.keepAlive" :key="route.path" class="art-page-view" :style="contentStyle"> |
| | | <div |
| | | v-if="route.meta.keepAlive" |
| | | :key="route.path" |
| | | class="art-page-view" |
| | | :style="contentStyle" |
| | | > |
| | | <KeepAlive :max="10" :exclude="keepAliveExclude"> |
| | | <component :is="Component" :key="route.path" /> |
| | | </KeepAlive> |
| | |
| | | |
| | | <!-- 非缓存路由动画 --> |
| | | <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear> |
| | | <div v-if="!route.meta.keepAlive" :key="route.path" class="art-page-view" :style="contentStyle"> |
| | | <div |
| | | v-if="!route.meta.keepAlive" |
| | | :key="route.path" |
| | | class="art-page-view" |
| | | :style="contentStyle" |
| | | > |
| | | <component :is="Component" :key="route.path" /> |
| | | </div> |
| | | </Transition> |
| | |
| | | line-height: 1.7; |
| | | word-break: break-word; |
| | | } |
| | | |
| | | </style> |
| | |
| | | import { mittBus } from '@/utils/sys' |
| | | import { StorageConfig } from '@/utils' |
| | | import { useTheme } from '@/hooks/core/useTheme' |
| | | import { useCeremony } from '@/hooks/core/useCeremony' |
| | | import { useSettingsState } from './useSettingsState' |
| | | import { useSettingsHandlers } from './useSettingsHandlers' |
| | | function useSettingsPanel() { |
| | | const settingStore = useSettingStore() |
| | | const { systemThemeType, systemThemeMode, menuType } = storeToRefs(settingStore) |
| | | const { openFestival, cleanup } = useCeremony() |
| | | const { setSystemTheme, setSystemAutoTheme } = useTheme() |
| | | const { initColorWeak } = useSettingsState() |
| | | const { domOperations } = useSettingsHandlers() |
| | |
| | | const boxMode = settingStore.boxBorderMode ? 'border-mode' : 'shadow-mode' |
| | | domOperations.setRootAttribute('data-box-mode', boxMode) |
| | | themeHandlers.initSystemTheme() |
| | | openFestival() |
| | | } |
| | | const cleanupSettings = () => { |
| | | stopWatch() |
| | | themeCleanup?.() |
| | | cleanup() |
| | | } |
| | | return { |
| | | initializeSettings, |
| | |
| | | { comment: '是否显示语言切换', key: 'showLanguage' }, |
| | | { comment: '是否显示进度条', key: 'showNprogress' }, |
| | | { comment: '是否显示设置引导', key: 'showSettingGuide' }, |
| | | { comment: '是否显示节日文本', key: 'showFestivalText' }, |
| | | { comment: '是否显示水印', key: 'watermarkVisible' }, |
| | | { comment: '是否自动关闭', key: 'autoClose' }, |
| | | { comment: '是否唯一展开', key: 'uniqueOpened' }, |
| | | { comment: '是否色弱模式', key: 'colorWeak' }, |
| | | { comment: '是否刷新', key: 'refresh' }, |
| | | { comment: '是否加载节日烟花', key: 'holidayFireworksLoaded' }, |
| | | { comment: '边框模式', key: 'boxBorderMode' }, |
| | | { comment: '页面过渡效果', key: 'pageTransition' }, |
| | | { comment: '标签页样式', key: 'tabStyle' }, |
| | | { comment: '自定义圆角', key: 'customRadius' }, |
| | | { comment: '容器宽度', key: 'containerWidth', enumMap: ENUM_MAPS.containerWidth }, |
| | | { comment: '节日日期', key: 'festivalDate', forceValue: '' } |
| | | { comment: '容器宽度', key: 'containerWidth', enumMap: ENUM_MAPS.containerWidth } |
| | | ] |
| | | const valueToCode = (value, enumMap) => { |
| | | if (value === null) return 'null' |
| | |
| | | settingStore.setNprogress() |
| | | ) |
| | | settingStore.setWorkTab(config.showWorkTab) |
| | | settingStore.setShowFestivalText(config.showFestivalText) |
| | | settingStore.setWatermarkVisible(config.watermarkVisible) |
| | | toggleIfDifferent(settingStore.autoClose, config.autoClose, () => settingStore.setAutoClose()) |
| | | toggleIfDifferent(settingStore.uniqueOpened, config.uniqueOpened, () => |
| | |
| | | settingStore.setTabStyle(config.tabStyle) |
| | | settingStore.setCustomRadius(config.customRadius) |
| | | settingStore.setContainerWidth(config.containerWidth) |
| | | settingStore.setFestivalDate(config.festivalDate) |
| | | settingStore.setholidayFireworksLoaded(config.holidayFireworksLoaded) |
| | | location.reload() |
| | | } catch (error) { |
| | | console.error('重置配置失败:', error) |
| | |
| | | enabled: true |
| | | }, |
| | | { |
| | | name: '礼花效果', |
| | | key: 'fireworks-effect', |
| | | component: defineAsyncComponent( |
| | | () => import('@/components/core/layouts/art-fireworks-effect/index.vue') |
| | | ), |
| | | enabled: true |
| | | }, |
| | | { |
| | | name: '水印效果', |
| | | key: 'watermark', |
| | | component: defineAsyncComponent( |
| | |
| | | showNprogress: false, |
| | | /** 是否显示设置引导 */ |
| | | showSettingGuide: true, |
| | | /** 是否显示节日文本 */ |
| | | showFestivalText: false, |
| | | /** 是否显示水印 */ |
| | | watermarkVisible: false, |
| | | /** 是否自动关闭 */ |
| | |
| | | colorWeak: false, |
| | | /** 是否刷新 */ |
| | | refresh: false, |
| | | /** 是否加载节日烟花 */ |
| | | holidayFireworksLoaded: false, |
| | | /** 边框模式 */ |
| | | boxBorderMode: true, |
| | | /** 页面过渡效果 */ |
| | |
| | | /** 自定义圆角 */ |
| | | customRadius: '0.75', |
| | | /** 容器宽度 */ |
| | | containerWidth: ContainerWidthEnum.FULL, |
| | | /** 节日日期 */ |
| | | festivalDate: '' |
| | | containerWidth: ContainerWidthEnum.FULL |
| | | } |
| | | function getSettingDefaults() { |
| | | return { ...SETTING_DEFAULT_CONFIG } |
| | |
| | | import { useTable } from './core/useTable' |
| | | import { useTableColumns } from './core/useTableColumns' |
| | | import { useTheme } from './core/useTheme' |
| | | import { useCeremony } from './core/useCeremony' |
| | | import { useFastEnter } from './core/useFastEnter' |
| | | import { useHeaderBar } from './core/useHeaderBar' |
| | | import { useChart, useChartComponent, useChartOps } from './core/useChart' |
| | |
| | | useAppMode, |
| | | useAuth, |
| | | useAutoLayoutHeight, |
| | | useCeremony, |
| | | useChart, |
| | | useChartComponent, |
| | | useChartOps, |
| | |
| | | "detail": { |
| | | "title": "Task Detail", |
| | | "taskCode": "Task No.", |
| | | "taskId": "Task ID", |
| | | "baseInfo": "Basic Information", |
| | | "pathInfo": "Execution Path", |
| | | "executionInfo": "Execution Information", |
| | | "items": "Task Items", |
| | | "itemsHint": "View related orders, materials, and execution records of the current task", |
| | | "flowStep": "Flow Steps", |
| | |
| | | "warehType": "Device Type", |
| | | "priority": "Priority", |
| | | "status": "Status", |
| | | "exceStatus": "Execution Status", |
| | | "expDesc": "Exception Description", |
| | | "expCode": "Exception Code", |
| | | "startTime": "Start Time", |
| | | "endTime": "End Time", |
| | | "robotCode": "Robot Code", |
| | | "createBy": "Created By", |
| | | "createTime": "Created At", |
| | | "updateBy": "Updated By", |
| | | "updateTime": "Updated At", |
| | | "memo": "Remark", |
| | | "orgLoc": "Source Location", |
| | |
| | | "empty": "No task items", |
| | | "orderType": "Order Type", |
| | | "wkType": "Business Type", |
| | | "platOrderCode": "Customer Order No.", |
| | | "platWorkCode": "Work Order No.", |
| | | "platItemId": "Line No.", |
| | | "anfme": "Quantity" |
| | | "projectCode": "Project Code", |
| | | "anfme": "Quantity", |
| | | "workQty": "Executed Qty", |
| | | "qty": "Completed Qty", |
| | | "spec": "Specification", |
| | | "model": "Model" |
| | | }, |
| | | "flowStepDialog": { |
| | | "title": "Flow Steps", |
| | | "create": "Add Step", |
| | | "createTitle": "Create Flow Step", |
| | | "editTitle": "Edit Flow Step", |
| | | "jumpCurrent": "Jump Current", |
| | | "currentTask": "Current Task", |
| | | "flowInstanceNo": "Flow Instance No.", |
| | | "stepCode": "Step Code", |
| | |
| | | "executeResult": "Execution Result", |
| | | "startTime": "Start Time", |
| | | "endTime": "End Time", |
| | | "timeout": "Flow steps timed out and waiting has stopped" |
| | | "timeout": "Flow steps timed out and waiting has stopped", |
| | | "form": { |
| | | "taskNo": "Task No.", |
| | | "stepOrder": "Step Order", |
| | | "stepCode": "Step Code", |
| | | "stepName": "Step Name", |
| | | "stepType": "Step Type", |
| | | "status": "Status", |
| | | "executeResult": "Execution Result", |
| | | "executeResultPlaceholder": "Enter execution result" |
| | | }, |
| | | "statusOptions": { |
| | | "0": "Queued", |
| | | "1": "Pending", |
| | | "2": "Running", |
| | | "3": "Succeeded", |
| | | "4": "Failed", |
| | | "5": "Skipped", |
| | | "6": "Canceled" |
| | | }, |
| | | "validation": { |
| | | "stepOrder": "Enter step order", |
| | | "stepCode": "Enter step code", |
| | | "stepName": "Enter step name", |
| | | "stepType": "Enter step type", |
| | | "status": "Select status" |
| | | }, |
| | | "messages": { |
| | | "createSuccess": "Flow step created successfully", |
| | | "updateSuccess": "Flow step updated successfully", |
| | | "submitFailed": "Failed to submit flow step", |
| | | "deleteConfirm": "Are you sure you want to delete flow step {code}?", |
| | | "deleteSuccess": "Flow step deleted successfully", |
| | | "deleteFailed": "Failed to delete flow step", |
| | | "jumpSuccess": "Jumped to current step successfully", |
| | | "jumpFailed": "Failed to jump to current step" |
| | | } |
| | | }, |
| | | "messages": { |
| | | "detailTimeout": "Task detail timed out and waiting has stopped", |
| | | "completeConfirm": "Are you sure you want to complete task {code}?", |
| | | "completeSuccess": "Task completed successfully", |
| | | "removeConfirm": "Are you sure you want to cancel task {code}?", |
| | |
| | | "detail": { |
| | | "title": "任务详情", |
| | | "taskCode": "任务号", |
| | | "taskId": "任务ID", |
| | | "baseInfo": "任务基础信息", |
| | | "pathInfo": "执行路径", |
| | | "executionInfo": "执行信息", |
| | | "items": "任务明细", |
| | | "itemsHint": "查看当前任务关联的业务单据、物料和执行记录", |
| | | "flowStep": "流程步骤", |
| | |
| | | "warehType": "设备类型", |
| | | "priority": "优先级", |
| | | "status": "状态", |
| | | "exceStatus": "执行状态", |
| | | "expDesc": "异常描述", |
| | | "expCode": "异常编码", |
| | | "startTime": "开始时间", |
| | | "endTime": "结束时间", |
| | | "robotCode": "机器人编码", |
| | | "createBy": "创建人", |
| | | "createTime": "创建时间", |
| | | "updateBy": "更新人", |
| | | "updateTime": "更新时间", |
| | | "memo": "备注", |
| | | "orgLoc": "源库位", |
| | |
| | | "empty": "暂无任务明细", |
| | | "orderType": "单据类型", |
| | | "wkType": "业务类型", |
| | | "platOrderCode": "客户订单号", |
| | | "platWorkCode": "工单号", |
| | | "platItemId": "行号", |
| | | "anfme": "数量" |
| | | "projectCode": "项目号", |
| | | "anfme": "数量", |
| | | "workQty": "执行数量", |
| | | "qty": "完成数量", |
| | | "spec": "规格", |
| | | "model": "型号" |
| | | }, |
| | | "flowStepDialog": { |
| | | "title": "流程步骤", |
| | | "create": "新增步骤", |
| | | "createTitle": "新增流程步骤", |
| | | "editTitle": "编辑流程步骤", |
| | | "jumpCurrent": "跳转当前", |
| | | "currentTask": "当前任务", |
| | | "flowInstanceNo": "流程实例号", |
| | | "stepCode": "步骤编码", |
| | |
| | | "executeResult": "执行结果", |
| | | "startTime": "开始时间", |
| | | "endTime": "结束时间", |
| | | "timeout": "流程步骤加载超时,已停止等待" |
| | | "timeout": "流程步骤加载超时,已停止等待", |
| | | "form": { |
| | | "taskNo": "任务号", |
| | | "stepOrder": "步骤顺序", |
| | | "stepCode": "步骤编码", |
| | | "stepName": "步骤名称", |
| | | "stepType": "步骤类型", |
| | | "status": "状态", |
| | | "executeResult": "执行结果", |
| | | "executeResultPlaceholder": "请输入执行结果" |
| | | }, |
| | | "statusOptions": { |
| | | "0": "排队中", |
| | | "1": "待执行", |
| | | "2": "执行中", |
| | | "3": "执行成功", |
| | | "4": "执行失败", |
| | | "5": "已跳过", |
| | | "6": "已取消" |
| | | }, |
| | | "validation": { |
| | | "stepOrder": "请输入步骤顺序", |
| | | "stepCode": "请输入步骤编码", |
| | | "stepName": "请输入步骤名称", |
| | | "stepType": "请输入步骤类型", |
| | | "status": "请选择状态" |
| | | }, |
| | | "messages": { |
| | | "createSuccess": "流程步骤新增成功", |
| | | "updateSuccess": "流程步骤修改成功", |
| | | "submitFailed": "流程步骤提交失败", |
| | | "deleteConfirm": "确定删除流程步骤 {code} 吗?", |
| | | "deleteSuccess": "流程步骤删除成功", |
| | | "deleteFailed": "流程步骤删除失败", |
| | | "jumpSuccess": "已跳转到当前步骤", |
| | | "jumpFailed": "跳转当前步骤失败" |
| | | } |
| | | }, |
| | | "messages": { |
| | | "detailTimeout": "任务详情加载超时,已停止等待", |
| | | "completeConfirm": "确定完成任务 {code} 吗?", |
| | | "completeSuccess": "任务完成成功", |
| | | "removeConfirm": "确定取消任务 {code} 吗?", |
| | |
| | | import { RouteValidator } from './RouteValidator.js' |
| | | import { RouteTransformer } from './RouteTransformer.js' |
| | | const DEFAULT_WARMUP_LIMIT = 12 |
| | | const DEFAULT_WARMUP_BATCH_SIZE = 6 |
| | | const DEFAULT_WARMUP_BATCH_INTERVAL = 160 |
| | | const DEFAULT_WARMUP_IDLE_TIMEOUT = 1200 |
| | | const HOME_COMPONENT_PATH = '/dashboard/console' |
| | | class RouteRegistry { |
| | | constructor(router, options = {}) { |
| | |
| | | */ |
| | | warm(menuList, options = {}) { |
| | | const limit = Number.isFinite(options.limit) ? options.limit : DEFAULT_WARMUP_LIMIT |
| | | const batchSize = Number.isFinite(options.batchSize) |
| | | ? Math.max(1, Math.floor(options.batchSize)) |
| | | : DEFAULT_WARMUP_BATCH_SIZE |
| | | const batchInterval = Number.isFinite(options.batchInterval) |
| | | ? Math.max(0, options.batchInterval) |
| | | : DEFAULT_WARMUP_BATCH_INTERVAL |
| | | const paths = collectWarmupPaths(menuList, limit) |
| | | if (paths.length === 0) { |
| | | return |
| | | } |
| | | const schedule = globalThis.requestIdleCallback |
| | | ? (task) => globalThis.requestIdleCallback(task, { timeout: 1200 }) |
| | | : (task) => setTimeout(task, 80) |
| | | schedule(() => { |
| | | void warmSequentially(paths, this.componentLoader) |
| | | scheduleWarmupTask(() => { |
| | | void warmInBatches(paths, this.componentLoader, { |
| | | batchSize, |
| | | batchInterval |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | |
| | | walk(menuList) |
| | | return paths |
| | | } |
| | | async function warmSequentially(paths, componentLoader) { |
| | | for (const componentPath of paths) { |
| | | await componentLoader.warm(componentPath) |
| | | function scheduleWarmupTask(task, delay = 0) { |
| | | const invoke = () => { |
| | | if (globalThis.requestIdleCallback) { |
| | | globalThis.requestIdleCallback(task, { timeout: DEFAULT_WARMUP_IDLE_TIMEOUT }) |
| | | return |
| | | } |
| | | setTimeout(task, 0) |
| | | } |
| | | if (delay > 0) { |
| | | setTimeout(invoke, delay) |
| | | return |
| | | } |
| | | invoke() |
| | | } |
| | | function waitForNextWarmupBatch(delay = 0) { |
| | | return new Promise((resolve) => { |
| | | scheduleWarmupTask(resolve, delay) |
| | | }) |
| | | } |
| | | async function warmInBatches(paths, componentLoader, options = {}) { |
| | | const batchSize = Number.isFinite(options.batchSize) |
| | | ? Math.max(1, Math.floor(options.batchSize)) |
| | | : DEFAULT_WARMUP_BATCH_SIZE |
| | | const batchInterval = Number.isFinite(options.batchInterval) |
| | | ? Math.max(0, options.batchInterval) |
| | | : DEFAULT_WARMUP_BATCH_INTERVAL |
| | | |
| | | for (let start = 0; start < paths.length; start += batchSize) { |
| | | const currentBatch = paths.slice(start, start + batchSize) |
| | | await Promise.allSettled( |
| | | currentBatch.map((componentPath) => componentLoader.warm(componentPath)) |
| | | ) |
| | | if (start + batchSize < paths.length) { |
| | | await waitForNextWarmupBatch(batchInterval) |
| | | } |
| | | } |
| | | } |
| | | export { RouteRegistry } |
| | |
| | | import NProgress from 'nprogress' |
| | | import { useCommon } from '@/hooks/core/useCommon' |
| | | import { loadingService } from '@/utils/ui' |
| | | import { getPendingLoading, resetPendingLoading } from './beforeEach' |
| | | import { getPendingLoading, resetPendingLoading, triggerHomeRouteWarmup } from './beforeEach' |
| | | function setupAfterEachGuard(router) { |
| | | const { scrollToTop } = useCommon() |
| | | router.afterEach(() => { |
| | | const { scrollToTop, homePath } = useCommon() |
| | | router.afterEach((to) => { |
| | | scrollToTop() |
| | | const settingStore = useSettingStore() |
| | | if (settingStore.showNprogress) { |
| | |
| | | resetPendingLoading() |
| | | }) |
| | | } |
| | | nextTick(() => { |
| | | triggerHomeRouteWarmup(to.path, homePath.value || '/') |
| | | }) |
| | | }) |
| | | } |
| | | export { setupAfterEachGuard } |
| | |
| | | import { RoutesAlias } from '../routesAlias' |
| | | import { staticRoutes } from '../routes/staticRoutes' |
| | | import { loadingService } from '@/utils/ui' |
| | | import { warmMenuIcons } from '@/utils/ui/iconify-loader' |
| | | import { useCommon } from '@/hooks/core/useCommon' |
| | | import { useWorktabStore } from '@/store/modules/worktab' |
| | | import { fetchGetUserInfo, normalizeUserInfo } from '@/api/auth' |
| | |
| | | let routeInitFailed = false |
| | | let routeInitInProgress = false |
| | | let pendingRouteLocation = null |
| | | let pendingWarmupMenuList = null |
| | | let homeWarmupTriggered = false |
| | | function getPendingLoading() { |
| | | return pendingLoading |
| | | } |
| | |
| | | routeInitFailed = false |
| | | routeInitInProgress = false |
| | | pendingRouteLocation = null |
| | | pendingWarmupMenuList = null |
| | | homeWarmupTriggered = false |
| | | } |
| | | function createRouteLocation(route) { |
| | | return { |
| | |
| | | throw new Error('获取菜单列表失败,请重新登录') |
| | | } |
| | | routeRegistry?.register(menuList) |
| | | routeRegistry?.warm(menuList) |
| | | pendingWarmupMenuList = menuList |
| | | homeWarmupTriggered = false |
| | | const menuStore = useMenuStore() |
| | | menuStore.setMenuList(menuList) |
| | | menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || []) |
| | | warmMenuIcons(menuList) |
| | | IframeRouteManager.getInstance().save() |
| | | useWorktabStore().validateWorktabs(router) |
| | | const initialTargetLocation = createRouteLocation(to) |
| | |
| | | function isUnauthorizedError(error) { |
| | | return isHttpError(error) && error.code === ApiStatus.unauthorized |
| | | } |
| | | function triggerHomeRouteWarmup(currentPath, homePath) { |
| | | if (homeWarmupTriggered || !routeRegistry || !pendingWarmupMenuList?.length) { |
| | | return |
| | | } |
| | | if (!currentPath || !homePath || currentPath !== homePath) { |
| | | return |
| | | } |
| | | homeWarmupTriggered = true |
| | | const schedule = globalThis.requestAnimationFrame |
| | | ? (task) => globalThis.requestAnimationFrame(() => globalThis.requestAnimationFrame(task)) |
| | | : (task) => setTimeout(task, 120) |
| | | schedule(() => { |
| | | routeRegistry?.warm(pendingWarmupMenuList, { |
| | | limit: Number.MAX_SAFE_INTEGER |
| | | }) |
| | | }) |
| | | } |
| | | export { |
| | | getPendingLoading, |
| | | getRouteInitFailed, |
| | | resetPendingLoading, |
| | | resetRouteInitState, |
| | | resetRouterState, |
| | | setupBeforeEachGuard |
| | | setupBeforeEachGuard, |
| | | triggerHomeRouteWarmup |
| | | } |
| | |
| | | import AppConfig from '@/config' |
| | | import { SystemThemeEnum } from '@/enums/appEnum' |
| | | import { setElementThemeColor } from '@/utils/ui' |
| | | import { useCeremony } from '@/hooks/core/useCeremony' |
| | | import { StorageConfig } from '@/utils' |
| | | import { SETTING_DEFAULT_CONFIG } from '@/config/setting' |
| | | const useSettingStore = defineStore( |
| | |
| | | const showLanguage = ref(SETTING_DEFAULT_CONFIG.showLanguage) |
| | | const showNprogress = ref(SETTING_DEFAULT_CONFIG.showNprogress) |
| | | const showSettingGuide = ref(SETTING_DEFAULT_CONFIG.showSettingGuide) |
| | | const showFestivalText = ref(SETTING_DEFAULT_CONFIG.showFestivalText) |
| | | const watermarkVisible = ref(SETTING_DEFAULT_CONFIG.watermarkVisible) |
| | | const autoClose = ref(SETTING_DEFAULT_CONFIG.autoClose) |
| | | const uniqueOpened = ref(SETTING_DEFAULT_CONFIG.uniqueOpened) |
| | | const colorWeak = ref(SETTING_DEFAULT_CONFIG.colorWeak) |
| | | const refresh = ref(SETTING_DEFAULT_CONFIG.refresh) |
| | | const holidayFireworksLoaded = ref(SETTING_DEFAULT_CONFIG.holidayFireworksLoaded) |
| | | const boxBorderMode = ref(SETTING_DEFAULT_CONFIG.boxBorderMode) |
| | | const pageTransition = ref(SETTING_DEFAULT_CONFIG.pageTransition) |
| | | const tabStyle = ref(SETTING_DEFAULT_CONFIG.tabStyle) |
| | | const customRadius = ref(SETTING_DEFAULT_CONFIG.customRadius) |
| | | const containerWidth = ref(SETTING_DEFAULT_CONFIG.containerWidth) |
| | | const festivalDate = ref('') |
| | | const getMenuTheme = computed(() => { |
| | | const list = AppConfig.themeList.filter((item) => item.theme === menuThemeType.value) |
| | | if (isDark.value) { |
| | |
| | | }) |
| | | const getCustomRadius = computed(() => { |
| | | return customRadius.value + 'rem' || SETTING_DEFAULT_CONFIG.customRadius + 'rem' |
| | | }) |
| | | const isShowFireworks = computed(() => { |
| | | return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true |
| | | }) |
| | | const switchMenuLayouts = (type) => { |
| | | menuType.value = type |
| | |
| | | customRadius.value = radius |
| | | document.documentElement.style.setProperty('--custom-radius', `${radius}rem`) |
| | | } |
| | | const setholidayFireworksLoaded = (isLoad) => { |
| | | holidayFireworksLoaded.value = isLoad |
| | | } |
| | | const setShowFestivalText = (show) => { |
| | | showFestivalText.value = show |
| | | } |
| | | const setFestivalDate = (date) => { |
| | | festivalDate.value = date |
| | | } |
| | | const setDualMenuShowText = (show) => { |
| | | dualMenuShowText.value = show |
| | | } |
| | |
| | | refresh, |
| | | watermarkVisible, |
| | | customRadius, |
| | | holidayFireworksLoaded, |
| | | showFestivalText, |
| | | festivalDate, |
| | | dualMenuShowText, |
| | | containerWidth, |
| | | getMenuTheme, |
| | | isDark, |
| | | getMenuOpenWidth, |
| | | getCustomRadius, |
| | | isShowFireworks, |
| | | switchMenuLayouts, |
| | | setMenuOpenWidth, |
| | | setGlopTheme, |
| | |
| | | reload, |
| | | setWatermarkVisible, |
| | | setCustomRadius, |
| | | setholidayFireworksLoaded, |
| | | setShowFestivalText, |
| | | setFestivalDate, |
| | | setDualMenuShowText |
| | | } |
| | | }, |
| New file |
| | |
| | | import AppConfig from '@/config' |
| | | import defaultLogo from '@imgs/common/logo.webp' |
| | | import { |
| | | fetchPublicProjectCopyrightConfig, |
| | | fetchPublicProjectLogoConfig |
| | | } from '@/api/system-manage' |
| | | |
| | | const publicProjectState = { |
| | | logoSrc: '', |
| | | logoRequest: null, |
| | | copyrightText: '', |
| | | copyrightRequest: null |
| | | } |
| | | |
| | | function getDefaultProjectLogo() { |
| | | return defaultLogo |
| | | } |
| | | |
| | | function getDefaultProjectCopyright() { |
| | | return `版权所有 © ${AppConfig.systemInfo.name}` |
| | | } |
| | | |
| | | function normalizeProjectLogo(value) { |
| | | const normalized = String(value || '').trim() |
| | | return normalized || getDefaultProjectLogo() |
| | | } |
| | | |
| | | function normalizeProjectCopyright(value) { |
| | | const normalized = String(value || '').trim() |
| | | return normalized || getDefaultProjectCopyright() |
| | | } |
| | | |
| | | function setPublicProjectLogo(value) { |
| | | const nextLogoSrc = normalizeProjectLogo(value) |
| | | publicProjectState.logoSrc = nextLogoSrc |
| | | publicProjectState.logoRequest = Promise.resolve(nextLogoSrc) |
| | | return nextLogoSrc |
| | | } |
| | | |
| | | function setPublicProjectCopyright(value) { |
| | | const nextCopyright = normalizeProjectCopyright(value) |
| | | publicProjectState.copyrightText = nextCopyright |
| | | publicProjectState.copyrightRequest = Promise.resolve(nextCopyright) |
| | | return nextCopyright |
| | | } |
| | | |
| | | function loadPublicProjectLogo(force = false) { |
| | | if (publicProjectState.logoSrc && !force) { |
| | | return Promise.resolve(publicProjectState.logoSrc) |
| | | } |
| | | |
| | | if (!publicProjectState.logoRequest || force) { |
| | | publicProjectState.logoRequest = fetchPublicProjectLogoConfig() |
| | | .then((response) => normalizeProjectLogo(response?.val)) |
| | | .catch(() => getDefaultProjectLogo()) |
| | | .then((resolvedLogo) => { |
| | | publicProjectState.logoSrc = resolvedLogo |
| | | return resolvedLogo |
| | | }) |
| | | } |
| | | |
| | | return publicProjectState.logoRequest |
| | | } |
| | | |
| | | function loadPublicProjectCopyright(force = false) { |
| | | if (publicProjectState.copyrightText && !force) { |
| | | return Promise.resolve(publicProjectState.copyrightText) |
| | | } |
| | | |
| | | if (!publicProjectState.copyrightRequest || force) { |
| | | publicProjectState.copyrightRequest = fetchPublicProjectCopyrightConfig() |
| | | .then((response) => normalizeProjectCopyright(response?.val)) |
| | | .catch(() => getDefaultProjectCopyright()) |
| | | .then((resolvedCopyright) => { |
| | | publicProjectState.copyrightText = resolvedCopyright |
| | | return resolvedCopyright |
| | | }) |
| | | } |
| | | |
| | | return publicProjectState.copyrightRequest |
| | | } |
| | | |
| | | export { |
| | | getDefaultProjectCopyright, |
| | | getDefaultProjectLogo, |
| | | loadPublicProjectCopyright, |
| | | loadPublicProjectLogo, |
| | | setPublicProjectCopyright, |
| | | setPublicProjectLogo |
| | | } |
| | |
| | | import { addCollection } from '@iconify/vue/offline' |
| | | import { LOCAL_ICON_COLLECTIONS } from '../../plugins/iconify.collections.js' |
| | | |
| | | const FULL_ICON_COLLECTION_LOADERS = Object.freeze({ |
| | | fluent: () => import('@iconify-json/fluent').then((module) => module.icons), |
| | | 'icon-park-outline': () => |
| | | import('@iconify-json/icon-park-outline').then((module) => module.icons), |
| | | iconamoon: () => import('@iconify-json/iconamoon').then((module) => module.icons), |
| | | ix: () => import('@iconify-json/ix').then((module) => module.icons), |
| | | 'line-md': () => import('@iconify-json/line-md').then((module) => module.icons), |
| | | ri: () => import('@iconify-json/ri').then((module) => module.icons), |
| | | solar: () => import('@iconify-json/solar').then((module) => module.icons), |
| | | 'svg-spinners': () => import('@iconify-json/svg-spinners').then((module) => module.icons), |
| | | 'system-uicons': () => import('@iconify-json/system-uicons').then((module) => module.icons), |
| | | vaadin: () => import('@iconify-json/vaadin').then((module) => module.icons) |
| | | }) |
| | | |
| | | const fullyRegisteredPrefixes = new Set() |
| | | const pendingPrefixLoads = new Map() |
| | | |
| | | function parseIconName(icon) { |
| | | if (typeof icon !== 'string') { |
| | | return null |
| | | } |
| | | |
| | | const normalizedIcon = icon.trim() |
| | | if (!normalizedIcon) { |
| | | return null |
| | | } |
| | | |
| | | const separatorIndex = normalizedIcon.indexOf(':') |
| | | if (separatorIndex <= 0 || separatorIndex >= normalizedIcon.length - 1) { |
| | | return null |
| | | } |
| | | |
| | | return { |
| | | prefix: normalizedIcon.slice(0, separatorIndex), |
| | | name: normalizedIcon.slice(separatorIndex + 1) |
| | | } |
| | | } |
| | | |
| | | function hasBundledIcon(icon) { |
| | | const parsedIcon = parseIconName(icon) |
| | | if (!parsedIcon) { |
| | | return false |
| | | } |
| | | |
| | | const collection = LOCAL_ICON_COLLECTIONS[parsedIcon.prefix] |
| | | if (!collection) { |
| | | return false |
| | | } |
| | | |
| | | return Boolean(collection.icons?.[parsedIcon.name] || collection.aliases?.[parsedIcon.name]) |
| | | } |
| | | |
| | | function loadFullIconCollection(prefix) { |
| | | if (fullyRegisteredPrefixes.has(prefix)) { |
| | | return Promise.resolve(true) |
| | | } |
| | | |
| | | const currentTask = pendingPrefixLoads.get(prefix) |
| | | if (currentTask) { |
| | | return currentTask |
| | | } |
| | | |
| | | const loader = FULL_ICON_COLLECTION_LOADERS[prefix] |
| | | if (!loader) { |
| | | return Promise.resolve(false) |
| | | } |
| | | |
| | | const loadTask = loader() |
| | | .then((collection) => { |
| | | addCollection(collection) |
| | | fullyRegisteredPrefixes.add(prefix) |
| | | pendingPrefixLoads.delete(prefix) |
| | | return true |
| | | }) |
| | | .catch((error) => { |
| | | pendingPrefixLoads.delete(prefix) |
| | | throw error |
| | | }) |
| | | |
| | | pendingPrefixLoads.set(prefix, loadTask) |
| | | return loadTask |
| | | } |
| | | |
| | | function collectRuntimeIcons(source, iconNames = new Set()) { |
| | | if (!Array.isArray(source)) { |
| | | return iconNames |
| | | } |
| | | |
| | | source.forEach((item) => { |
| | | if (!item || typeof item !== 'object') { |
| | | return |
| | | } |
| | | |
| | | const icon = item.meta?.icon || item.icon |
| | | if (typeof icon === 'string' && icon.includes(':') && !hasBundledIcon(icon)) { |
| | | iconNames.add(icon) |
| | | } |
| | | |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | collectRuntimeIcons(item.children, iconNames) |
| | | } |
| | | }) |
| | | |
| | | return iconNames |
| | | } |
| | | |
| | | function scheduleIdleTask(task, delay = 0) { |
| | | const invoke = () => { |
| | | if (globalThis.requestIdleCallback) { |
| | | globalThis.requestIdleCallback(task, { timeout: 1000 }) |
| | | return |
| | | } |
| | | setTimeout(task, 0) |
| | | } |
| | | |
| | | if (delay > 0) { |
| | | setTimeout(invoke, delay) |
| | | return |
| | | } |
| | | |
| | | invoke() |
| | | } |
| | | |
| | | async function ensureIconRegistered(icon) { |
| | | const parsedIcon = parseIconName(icon) |
| | | if (!parsedIcon) { |
| | | return false |
| | | } |
| | | |
| | | if (hasBundledIcon(icon) || fullyRegisteredPrefixes.has(parsedIcon.prefix)) { |
| | | return true |
| | | } |
| | | |
| | | return loadFullIconCollection(parsedIcon.prefix) |
| | | } |
| | | |
| | | function warmRuntimeIcons(iconNames, delay = 120) { |
| | | const icons = [...new Set(Array.isArray(iconNames) ? iconNames : [])].filter(Boolean) |
| | | if (icons.length === 0) { |
| | | return |
| | | } |
| | | |
| | | scheduleIdleTask(() => { |
| | | icons.forEach((icon) => { |
| | | void ensureIconRegistered(icon) |
| | | }) |
| | | }, delay) |
| | | } |
| | | |
| | | function warmMenuIcons(menuList, delay = 120) { |
| | | warmRuntimeIcons([...collectRuntimeIcons(menuList)], delay) |
| | | } |
| | | |
| | | export { |
| | | collectRuntimeIcons, |
| | | ensureIconRegistered, |
| | | hasBundledIcon, |
| | | warmMenuIcons, |
| | | warmRuntimeIcons |
| | | } |
| | |
| | | /> |
| | | </ElCard> |
| | | |
| | | <TaskFlowStepDialog |
| | | v-model:visible="flowStepDialogVisible" |
| | | :task-row="activeTaskRow" |
| | | /> |
| | | <TaskFlowStepDialog v-model:visible="flowStepDialogVisible" :task-row="activeTaskRow" /> |
| | | |
| | | <TaskDetailDrawer |
| | | v-model:visible="detailDrawerVisible" |
| | |
| | | fetchPickTask, |
| | | fetchRemoveTask, |
| | | fetchTaskAutoRunFlag, |
| | | fetchTaskDetail, |
| | | fetchTaskItemPage, |
| | | fetchTaskPage, |
| | | fetchTopTask, |
| | |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'platOrderCode', |
| | | label: t('pages.task.expand.platOrderCode'), |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'platWorkCode', |
| | | label: t('pages.orders.transfer.detail.relatedCode'), |
| | | minWidth: 150, |
| | |
| | | prop: 'platItemId', |
| | | label: t('pages.orders.delivery.table.platItemId'), |
| | | minWidth: 100, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'projectCode', |
| | | label: t('pages.task.expand.projectCode'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'workQty', |
| | | label: t('pages.task.expand.workQty'), |
| | | width: 100, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'qty', |
| | | label: t('pages.task.expand.qty'), |
| | | width: 100, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'spec', |
| | | label: t('pages.task.expand.spec'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'model', |
| | | label: t('pages.task.expand.model'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createByText', |
| | | label: t('table.createBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: t('table.createTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'updateByText', |
| | | label: t('pages.orders.delivery.detail.updateBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'statusText', |
| | | label: t('table.status'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'memo', |
| | | label: t('table.remark'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | |
| | | } |
| | | |
| | | if (action.key === 'complete') { |
| | | await confirmTaskAction(t('pages.task.messages.completeConfirm', { code: row.taskCode || '' })) |
| | | await confirmTaskAction( |
| | | t('pages.task.messages.completeConfirm', { code: row.taskCode || '' }) |
| | | ) |
| | | await fetchCompleteTask(row.id) |
| | | ElMessage.success(t('pages.task.messages.completeSuccess')) |
| | | } else if (action.key === 'remove') { |
| | | await confirmTaskAction(t('pages.task.messages.removeConfirm', { code: row.taskCode || '' })) |
| | | await confirmTaskAction( |
| | | t('pages.task.messages.removeConfirm', { code: row.taskCode || '' }) |
| | | ) |
| | | await fetchRemoveTask(row.id) |
| | | ElMessage.success(t('pages.task.messages.removeSuccess')) |
| | | } else if (action.key === 'check') { |
| | |
| | | async function loadAutoRunConfig() { |
| | | autoRunLoading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage(fetchTaskAutoRunFlag(), { val: false }, { |
| | | timeoutMessage: t('pages.task.messages.autoRunTimeout') |
| | | }) |
| | | const response = await guardRequestWithMessage( |
| | | fetchTaskAutoRunFlag(), |
| | | { val: false }, |
| | | { |
| | | timeoutMessage: t('pages.task.messages.autoRunTimeout') |
| | | } |
| | | ) |
| | | const rawValue = response?.val |
| | | autoRunEnabled.value = |
| | | rawValue === true || rawValue === 'true' || rawValue === 1 || rawValue === '1' |
| | |
| | | try { |
| | | await fetchUpdateTaskAutoRunFlag(enabled) |
| | | autoRunEnabled.value = enabled |
| | | ElMessage.success(enabled ? t('pages.task.messages.autoRunOnSuccess') : t('pages.task.messages.autoRunOffSuccess')) |
| | | ElMessage.success( |
| | | enabled |
| | | ? t('pages.task.messages.autoRunOnSuccess') |
| | | : t('pages.task.messages.autoRunOffSuccess') |
| | | ) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.task.messages.autoRunUpdateFailed')) |
| | | } finally { |
| | |
| | | |
| | | detailLoading.value = true |
| | | try { |
| | | const taskItemResponse = await guardRequestWithMessage( |
| | | fetchTaskItemPage({ |
| | | taskId: activeTaskRow.value.id, |
| | | current: detailPagination.current, |
| | | pageSize: detailPagination.size |
| | | const [taskDetailResult, taskItemResult] = await Promise.allSettled([ |
| | | guardRequestWithMessage(fetchTaskDetail(activeTaskRow.value.id), activeTaskRow.value, { |
| | | timeoutMessage: t('pages.task.messages.detailTimeout') |
| | | }), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: detailPagination.current, |
| | | size: detailPagination.size |
| | | }, |
| | | { timeoutMessage: t('pages.task.messages.itemsTimeout') } |
| | | ) |
| | | guardRequestWithMessage( |
| | | fetchTaskItemPage({ |
| | | taskId: activeTaskRow.value.id, |
| | | current: detailPagination.current, |
| | | pageSize: detailPagination.size |
| | | }), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: detailPagination.current, |
| | | size: detailPagination.size |
| | | }, |
| | | { timeoutMessage: t('pages.task.messages.itemsTimeout') } |
| | | ) |
| | | ]) |
| | | |
| | | const taskDetailResponse = |
| | | taskDetailResult.status === 'fulfilled' ? taskDetailResult.value : activeTaskRow.value |
| | | const taskItemResponse = |
| | | taskItemResult.status === 'fulfilled' |
| | | ? taskItemResult.value |
| | | : { |
| | | records: [], |
| | | total: 0, |
| | | current: detailPagination.current, |
| | | size: detailPagination.size |
| | | } |
| | | |
| | | activeTaskRow.value = { |
| | | ...activeTaskRow.value, |
| | | ...taskDetailResponse |
| | | } |
| | | detailData.value = normalizeTaskRow(activeTaskRow.value) |
| | | detailTableData.value = Array.isArray(taskItemResponse?.records) |
| | | detailTableData.value = Array.isArray(taskItemResponse.records) |
| | | ? taskItemResponse.records.map((record) => normalizeTaskItemRow(record)) |
| | | : [] |
| | | updatePaginationState(detailPagination, taskItemResponse, detailPagination.current, detailPagination.size) |
| | | updatePaginationState( |
| | | detailPagination, |
| | | taskItemResponse, |
| | | detailPagination.current, |
| | | detailPagination.size |
| | | ) |
| | | |
| | | if (taskDetailResult.status === 'rejected' && taskItemResult.status === 'rejected') { |
| | | throw taskDetailResult.reason || taskItemResult.reason |
| | | } |
| | | } catch (error) { |
| | | detailTableData.value = [] |
| | | detailData.value = normalizeTaskRow(activeTaskRow.value) |
| | | ElMessage.error(error?.message || t('pages.task.messages.detailLoadFailed')) |
| | | } finally { |
| | | detailLoading.value = false |
| | |
| | | size="85%" |
| | | @update:model-value="handleVisibleChange" |
| | | > |
| | | <div class="flex h-full flex-col gap-4"> |
| | | <div class="grid gap-4 xl:grid-cols-[1.45fr_1fr]"> |
| | | <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between"> |
| | | <span class="font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.baseInfo') }}</span> |
| | | <ElTag size="small" effect="plain" type="primary"> |
| | | {{ detail.taskCode || '--' }} |
| | | </ElTag> |
| | | <ElScrollbar class="h-full"> |
| | | <div class="flex min-h-full min-w-0 flex-col gap-4 pr-2"> |
| | | <div class="grid shrink-0 gap-4 xl:grid-cols-[1.45fr_1fr]"> |
| | | <ElCard |
| | | shadow="never" |
| | | class="task-detail-card border border-[var(--el-border-color-lighter)]" |
| | | > |
| | | <template #header> |
| | | <div class="flex items-center justify-between"> |
| | | <span class="font-medium text-[var(--art-text-gray-900)]">{{ |
| | | t('pages.task.detail.baseInfo') |
| | | }}</span> |
| | | <ElTag size="small" effect="plain" type="primary"> |
| | | {{ detail.taskCode || '--' }} |
| | | </ElTag> |
| | | </div> |
| | | </template> |
| | | |
| | | <ElDescriptions :column="2" border size="small" class="compact-descriptions"> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.taskId')">{{ |
| | | detail.taskId ?? '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.taskStatus')">{{ |
| | | detail.taskStatusLabel || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.taskType')">{{ |
| | | detail.taskTypeLabel || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.warehType')">{{ |
| | | detail.warehTypeLabel || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.priority')">{{ |
| | | detail.sort ?? '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.status')">{{ |
| | | detail.statusText || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.robotCode')">{{ |
| | | detail.robotCode || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.createBy')">{{ |
| | | detail.createByText || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.createTime')">{{ |
| | | detail.createTimeText || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.updateBy')">{{ |
| | | detail.updateByText || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.updateTime')">{{ |
| | | detail.updateTimeText || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.memo')" :span="2">{{ |
| | | detail.memo || '--' |
| | | }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | </ElCard> |
| | | |
| | | <ElCard |
| | | shadow="never" |
| | | class="task-detail-card border border-[var(--el-border-color-lighter)]" |
| | | > |
| | | <template #header> |
| | | <div class="flex items-center justify-between"> |
| | | <span class="font-medium text-[var(--art-text-gray-900)]">{{ |
| | | t('pages.task.detail.pathInfo') |
| | | }}</span> |
| | | <ElButton text type="primary" @click="$emit('flow-step')">{{ |
| | | t('pages.task.detail.flowStep') |
| | | }}</ElButton> |
| | | </div> |
| | | </template> |
| | | |
| | | <ElDescriptions :column="1" border size="small" class="compact-descriptions"> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.orgLoc')">{{ |
| | | detail.orgLoc || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.orgSite')">{{ |
| | | detail.orgSiteLabel || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.targLoc')">{{ |
| | | detail.targLoc || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.targSite')">{{ |
| | | detail.targSiteLabel || '--' |
| | | }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.barcode')">{{ |
| | | detail.barcode || '--' |
| | | }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | </ElCard> |
| | | </div> |
| | | |
| | | <div class="shrink-0 flex items-center justify-between"> |
| | | <div> |
| | | <div class="text-sm font-medium text-[var(--art-text-gray-900)]">{{ |
| | | t('pages.task.detail.items') |
| | | }}</div> |
| | | <div class="mt-1 text-xs text-[var(--art-text-gray-500)]"> |
| | | {{ t('pages.task.detail.itemsHint') }} |
| | | </div> |
| | | </template> |
| | | |
| | | <ElDescriptions :column="2" border> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.taskStatus')">{{ detail.taskStatusLabel || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.taskType')">{{ detail.taskTypeLabel || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.warehType')">{{ detail.warehTypeLabel || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.priority')">{{ detail.sort ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.status')">{{ detail.statusText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.robotCode')">{{ detail.robotCode || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.memo')" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | </ElCard> |
| | | |
| | | <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between"> |
| | | <span class="font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.pathInfo') }}</span> |
| | | <ElButton text type="primary" @click="$emit('flow-step')">{{ t('pages.task.detail.flowStep') }}</ElButton> |
| | | </div> |
| | | </template> |
| | | |
| | | <ElDescriptions :column="1" border> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.orgLoc')">{{ detail.orgLoc || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.orgSite')">{{ detail.orgSiteLabel || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.targLoc')">{{ detail.targLoc || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.targSite')">{{ detail.targSiteLabel || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem :label="t('pages.task.detail.barcode')">{{ detail.barcode || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | </ElCard> |
| | | </div> |
| | | |
| | | <div class="flex items-center justify-between"> |
| | | <div> |
| | | <div class="text-sm font-medium text-[var(--art-text-gray-900)]">{{ t('pages.task.detail.items') }}</div> |
| | | <div class="mt-1 text-xs text-[var(--art-text-gray-500)]"> |
| | | {{ t('pages.task.detail.itemsHint') }} |
| | | </div> |
| | | <div class="flex items-center gap-2"> |
| | | <ElButton :loading="loading" @click="$emit('refresh')">{{ |
| | | t('common.actions.refresh') |
| | | }}</ElButton> |
| | | </div> |
| | | </div> |
| | | <div class="flex items-center gap-2"> |
| | | <ElButton :loading="loading" @click="$emit('refresh')">{{ t('common.actions.refresh') }}</ElButton> |
| | | |
| | | <div class="min-w-0"> |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="$emit('size-change', $event)" |
| | | @pagination:current-change="$emit('current-change', $event)" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="$emit('size-change', $event)" |
| | | @pagination:current-change="$emit('current-change', $event)" |
| | | /> |
| | | </div> |
| | | </ElScrollbar> |
| | | </ElDrawer> |
| | | </template> |
| | | |
| | |
| | | pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change', 'flow-step']) |
| | | const emit = defineEmits([ |
| | | 'update:visible', |
| | | 'refresh', |
| | | 'size-change', |
| | | 'current-change', |
| | | 'flow-step' |
| | | ]) |
| | | |
| | | function handleVisibleChange(visible) { |
| | | emit('update:visible', visible) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | :deep(.task-detail-card .el-card__header) { |
| | | padding: 12px 16px; |
| | | } |
| | | |
| | | :deep(.task-detail-card .el-card__body) { |
| | | padding: 12px 16px 16px; |
| | | } |
| | | |
| | | :deep(.compact-descriptions .el-descriptions__label.el-descriptions__cell), |
| | | :deep(.compact-descriptions .el-descriptions__content.el-descriptions__cell) { |
| | | padding-top: 10px; |
| | | padding-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="rounded-xl bg-[var(--el-fill-color-blank)] px-4 py-4"> |
| | | <div class="mb-3 flex items-center justify-between"> |
| | | <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.task.expand.title') }}</div> |
| | | <ElButton text size="small" :loading="loading" @click="loadData">{{ t('common.actions.refresh') }}</ElButton> |
| | | <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ |
| | | t('pages.task.expand.title') |
| | | }}</div> |
| | | <ElButton text size="small" :loading="loading" @click="loadData">{{ |
| | | t('common.actions.refresh') |
| | | }}</ElButton> |
| | | </div> |
| | | |
| | | <ArtTable |
| | |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'platOrderCode', |
| | | label: t('pages.task.expand.platOrderCode'), |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'platWorkCode', |
| | | label: t('pages.task.expand.platWorkCode'), |
| | | minWidth: 150, |
| | |
| | | prop: 'platItemId', |
| | | label: t('pages.task.expand.platItemId'), |
| | | minWidth: 100, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'projectCode', |
| | | label: t('pages.task.expand.projectCode'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'workQty', |
| | | label: t('pages.task.expand.workQty'), |
| | | width: 100, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'qty', |
| | | label: t('pages.task.expand.qty'), |
| | | width: 100, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'spec', |
| | | label: t('pages.task.expand.spec'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'model', |
| | | label: t('pages.task.expand.model'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createByText', |
| | | label: t('table.createBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: t('table.createTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'updateByText', |
| | | label: t('table.updateBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'statusText', |
| | | label: t('table.status'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'memo', |
| | | label: t('table.remark'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: t('table.updateTime'), |
| | | minWidth: 180, |
| | |
| | | @update:model-value="emit('update:visible', $event)" |
| | | > |
| | | <div class="flex flex-col gap-4"> |
| | | <div class="rounded-xl border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)] px-4 py-3"> |
| | | <div class="text-sm text-[var(--art-gray-500)]">{{ t('pages.task.flowStepDialog.currentTask') }}</div> |
| | | <div class="mt-1 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-[var(--art-gray-900)]"> |
| | | <div |
| | | class="rounded-xl border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)] px-4 py-3" |
| | | > |
| | | <div class="text-sm text-[var(--art-gray-500)]">{{ |
| | | t('pages.task.flowStepDialog.currentTask') |
| | | }}</div> |
| | | <div |
| | | class="mt-1 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-[var(--art-gray-900)]" |
| | | > |
| | | <span>{{ t('pages.task.detail.taskCode') }}:{{ taskRow?.taskCode || '--' }}</span> |
| | | <span>{{ t('pages.task.detail.taskStatus') }}:{{ taskRow?.taskStatusLabel || '--' }}</span> |
| | | <span |
| | | >{{ t('pages.task.detail.taskStatus') }}:{{ taskRow?.taskStatusLabel || '--' }}</span |
| | | > |
| | | <span>{{ t('pages.task.detail.taskType') }}:{{ taskRow?.taskTypeLabel || '--' }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex items-center justify-end"> |
| | | <ElButton v-auth="'add'" type="primary" plain @click="handleOpenCreate"> |
| | | {{ t('pages.task.flowStepDialog.create') }} |
| | | </ElButton> |
| | | </div> |
| | | |
| | | <ArtTable |
| | |
| | | @pagination:current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | |
| | | <ElDialog |
| | | :model-value="editorVisible" |
| | | :title="editorTitle" |
| | | width="560px" |
| | | append-to-body |
| | | destroy-on-close |
| | | @update:model-value="handleEditorVisibleChange" |
| | | > |
| | | <ElForm |
| | | ref="formRef" |
| | | :model="formData" |
| | | :rules="formRules" |
| | | label-width="96px" |
| | | label-position="right" |
| | | > |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.taskNo')" prop="taskNo"> |
| | | <ElInput v-model="formData.taskNo" disabled /> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.stepOrder')" prop="stepOrder"> |
| | | <ElInputNumber |
| | | v-model="formData.stepOrder" |
| | | class="w-full" |
| | | :min="0" |
| | | controls-position="right" |
| | | /> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.stepCode')" prop="stepCode"> |
| | | <ElInput v-model.trim="formData.stepCode" /> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.stepName')" prop="stepName"> |
| | | <ElInput v-model.trim="formData.stepName" /> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.stepType')" prop="stepType"> |
| | | <ElInput v-model.trim="formData.stepType" /> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.status')" prop="status"> |
| | | <ElSelect v-model="formData.status" class="w-full"> |
| | | <ElOption |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </ElSelect> |
| | | </ElFormItem> |
| | | <ElFormItem :label="t('pages.task.flowStepDialog.form.executeResult')" prop="executeResult"> |
| | | <ElInput |
| | | v-model.trim="formData.executeResult" |
| | | type="textarea" |
| | | :rows="3" |
| | | :placeholder="t('pages.task.flowStepDialog.form.executeResultPlaceholder')" |
| | | /> |
| | | </ElFormItem> |
| | | </ElForm> |
| | | |
| | | <template #footer> |
| | | <div class="flex justify-end gap-3"> |
| | | <ElButton @click="handleEditorVisibleChange(false)">{{ t('common.cancel') }}</ElButton> |
| | | <ElButton type="primary" :loading="submitLoading" @click="handleSubmit"> |
| | | {{ t('common.actions.save') }} |
| | | </ElButton> |
| | | </div> |
| | | </template> |
| | | </ElDialog> |
| | | </ElDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, reactive, ref, watch } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { computed, h, reactive, ref, watch } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { fetchFlowStepInstancePage } from '@/api/flow-step-instance' |
| | | import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue' |
| | | import { |
| | | fetchFlowStepInstancePage, |
| | | fetchJumpCurrentFlowStepInstance, |
| | | fetchRemoveFlowStepInstance, |
| | | fetchSaveFlowStepInstance, |
| | | fetchUpdateFlowStepInstance |
| | | } from '@/api/flow-step-instance' |
| | | import { normalizeFlowStepInstanceRow } from '@/views/system/flow-step-instance/flowStepInstancePage.helpers' |
| | | |
| | | const props = defineProps({ |
| | |
| | | const emit = defineEmits(['update:visible']) |
| | | const { t } = useI18n() |
| | | |
| | | const formRef = ref() |
| | | const loading = ref(false) |
| | | const rows = ref([]) |
| | | const editorVisible = ref(false) |
| | | const submitLoading = ref(false) |
| | | const editingId = ref(null) |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0 |
| | | }) |
| | | const formData = reactive(createFormState()) |
| | | |
| | | const statusOptions = computed(() => [ |
| | | { value: 0, label: t('pages.task.flowStepDialog.statusOptions.0') }, |
| | | { value: 1, label: t('pages.task.flowStepDialog.statusOptions.1') }, |
| | | { value: 2, label: t('pages.task.flowStepDialog.statusOptions.2') }, |
| | | { value: 3, label: t('pages.task.flowStepDialog.statusOptions.3') }, |
| | | { value: 4, label: t('pages.task.flowStepDialog.statusOptions.4') }, |
| | | { value: 5, label: t('pages.task.flowStepDialog.statusOptions.5') }, |
| | | { value: 6, label: t('pages.task.flowStepDialog.statusOptions.6') } |
| | | ]) |
| | | |
| | | const formRules = computed(() => ({ |
| | | stepOrder: [ |
| | | { |
| | | required: true, |
| | | message: t('pages.task.flowStepDialog.validation.stepOrder'), |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | stepCode: [ |
| | | { |
| | | required: true, |
| | | message: t('pages.task.flowStepDialog.validation.stepCode'), |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | stepName: [ |
| | | { |
| | | required: true, |
| | | message: t('pages.task.flowStepDialog.validation.stepName'), |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | stepType: [ |
| | | { |
| | | required: true, |
| | | message: t('pages.task.flowStepDialog.validation.stepType'), |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | status: [ |
| | | { |
| | | required: true, |
| | | message: t('pages.task.flowStepDialog.validation.status'), |
| | | trigger: 'change' |
| | | } |
| | | ] |
| | | })) |
| | | |
| | | const editorTitle = computed(() => |
| | | editingId.value |
| | | ? t('pages.task.flowStepDialog.editTitle') |
| | | : t('pages.task.flowStepDialog.createTitle') |
| | | ) |
| | | |
| | | const columns = computed(() => [ |
| | | { |
| | |
| | | label: t('pages.task.flowStepDialog.endTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'operation', |
| | | label: t('table.operation'), |
| | | width: 120, |
| | | align: 'center', |
| | | fixed: 'right', |
| | | formatter: (row) => |
| | | h(ArtButtonMore, { |
| | | list: getActionList(row), |
| | | onClick: (item) => handleActionClick(item, row) |
| | | }) |
| | | } |
| | | ]) |
| | | |
| | | function createFormState() { |
| | | return { |
| | | id: '', |
| | | taskNo: '', |
| | | stepOrder: 0, |
| | | stepCode: '', |
| | | stepName: '', |
| | | stepType: '', |
| | | status: 0, |
| | | executeResult: '' |
| | | } |
| | | } |
| | | |
| | | function resetFormState(seed = {}) { |
| | | Object.assign(formData, createFormState(), seed) |
| | | } |
| | | |
| | | function getActionList() { |
| | | return [ |
| | | { |
| | | key: 'edit', |
| | | label: t('common.actions.edit'), |
| | | icon: 'ri:pencil-line', |
| | | auth: 'update' |
| | | }, |
| | | { |
| | | key: 'jumpCurrent', |
| | | label: t('pages.task.flowStepDialog.jumpCurrent'), |
| | | icon: 'ri:send-plane-line', |
| | | auth: 'update' |
| | | }, |
| | | { |
| | | key: 'delete', |
| | | label: t('common.actions.delete'), |
| | | icon: 'ri:delete-bin-5-line', |
| | | color: '#f56c6c', |
| | | auth: 'delete' |
| | | } |
| | | ] |
| | | } |
| | | |
| | | function updatePaginationState(response) { |
| | | pagination.total = Number(response?.total || 0) |
| | |
| | | void loadRows() |
| | | } |
| | | |
| | | function handleOpenCreate() { |
| | | editingId.value = null |
| | | resetFormState({ |
| | | taskNo: props.taskRow?.taskCode || '', |
| | | status: 0 |
| | | }) |
| | | editorVisible.value = true |
| | | } |
| | | |
| | | function handleOpenEdit(row) { |
| | | editingId.value = row.id |
| | | resetFormState({ |
| | | ...row, |
| | | id: row.id, |
| | | taskNo: row.taskNo || props.taskRow?.taskCode || '', |
| | | stepOrder: Number(row.stepOrder ?? 0), |
| | | status: Number(row.status ?? 0) |
| | | }) |
| | | editorVisible.value = true |
| | | } |
| | | |
| | | function handleEditorVisibleChange(visible) { |
| | | editorVisible.value = visible |
| | | if (!visible) { |
| | | editingId.value = null |
| | | resetFormState({ |
| | | taskNo: props.taskRow?.taskCode || '', |
| | | status: 0 |
| | | }) |
| | | formRef.value?.clearValidate() |
| | | } |
| | | } |
| | | |
| | | async function handleSubmit() { |
| | | if (!formRef.value) { |
| | | return |
| | | } |
| | | |
| | | const valid = await formRef.value.validate().catch(() => false) |
| | | if (!valid) { |
| | | return |
| | | } |
| | | |
| | | submitLoading.value = true |
| | | try { |
| | | const payload = { |
| | | ...(editingId.value ? { id: editingId.value } : {}), |
| | | taskNo: formData.taskNo, |
| | | stepOrder: Number(formData.stepOrder ?? 0), |
| | | stepCode: formData.stepCode, |
| | | stepName: formData.stepName, |
| | | stepType: formData.stepType, |
| | | status: Number(formData.status ?? 0), |
| | | executeResult: formData.executeResult |
| | | } |
| | | |
| | | if (editingId.value) { |
| | | await fetchUpdateFlowStepInstance(payload) |
| | | ElMessage.success(t('pages.task.flowStepDialog.messages.updateSuccess')) |
| | | } else { |
| | | await fetchSaveFlowStepInstance(payload) |
| | | ElMessage.success(t('pages.task.flowStepDialog.messages.createSuccess')) |
| | | } |
| | | |
| | | handleEditorVisibleChange(false) |
| | | await loadRows() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.task.flowStepDialog.messages.submitFailed')) |
| | | } finally { |
| | | submitLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function handleDelete(row) { |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | t('pages.task.flowStepDialog.messages.deleteConfirm', { |
| | | code: row.stepCode || row.id || '' |
| | | }), |
| | | t('crud.confirm.deleteTitle'), |
| | | { |
| | | type: 'warning', |
| | | confirmButtonText: t('common.confirm'), |
| | | cancelButtonText: t('common.cancel') |
| | | } |
| | | ) |
| | | await fetchRemoveFlowStepInstance(row.id) |
| | | ElMessage.success(t('pages.task.flowStepDialog.messages.deleteSuccess')) |
| | | await loadRows() |
| | | } catch (error) { |
| | | if (error === 'cancel' || error === 'close') { |
| | | return |
| | | } |
| | | ElMessage.error(error?.message || t('pages.task.flowStepDialog.messages.deleteFailed')) |
| | | } |
| | | } |
| | | |
| | | async function handleJumpCurrent(row) { |
| | | try { |
| | | await fetchJumpCurrentFlowStepInstance(row.id) |
| | | ElMessage.success(t('pages.task.flowStepDialog.messages.jumpSuccess')) |
| | | await loadRows() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.task.flowStepDialog.messages.jumpFailed')) |
| | | } |
| | | } |
| | | |
| | | function handleActionClick(action, row) { |
| | | if (action.key === 'edit') { |
| | | handleOpenEdit(row) |
| | | return |
| | | } |
| | | if (action.key === 'jumpCurrent') { |
| | | void handleJumpCurrent(row) |
| | | return |
| | | } |
| | | if (action.key === 'delete') { |
| | | void handleDelete(row) |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => [props.visible, props.taskRow?.taskCode], |
| | | ([visible]) => { |
| | | if (!visible) { |
| | | rows.value = [] |
| | | pagination.current = 1 |
| | | handleEditorVisibleChange(false) |
| | | return |
| | | } |
| | | pagination.current = 1 |
| | | resetFormState({ |
| | | taskNo: props.taskRow?.taskCode || '', |
| | | status: 0 |
| | | }) |
| | | void loadRows() |
| | | } |
| | | ) |
| | |
| | | export function normalizeTaskRow(record = {}) { |
| | | return { |
| | | ...record, |
| | | taskId: record.id ?? '--', |
| | | taskCode: record.taskCode || '-', |
| | | taskStatusLabel: record['taskStatus$'] || '-', |
| | | taskTypeLabel: record['taskType$'] || '-', |
| | |
| | | barcode: record.barcode || '-', |
| | | robotCode: record.robotCode || '-', |
| | | sort: normalizeNumber(record.sort), |
| | | exceStatusText: record['exceStatus$'] || record.exceStatus || '-', |
| | | expDesc: record.expDesc || '-', |
| | | expCode: record.expCode || '-', |
| | | startTimeText: record['startTime$'] || record.startTime || '-', |
| | | endTimeText: record['endTime$'] || record.endTime || '-', |
| | | createByText: record['createBy$'] || record.createByText || record.createBy || '-', |
| | | statusText: record['status$'] || '-', |
| | | memo: record.memo || '-', |
| | | updateTimeText: record['updateTime$'] || record.updateTime || '-', |
| | | createTimeText: record['createTime$'] || record.createTime || '-', |
| | | updateByText: record['updateBy$'] || record.updateByText || record.updateBy || '-', |
| | | canComplete: record.canComplete === true, |
| | | canCancel: record.canCancel === true |
| | | } |
| | |
| | | ...record, |
| | | orderTypeLabel: record['orderType$'] || '-', |
| | | wkTypeLabel: record['wkType$'] || '-', |
| | | platOrderCode: record.platOrderCode || '-', |
| | | platWorkCode: record.platWorkCode || '-', |
| | | platItemId: record.platItemId || '-', |
| | | projectCode: record.projectCode || '-', |
| | | matnrCode: record.matnrCode || '-', |
| | | maktx: record.maktx || '-', |
| | | batch: record.batch || '-', |
| | | unit: record.unit || '-', |
| | | anfme: normalizeNumber(record.anfme), |
| | | updateByText: record['updateBy$'] || '-', |
| | | updateTimeText: record['updateTime$'] || record.updateTime || '-' |
| | | workQty: normalizeNumber(record.workQty), |
| | | qty: normalizeNumber(record.qty), |
| | | spec: record.spec || '-', |
| | | model: record.model || '-', |
| | | createByText: record['createBy$'] || record.createByText || record.createBy || '-', |
| | | createTimeText: record['createTime$'] || record.createTime || '-', |
| | | updateByText: record['updateBy$'] || record.updateByText || record.updateBy || '-', |
| | | updateTimeText: record['updateTime$'] || record.updateTime || '-', |
| | | statusText: record['status$'] || record.status || '-', |
| | | memo: record.memo || '-' |
| | | } |
| | | } |
| | | |
| | |
| | | <artifactId>spring-boot-starter-actuator</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.github.ben-manes.caffeine</groupId> |
| | | <artifactId>caffeine</artifactId> |
| | | <version>2.9.3</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.springframework.ai</groupId> |
| | | <artifactId>spring-ai-openai</artifactId> |
| | | </dependency> |
| | |
| | | import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; |
| | | import com.baomidou.mybatisplus.core.MybatisConfiguration; |
| | | import com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory; |
| | | import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; |
| | | import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache; |
| | | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| | | import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; |
| | | import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; |
| | |
| | | import com.vincent.rsf.server.system.entity.User; |
| | | import net.sf.jsqlparser.expression.Expression; |
| | | import net.sf.jsqlparser.expression.LongValue; |
| | | import net.sf.jsqlparser.expression.NullValue; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.security.core.Authentication; |
| | |
| | | import org.springframework.transaction.annotation.EnableTransactionManagement; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ThreadPoolExecutor; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | |
| | | /** |
| | | * MybatisPlus配置 |
| | |
| | | @EnableTransactionManagement |
| | | public class MybatisPlusConfig { |
| | | |
| | | private static volatile boolean jsqlParserConfigured = false; |
| | | |
| | | @Bean |
| | | public MybatisPlusInterceptor mybatisPlusInterceptor() { |
| | | configureJsqlParser(); |
| | | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); |
| | | |
| | | // 添加乐观锁插件 |
| | |
| | | return interceptor; |
| | | } |
| | | |
| | | private void configureJsqlParser() { |
| | | if (jsqlParserConfigured) { |
| | | return; |
| | | } |
| | | synchronized (MybatisPlusConfig.class) { |
| | | if (jsqlParserConfigured) { |
| | | return; |
| | | } |
| | | AtomicInteger threadIndex = new AtomicInteger(1); |
| | | int parserThreads = Math.max(4, Runtime.getRuntime().availableProcessors()); |
| | | ThreadPoolExecutor parserExecutor = new ThreadPoolExecutor( |
| | | parserThreads, |
| | | parserThreads, |
| | | 0L, |
| | | TimeUnit.MILLISECONDS, |
| | | new LinkedBlockingQueue<>(2048), |
| | | runnable -> { |
| | | Thread thread = new Thread(runnable); |
| | | thread.setName("jsql-parser-" + threadIndex.getAndIncrement()); |
| | | thread.setDaemon(true); |
| | | return thread; |
| | | }, |
| | | new ThreadPoolExecutor.CallerRunsPolicy() |
| | | ); |
| | | JsqlParserGlobal.setExecutorService(parserExecutor, new Thread(() -> { |
| | | if (!parserExecutor.isShutdown()) { |
| | | parserExecutor.shutdown(); |
| | | } |
| | | }, "jsql-parser-shutdown")); |
| | | JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(builder -> |
| | | builder.maximumSize(1024).expireAfterAccess(30, TimeUnit.MINUTES))); |
| | | jsqlParserConfigured = true; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取当前登录用户的租户id |
| | | * |