From 4259deb19122a4807d50c99ed4a95405ebe4a47c Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期五, 10 四月 2026 08:40:18 +0800
Subject: [PATCH] #

---
 rsf-design/src/config/modules/component.js                                                |    8 
 rsf-design/src/utils/sys/public-project-config.js                                         |   90 +++
 rsf-design/src/components/core/base/art-svg-icon/index.vue                                |   51 +
 rsf-design/src/components/core/layouts/art-page-content/index.vue                         |   18 
 rsf-design/src/hooks/index.js                                                             |    2 
 rsf-design/src/utils/ui/iconify-loader.js                                                 |  163 ++++++
 rsf-design/src/store/modules/setting.js                                                   |   23 
 rsf-design/src/views/manager/task/modules/task-expand-panel.vue                           |   68 ++
 rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue                       |  337 ++++++++++++
 rsf-design/src/api/flow-step-instance.js                                                  |   26 
 rsf-design/src/router/core/RouteRegistry.js                                               |   57 +
 rsf-design/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.js |    4 
 rsf-design/src/components/core/base/art-copyright/index.vue                               |   38 -
 rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java      |   45 +
 rsf-design/.env.development                                                               |    2 
 rsf-design/src/config/setting.js                                                          |    8 
 rsf-design/src/router/guards/afterEach.js                                                 |    9 
 rsf-server/pom.xml                                                                        |    5 
 rsf-design/src/views/manager/task/index.vue                                               |  151 ++++
 rsf-design/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue       |    8 
 rsf-design/src/views/manager/task/modules/task-detail-drawer.vue                          |  199 +++++--
 rsf-design/src/components/core/base/art-logo/index.vue                                    |   40 -
 rsf-design/src/locales/langs/en.json                                                      |   60 ++
 rsf-design/src/locales/langs/zh.json                                                      |   60 ++
 /dev/null                                                                                 |   83 ---
 rsf-design/src/views/manager/task/taskPage.helpers.js                                     |   23 
 rsf-design/src/router/guards/beforeEach.js                                                |   29 +
 27 files changed, 1,292 insertions(+), 315 deletions(-)

diff --git a/rsf-design/.env.development b/rsf-design/.env.development
index 3e69e34..8b3e566 100644
--- a/rsf-design/.env.development
+++ b/rsf-design/.env.development
@@ -13,4 +13,4 @@
 VITE_DROP_CONSOLE = false
 
 # 鏄惁寮�鍚� Vue DevTools / Inspector锛堝紑鍚悗浼氬鍔犳湰鍦版ā鍧楄浆鎹㈣�楁椂锛�
-VITE_ENABLE_VUE_DEVTOOLS = true
+VITE_ENABLE_VUE_DEVTOOLS = false
diff --git a/rsf-design/src/api/flow-step-instance.js b/rsf-design/src/api/flow-step-instance.js
index bbb0888..fdaa512 100644
--- a/rsf-design/src/api/flow-step-instance.js
+++ b/rsf-design/src/api/flow-step-instance.js
@@ -57,6 +57,32 @@
   })
 }
 
+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',
diff --git a/rsf-design/src/components/core/base/art-copyright/index.vue b/rsf-design/src/components/core/base/art-copyright/index.vue
index a247991..8665de8 100644
--- a/rsf-design/src/components/core/base/art-copyright/index.vue
+++ b/rsf-design/src/components/core/base/art-copyright/index.vue
@@ -3,50 +3,30 @@
 </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(() => {
diff --git a/rsf-design/src/components/core/base/art-logo/index.vue b/rsf-design/src/components/core/base/art-logo/index.vue
index b578412..3601e75 100644
--- a/rsf-design/src/components/core/base/art-logo/index.vue
+++ b/rsf-design/src/components/core/base/art-logo/index.vue
@@ -12,19 +12,20 @@
 </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) {
@@ -42,43 +43,24 @@
 
   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()
diff --git a/rsf-design/src/components/core/base/art-svg-icon/index.vue b/rsf-design/src/components/core/base/art-svg-icon/index.vue
index 7f2dc9e..14a67d1 100644
--- a/rsf-design/src/components/core/base/art-svg-icon/index.vue
+++ b/rsf-design/src/components/core/base/art-svg-icon/index.vue
@@ -1,20 +1,65 @@
 <!-- 鍥炬爣缁勪欢 -->
 <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>
diff --git a/rsf-design/src/components/core/layouts/art-fireworks-effect/index.vue b/rsf-design/src/components/core/layouts/art-fireworks-effect/index.vue
deleted file mode 100644
index 3ccc3ed..0000000
--- a/rsf-design/src/components/core/layouts/art-fireworks-effect/index.vue
+++ /dev/null
@@ -1,427 +0,0 @@
-<!-- 鐑熻姳鏁堟灉 | 绀艰姳鏁堟灉 -->
-<template>
-  <canvas
-    ref="canvasRef"
-    class="fixed top-0 left-0 z-[9999] w-full h-full pointer-events-none"
-  ></canvas>
-</template>
-
-<script setup>
-  import { useEventListener } from '@vueuse/core'
-  import { mittBus } from '@/utils/sys'
-  import bp from '@/assets/images/ceremony/hb.png'
-  import sd from '@/assets/images/ceremony/sd.png'
-  import yd from '@/assets/images/ceremony/yd.png'
-  defineOptions({ name: 'ArtFireworksEffect' })
-  const CONFIG = {
-    // 鎬ц兘鐩稿叧閰嶇疆
-    POOL_SIZE: 600,
-    // 瀵硅薄姹犲ぇ灏忥紝褰卞搷鍚屾椂瀛樺湪鐨勬渶澶х矑瀛愭暟
-    PARTICLES_PER_BURST: 200,
-    // 姣忔鐖嗙偢鐨勭矑瀛愭暟閲忥紝褰卞搷瑙嗚鏁堟灉瀵嗗害
-    // 绮掑瓙灏哄閰嶇疆
-    SIZES: {
-      RECTANGLE: { WIDTH: 24, HEIGHT: 12 },
-      // 鐭╁舰绮掑瓙灏哄
-      SQUARE: { SIZE: 12 },
-      // 姝f柟褰㈢矑瀛愬昂瀵�
-      CIRCLE: { SIZE: 12 },
-      // 鍦嗗舰绮掑瓙灏哄
-      TRIANGLE: { SIZE: 10 },
-      // 涓夎褰㈢矑瀛愬昂瀵�
-      OVAL: { WIDTH: 24, HEIGHT: 12 },
-      // 妞渾绮掑瓙灏哄
-      IMAGE: { WIDTH: 30, HEIGHT: 30 }
-      // 鍥剧墖绮掑瓙灏哄
-    },
-    // 鏃嬭浆鍔ㄧ敾閰嶇疆
-    ROTATION: {
-      BASE_SPEED: 2,
-      // 鍩虹鏃嬭浆閫熷害
-      RANDOM_SPEED: 3,
-      // 棰濆闅忔満鏃嬭浆閫熷害鑼冨洿
-      DECAY: 0.98
-      // 鏃嬭浆閫熷害琛板噺绯绘暟 (瓒婂皬琛板噺瓒婂揩)
-    },
-    // 鐗╃悊鏁堟灉閰嶇疆
-    PHYSICS: {
-      GRAVITY: 0.525,
-      // 閲嶅姏鍔犻�熷害锛屽奖鍝嶇矑瀛愪笅钀介�熷害
-      VELOCITY_THRESHOLD: 10,
-      // 閫熷害闃堝�硷紝瓒呰繃鏃跺紑濮嬮�忔槑搴﹁“鍑�
-      OPACITY_DECAY: 0.02
-      // 閫忔槑搴﹁“鍑忛�熷害锛屽奖鍝嶇矑瀛愭秷澶卞揩鎱�
-    },
-    // 绮掑瓙棰滆壊閰嶇疆 - 浣跨敤RGBA鏍煎紡鏀寔閫忔槑搴�
-    COLORS: [
-      'rgba(255, 68, 68, 1)',
-      // 绾㈣壊绯�
-      'rgba(255, 68, 68, 0.9)',
-      'rgba(255, 68, 68, 0.8)',
-      'rgba(255, 116, 188, 1)',
-      // 绮夎壊绯�
-      'rgba(255, 116, 188, 0.9)',
-      'rgba(255, 116, 188, 0.8)',
-      'rgba(68, 68, 255, 0.8)',
-      // 钃濊壊绯�
-      'rgba(92, 202, 56, 0.7)',
-      // 缁胯壊绯�
-      'rgba(255, 68, 255, 0.8)',
-      // 绱壊绯�
-      'rgba(68, 255, 255, 0.7)',
-      // 闈掕壊绯�
-      'rgba(255, 136, 68, 0.7)',
-      // 姗欒壊绯�
-      'rgba(68, 136, 255, 1)',
-      // 钃濊壊绯�
-      'rgba(250, 198, 122, 0.8)'
-      // 閲戣壊绯�
-    ],
-    // 绮掑瓙褰㈢姸閰嶇疆 - 鐭╁舰鍑虹幇姒傜巼鏇撮珮锛岃惀閫犳洿涓板瘜鐨勮瑙夋晥鏋�
-    SHAPES: [
-      'rectangle',
-      'rectangle',
-      'rectangle',
-      'rectangle',
-      'rectangle',
-      'rectangle',
-      'rectangle',
-      'circle',
-      'triangle',
-      'oval'
-    ]
-  }
-  const canvasRef = ref()
-  const ctx = ref(null)
-  class FireworkSystem {
-    constructor() {
-      this.particlePool = []
-      this.activeParticles = []
-      this.poolIndex = 0
-      this.imageCache = {}
-      this.animationId = 0
-      this.canvasWidth = 0
-      this.canvasHeight = 0
-      this.animate = () => {
-        this.updateParticles()
-        this.render()
-        this.animationId = requestAnimationFrame(this.animate)
-      }
-      this.initializePool()
-    }
-    /**
-     * 鍒濆鍖栧璞℃睜
-     * 棰勫厛鍒涘缓鎸囧畾鏁伴噺鐨勭矑瀛愬璞★紝閬垮厤杩愯鏃堕绻佸垱寤�
-     */
-    initializePool() {
-      for (let i = 0; i < CONFIG.POOL_SIZE; i++) {
-        this.particlePool.push(this.createParticle())
-      }
-    }
-    /**
-     * 鍒涘缓涓�涓柊鐨勭矑瀛愬璞�
-     * 杩斿洖鍒濆鍖栫姸鎬佺殑绮掑瓙
-     */
-    createParticle() {
-      return {
-        x: 0,
-        y: 0,
-        vx: 0,
-        vy: 0,
-        color: '',
-        rotation: 0,
-        rotationSpeed: 0,
-        scale: 1,
-        shape: 'circle',
-        opacity: 1,
-        active: false
-      }
-    }
-    /**
-     * 浠庡璞℃睜鑾峰彇鍙敤绮掑瓙 (鎬ц兘浼樺寲鐗堟湰)
-     * 浣跨敤寰幆绱㈠紩鑰岄潪Array.find()锛屾椂闂村鏉傚害浠嶰(n)闄嶈嚦O(1)
-     * @returns 鍙敤鐨勭矑瀛愬璞℃垨null
-     */
-    getAvailableParticle() {
-      for (let i = 0; i < CONFIG.POOL_SIZE; i++) {
-        const index = (this.poolIndex + i) % CONFIG.POOL_SIZE
-        const particle = this.particlePool[index]
-        if (!particle.active) {
-          this.poolIndex = (index + 1) % CONFIG.POOL_SIZE
-          particle.active = true
-          return particle
-        }
-      }
-      return null
-    }
-    /**
-     * 棰勫姞杞藉崟涓浘鐗囪祫婧�
-     * @param url 鍥剧墖URL
-     * @returns Promise<HTMLImageElement>
-     */
-    async preloadImage(url) {
-      if (this.imageCache[url]) {
-        return this.imageCache[url]
-      }
-      return new Promise((resolve, reject) => {
-        const img = new Image()
-        img.crossOrigin = 'anonymous'
-        img.onload = () => {
-          this.imageCache[url] = img
-          resolve(img)
-        }
-        img.onerror = reject
-        img.src = url
-      })
-    }
-    /**
-     * 棰勫姞杞芥墍鏈夐渶瑕佺殑鍥剧墖璧勬簮
-     * 鍦ㄧ粍浠跺垵濮嬪寲鏃惰皟鐢紝纭繚鍥剧墖ready
-     */
-    async preloadAllImages() {
-      const imageUrls = [bp, sd, yd]
-      try {
-        await Promise.all(imageUrls.map((url) => this.preloadImage(url)))
-      } catch (error) {
-        console.error('Image preloading failed:', error)
-      }
-    }
-    /**
-     * 鍒涘缓鐑熻姳鐖嗙偢鏁堟灉
-     * @param imageUrl 鍙�夌殑鍥剧墖URL锛屽鏋滄彁渚涘垯浣跨敤鍥剧墖绮掑瓙
-     */
-    createFirework(imageUrl) {
-      const startX = Math.random() * this.canvasWidth
-      const startY = this.canvasHeight
-      const availableShapes = imageUrl && this.imageCache[imageUrl] ? ['image'] : CONFIG.SHAPES
-      const particles = []
-      for (let i = 0; i < CONFIG.PARTICLES_PER_BURST; i++) {
-        const particle = this.getAvailableParticle()
-        if (!particle) continue
-        const angle = (Math.PI * i) / (CONFIG.PARTICLES_PER_BURST / 2)
-        const speed = (12 + Math.random() * 6) * 1.5
-        const spread = Math.random() * Math.PI * 2
-        particle.x = startX
-        particle.y = startY
-        particle.vx = Math.cos(angle) * Math.cos(spread) * speed * (Math.random() * 0.5 + 0.5)
-        particle.vy = Math.sin(angle) * speed - 15
-        particle.color = CONFIG.COLORS[Math.floor(Math.random() * CONFIG.COLORS.length)]
-        particle.rotation = Math.random() * 360
-        particle.rotationSpeed =
-          (Math.random() * CONFIG.ROTATION.RANDOM_SPEED + CONFIG.ROTATION.BASE_SPEED) *
-          (Math.random() > 0.5 ? 1 : -1)
-        particle.scale = 0.8 + Math.random() * 0.4
-        particle.shape = availableShapes[Math.floor(Math.random() * availableShapes.length)]
-        particle.opacity = 1
-        particle.imageUrl = imageUrl && this.imageCache[imageUrl] ? imageUrl : void 0
-        particles.push(particle)
-      }
-      this.activeParticles.push(...particles)
-    }
-    /**
-     * 鏇存柊鎵�鏈夌矑瀛愮殑鐗╃悊鐘舵�� (鎬ц兘浼樺寲鐗堟湰)
-     * 鍖呮嫭浣嶇疆銆侀�熷害銆佹棆杞�侀�忔槑搴︾瓑
-     */
-    updateParticles() {
-      const { GRAVITY, VELOCITY_THRESHOLD, OPACITY_DECAY } = CONFIG.PHYSICS
-      const { DECAY } = CONFIG.ROTATION
-      for (let i = this.activeParticles.length - 1; i >= 0; i--) {
-        const particle = this.activeParticles[i]
-        particle.x += particle.vx
-        particle.y += particle.vy
-        particle.vy += GRAVITY
-        particle.rotation += particle.rotationSpeed
-        particle.rotationSpeed *= DECAY
-        if (particle.vy > VELOCITY_THRESHOLD) {
-          particle.opacity -= OPACITY_DECAY
-          if (particle.opacity <= 0) {
-            this.recycleParticle(i)
-            continue
-          }
-        }
-        if (this.isOutOfBounds(particle)) {
-          this.recycleParticle(i)
-        }
-      }
-    }
-    /**
-     * 鍥炴敹绮掑瓙鍒板璞℃睜
-     * @param index 瑕佸洖鏀剁殑绮掑瓙鍦ㄦ椿鍔ㄦ暟缁勪腑鐨勭储寮�
-     */
-    recycleParticle(index) {
-      const particle = this.activeParticles[index]
-      particle.active = false
-      this.activeParticles.splice(index, 1)
-    }
-    /**
-     * 妫�鏌ョ矑瀛愭槸鍚﹁秴鍑哄睆骞曡竟鐣�
-     * @param particle 瑕佹鏌ョ殑绮掑瓙
-     * @returns 鏄惁瓒呭嚭杈圭晫
-     */
-    isOutOfBounds(particle) {
-      const margin = 100
-      return (
-        particle.x < -margin ||
-        particle.x > this.canvasWidth + margin ||
-        particle.y < -margin ||
-        particle.y > this.canvasHeight + margin
-      )
-    }
-    /**
-     * 缁樺埗鍗曚釜绮掑瓙
-     * @param particle 瑕佺粯鍒剁殑绮掑瓙瀵硅薄
-     */
-    drawParticle(particle) {
-      if (!ctx.value) return
-      ctx.value.save()
-      ctx.value.globalAlpha = particle.opacity
-      ctx.value.translate(particle.x, particle.y)
-      ctx.value.rotate((particle.rotation * Math.PI) / 180)
-      ctx.value.scale(particle.scale, particle.scale)
-      this.renderShape(particle)
-      ctx.value.restore()
-    }
-    /**
-     * 鏍规嵁绮掑瓙绫诲瀷娓叉煋瀵瑰簲鐨勫舰鐘�
-     * @param particle 瑕佹覆鏌撶殑绮掑瓙
-     */
-    renderShape(particle) {
-      if (!ctx.value) return
-      const { SIZES } = CONFIG
-      ctx.value.fillStyle = particle.color
-      switch (particle.shape) {
-        case 'rectangle':
-          ctx.value.fillRect(
-            -SIZES.RECTANGLE.WIDTH / 2,
-            -SIZES.RECTANGLE.HEIGHT / 2,
-            SIZES.RECTANGLE.WIDTH,
-            SIZES.RECTANGLE.HEIGHT
-          )
-          break
-        case 'square':
-          ctx.value.fillRect(
-            -SIZES.SQUARE.SIZE / 2,
-            -SIZES.SQUARE.SIZE / 2,
-            SIZES.SQUARE.SIZE,
-            SIZES.SQUARE.SIZE
-          )
-          break
-        case 'circle':
-          ctx.value.beginPath()
-          ctx.value.arc(0, 0, SIZES.CIRCLE.SIZE / 2, 0, Math.PI * 2)
-          ctx.value.fill()
-          break
-        case 'triangle':
-          ctx.value.beginPath()
-          ctx.value.moveTo(0, -SIZES.TRIANGLE.SIZE)
-          ctx.value.lineTo(SIZES.TRIANGLE.SIZE, SIZES.TRIANGLE.SIZE)
-          ctx.value.lineTo(-SIZES.TRIANGLE.SIZE, SIZES.TRIANGLE.SIZE)
-          ctx.value.closePath()
-          ctx.value.fill()
-          break
-        case 'oval':
-          ctx.value.beginPath()
-          ctx.value.ellipse(0, 0, SIZES.OVAL.WIDTH / 2, SIZES.OVAL.HEIGHT / 2, 0, 0, Math.PI * 2)
-          ctx.value.fill()
-          break
-        case 'image':
-          this.renderImage(particle)
-          break
-      }
-    }
-    /**
-     * 娓叉煋鍥剧墖绫诲瀷鐨勭矑瀛�
-     * @param particle 鍖呭惈鍥剧墖URL鐨勭矑瀛愬璞�
-     */
-    renderImage(particle) {
-      if (!ctx.value || !particle.imageUrl) return
-      const img = this.imageCache[particle.imageUrl]
-      if (img?.complete) {
-        const { WIDTH, HEIGHT } = CONFIG.SIZES.IMAGE
-        ctx.value.drawImage(img, -WIDTH / 2, -HEIGHT / 2, WIDTH, HEIGHT)
-      }
-    }
-    /**
-     * 娓叉煋鎵�鏈夋椿鍔ㄧ矑瀛愬埌鐢诲竷
-     * 娓呴櫎鐢诲竷骞堕噸鏂扮粯鍒舵墍鏈夌矑瀛�
-     */
-    render() {
-      if (!ctx.value || !canvasRef.value) return
-      ctx.value.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
-      ctx.value.globalCompositeOperation = 'lighter'
-      for (const particle of this.activeParticles) {
-        this.drawParticle(particle)
-      }
-    }
-    /**
-     * 鏇存柊鐢诲竷灏哄缂撳瓨
-     * 鍦ㄧ獥鍙eぇ灏忔敼鍙樻椂璋冪敤
-     * @param width 鏂扮殑鐢诲竷瀹藉害
-     * @param height 鏂扮殑鐢诲竷楂樺害
-     */
-    updateCanvasSize(width, height) {
-      this.canvasWidth = width
-      this.canvasHeight = height
-    }
-    /**
-     * 鍚姩鍔ㄧ敾寰幆
-     */
-    start() {
-      this.animate()
-    }
-    /**
-     * 鍋滄鍔ㄧ敾寰幆
-     * 鍦ㄧ粍浠跺嵏杞芥椂璋冪敤锛岄伩鍏嶅唴瀛樻硠婕�
-     */
-    stop() {
-      if (this.animationId) {
-        cancelAnimationFrame(this.animationId)
-        this.animationId = 0
-      }
-    }
-    /**
-     * 鑾峰彇褰撳墠娲诲姩绮掑瓙鏁伴噺
-     * 鐢ㄤ簬璋冭瘯鍜屾�ц兘鐩戞帶
-     * @returns 娲诲姩绮掑瓙鏁伴噺
-     */
-    getActiveParticleCount() {
-      return this.activeParticles.length
-    }
-  }
-  const fireworkSystem = new FireworkSystem()
-  const handleKeyPress = (event) => {
-    const isFireworkShortcut =
-      (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'p') ||
-      (event.metaKey && event.shiftKey && event.key.toLowerCase() === 'p')
-    if (isFireworkShortcut) {
-      event.preventDefault()
-      fireworkSystem.createFirework()
-    }
-  }
-  const resizeCanvas = () => {
-    if (!canvasRef.value) return
-    const { innerWidth, innerHeight } = window
-    canvasRef.value.width = innerWidth
-    canvasRef.value.height = innerHeight
-    fireworkSystem.updateCanvasSize(innerWidth, innerHeight)
-  }
-  const handleFireworkTrigger = (event) => {
-    const imageUrl = event
-    fireworkSystem.createFirework(imageUrl)
-  }
-  onMounted(async () => {
-    if (!canvasRef.value) return
-    ctx.value = canvasRef.value.getContext('2d')
-    if (!ctx.value) return
-    resizeCanvas()
-    await fireworkSystem.preloadAllImages()
-    fireworkSystem.start()
-    useEventListener(window, 'keydown', handleKeyPress)
-    useEventListener(window, 'resize', resizeCanvas)
-    mittBus.on('triggerFireworks', handleFireworkTrigger)
-  })
-  onUnmounted(() => {
-    fireworkSystem.stop()
-    mittBus.off('triggerFireworks', handleFireworkTrigger)
-  })
-</script>
diff --git a/rsf-design/src/components/core/layouts/art-page-content/index.vue b/rsf-design/src/components/core/layouts/art-page-content/index.vue
index c208db8..bff287e 100644
--- a/rsf-design/src/components/core/layouts/art-page-content/index.vue
+++ b/rsf-design/src/components/core/layouts/art-page-content/index.vue
@@ -2,9 +2,6 @@
 <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'"
@@ -27,7 +24,12 @@
     <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>
@@ -36,7 +38,12 @@
 
       <!-- 闈炵紦瀛樿矾鐢卞姩鐢� -->
       <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>
@@ -163,5 +170,4 @@
     line-height: 1.7;
     word-break: break-word;
   }
-
 </style>
diff --git a/rsf-design/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.js b/rsf-design/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.js
index 5f9a752..a80e703 100644
--- a/rsf-design/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.js
+++ b/rsf-design/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.js
@@ -7,13 +7,11 @@
 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()
@@ -144,12 +142,10 @@
       const boxMode = settingStore.boxBorderMode ? 'border-mode' : 'shadow-mode'
       domOperations.setRootAttribute('data-box-mode', boxMode)
       themeHandlers.initSystemTheme()
-      openFestival()
     }
     const cleanupSettings = () => {
       stopWatch()
       themeCleanup?.()
-      cleanup()
     }
     return {
       initializeSettings,
diff --git a/rsf-design/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue b/rsf-design/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue
index c11991f..022010c 100644
--- a/rsf-design/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue
+++ b/rsf-design/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue
@@ -64,19 +64,16 @@
     { 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'
@@ -147,7 +144,6 @@
         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, () =>
@@ -161,8 +157,6 @@
       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)
diff --git a/rsf-design/src/components/core/text-effect/art-festival-text-scroll/index.vue b/rsf-design/src/components/core/text-effect/art-festival-text-scroll/index.vue
deleted file mode 100644
index 0904cc8..0000000
--- a/rsf-design/src/components/core/text-effect/art-festival-text-scroll/index.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-<!-- 鑺傛棩鏂囨湰婊氬姩 -->
-<template>
-  <div
-    class="overflow-hidden transition-[height] duration-600 ease-in-out"
-    :style="{
-      height: showFestivalText ? '48px' : '0'
-    }"
-  >
-    <ArtTextScroll
-      v-if="showFestivalText && currentFestivalData?.scrollText !== ''"
-      :text="currentFestivalData?.scrollText || ''"
-      style="margin-bottom: 12px"
-      showClose
-      @close="handleClose"
-    />
-  </div>
-</template>
-
-<script setup>
-  import { useSettingStore } from '@/store/modules/setting'
-  import { useCeremony } from '@/hooks/core/useCeremony'
-  defineOptions({ name: 'ArtFestivalTextScroll' })
-  const settingStore = useSettingStore()
-  const { showFestivalText } = storeToRefs(settingStore)
-  const { currentFestivalData } = useCeremony()
-  const handleClose = () => {
-    settingStore.setShowFestivalText(false)
-  }
-</script>
diff --git a/rsf-design/src/config/modules/component.js b/rsf-design/src/config/modules/component.js
index dcb9fdc..7ffa4fc 100644
--- a/rsf-design/src/config/modules/component.js
+++ b/rsf-design/src/config/modules/component.js
@@ -33,14 +33,6 @@
     enabled: true
   },
   {
-    name: '绀艰姳鏁堟灉',
-    key: 'fireworks-effect',
-    component: defineAsyncComponent(
-      () => import('@/components/core/layouts/art-fireworks-effect/index.vue')
-    ),
-    enabled: true
-  },
-  {
     name: '姘村嵃鏁堟灉',
     key: 'watermark',
     component: defineAsyncComponent(
diff --git a/rsf-design/src/config/modules/festival.js b/rsf-design/src/config/modules/festival.js
deleted file mode 100644
index 6519a98..0000000
--- a/rsf-design/src/config/modules/festival.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const festivalConfigList = [
-  // 璺ㄦ棩鏈熺ず渚�
-  // {
-  //   name: 'v3.0 Sass 鍗囩骇鑷� TailwindCSS',
-  //   date: '2025-11-03',
-  //   endDate: '2025-11-09',
-  //   image: '',
-  //   count: 3,
-  //   scrollText:
-  //     '馃殌 绯荤粺 v3.0 娴嬭瘯闃舵姝e紡寮�鍚紒娴嬭瘯鍛ㄦ湡涓� 11 鏈� 3 鏃� - 11 鏈� 16 鏃ワ紝閫氳繃 TailwindCSS 閲嶆瀯鏍峰紡浣撶郴銆佺粺涓� Iconify 鍥炬爣鏂规锛屽甫鏉ユ洿楂樻晥鐜颁唬鐨勫紑鍙戜綋楠岋紝姝e紡鍙戝竷鏁鏈熷緟锝�'
-  // }
-  // 鍗曟棩绀轰緥锛氬湥璇炶妭
-  // {
-  //   name: '鍦h癁鑺�',
-  //   date: '2024-12-25',
-  //   image: sd,
-  //   count: 3 // 鍙�夛紝涓嶈缃垯浣跨敤榛樿鍊� 3 娆�
-  //   scrollText: 'Merry Christmas锛丄rt Design Pro 绁濇偍鍦h癁蹇箰锛屾効鑺傛棩鐨勬涔愪笌绁濈濡傞洩鑺辫埇绾疯嚦娌撴潵锛�',
-  // }
-]
-export { festivalConfigList }
diff --git a/rsf-design/src/config/setting.js b/rsf-design/src/config/setting.js
index a15ffab..9275860 100644
--- a/rsf-design/src/config/setting.js
+++ b/rsf-design/src/config/setting.js
@@ -33,8 +33,6 @@
   showNprogress: false,
   /** 鏄惁鏄剧ず璁剧疆寮曞 */
   showSettingGuide: true,
-  /** 鏄惁鏄剧ず鑺傛棩鏂囨湰 */
-  showFestivalText: false,
   /** 鏄惁鏄剧ず姘村嵃 */
   watermarkVisible: false,
   /** 鏄惁鑷姩鍏抽棴 */
@@ -45,8 +43,6 @@
   colorWeak: false,
   /** 鏄惁鍒锋柊 */
   refresh: false,
-  /** 鏄惁鍔犺浇鑺傛棩鐑熻姳 */
-  holidayFireworksLoaded: false,
   /** 杈规妯″紡 */
   boxBorderMode: true,
   /** 椤甸潰杩囨浮鏁堟灉 */
@@ -56,9 +52,7 @@
   /** 鑷畾涔夊渾瑙� */
   customRadius: '0.75',
   /** 瀹瑰櫒瀹藉害 */
-  containerWidth: ContainerWidthEnum.FULL,
-  /** 鑺傛棩鏃ユ湡 */
-  festivalDate: ''
+  containerWidth: ContainerWidthEnum.FULL
 }
 function getSettingDefaults() {
   return { ...SETTING_DEFAULT_CONFIG }
diff --git a/rsf-design/src/hooks/core/useCeremony.js b/rsf-design/src/hooks/core/useCeremony.js
deleted file mode 100644
index fbcf000..0000000
--- a/rsf-design/src/hooks/core/useCeremony.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { useTimeoutFn, useIntervalFn, useDateFormat } from '@vueuse/core'
-import { storeToRefs } from 'pinia'
-import { computed } from 'vue'
-import { useSettingStore } from '@/store/modules/setting'
-import { mittBus } from '@/utils/sys'
-import { festivalConfigList } from '@/config/modules/festival'
-const FESTIVAL_CONFIG = {
-  /** 鍒濆寤惰繜锛堟绉掞級 */
-  INITIAL_DELAY: 300,
-  /** 鐑熻姳鎾斁闂撮殧锛堟绉掞級 */
-  FIREWORK_INTERVAL: 1e3,
-  /** 鏂囨湰鏄剧ず寤惰繜锛堟绉掞級 */
-  TEXT_DELAY: 2e3,
-  /** 榛樿鐑熻姳鎾斁娆℃暟 */
-  DEFAULT_FIREWORKS_COUNT: 3
-}
-function useCeremony() {
-  const settingStore = useSettingStore()
-  const { holidayFireworksLoaded, isShowFireworks } = storeToRefs(settingStore)
-  let fireworksInterval = null
-  const isDateInRange = (currentDate, festivalDate, festivalEndDate) => {
-    if (!festivalEndDate) {
-      return currentDate === festivalDate
-    }
-    const current = new Date(currentDate)
-    const start = new Date(festivalDate)
-    const end = new Date(festivalEndDate)
-    return current >= start && current <= end
-  }
-  const currentFestivalData = computed(() => {
-    const currentDate = useDateFormat(/* @__PURE__ */ new Date(), 'YYYY-MM-DD').value
-    return festivalConfigList.find((item) => isDateInRange(currentDate, item.date, item.endDate))
-  })
-  const updateFestivalDate = () => {
-    settingStore.setFestivalDate(currentFestivalData.value?.date || '')
-  }
-  const triggerFirework = () => {
-    mittBus.emit('triggerFireworks', currentFestivalData.value?.image)
-  }
-  const showFestivalText = () => {
-    settingStore.setholidayFireworksLoaded(true)
-    useTimeoutFn(() => {
-      settingStore.setShowFestivalText(true)
-      updateFestivalDate()
-    }, FESTIVAL_CONFIG.TEXT_DELAY)
-  }
-  const startFireworksLoop = () => {
-    let playedCount = 0
-    const count = currentFestivalData.value?.count ?? FESTIVAL_CONFIG.DEFAULT_FIREWORKS_COUNT
-    const { pause } = useIntervalFn(() => {
-      triggerFirework()
-      playedCount++
-      if (playedCount >= count) {
-        pause()
-        showFestivalText()
-      }
-    }, FESTIVAL_CONFIG.FIREWORK_INTERVAL)
-    fireworksInterval = { pause }
-  }
-  const openFestival = () => {
-    if (!currentFestivalData.value || !isShowFireworks.value) {
-      return
-    }
-    const { start } = useTimeoutFn(startFireworksLoop, FESTIVAL_CONFIG.INITIAL_DELAY)
-    start()
-  }
-  const cleanup = () => {
-    if (fireworksInterval) {
-      fireworksInterval.pause()
-      fireworksInterval = null
-    }
-    settingStore.setShowFestivalText(false)
-    updateFestivalDate()
-  }
-  return {
-    openFestival,
-    cleanup,
-    holidayFireworksLoaded,
-    currentFestivalData,
-    isShowFireworks
-  }
-}
-export { useCeremony }
diff --git a/rsf-design/src/hooks/index.js b/rsf-design/src/hooks/index.js
index 1aa58d3..4946bc6 100644
--- a/rsf-design/src/hooks/index.js
+++ b/rsf-design/src/hooks/index.js
@@ -4,7 +4,6 @@
 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'
@@ -13,7 +12,6 @@
   useAppMode,
   useAuth,
   useAutoLayoutHeight,
-  useCeremony,
   useChart,
   useChartComponent,
   useChartOps,
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index 898594e..1bbe119 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -2123,8 +2123,10 @@
       "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",
@@ -2133,8 +2135,15 @@
         "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",
@@ -2148,12 +2157,22 @@
         "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",
@@ -2162,9 +2181,46 @@
         "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}?",
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index 7b7b4ba..db85659 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -2131,8 +2131,10 @@
       "detail": {
         "title": "浠诲姟璇︽儏",
         "taskCode": "浠诲姟鍙�",
+        "taskId": "浠诲姟ID",
         "baseInfo": "浠诲姟鍩虹淇℃伅",
         "pathInfo": "鎵ц璺緞",
+        "executionInfo": "鎵ц淇℃伅",
         "items": "浠诲姟鏄庣粏",
         "itemsHint": "鏌ョ湅褰撳墠浠诲姟鍏宠仈鐨勪笟鍔″崟鎹�佺墿鏂欏拰鎵ц璁板綍",
         "flowStep": "娴佺▼姝ラ",
@@ -2141,8 +2143,15 @@
         "warehType": "璁惧绫诲瀷",
         "priority": "浼樺厛绾�",
         "status": "鐘舵��",
+        "exceStatus": "鎵ц鐘舵��",
+        "expDesc": "寮傚父鎻忚堪",
+        "expCode": "寮傚父缂栫爜",
+        "startTime": "寮�濮嬫椂闂�",
+        "endTime": "缁撴潫鏃堕棿",
         "robotCode": "鏈哄櫒浜虹紪鐮�",
+        "createBy": "鍒涘缓浜�",
         "createTime": "鍒涘缓鏃堕棿",
+        "updateBy": "鏇存柊浜�",
         "updateTime": "鏇存柊鏃堕棿",
         "memo": "澶囨敞",
         "orgLoc": "婧愬簱浣�",
@@ -2156,12 +2165,22 @@
         "empty": "鏆傛棤浠诲姟鏄庣粏",
         "orderType": "鍗曟嵁绫诲瀷",
         "wkType": "涓氬姟绫诲瀷",
+        "platOrderCode": "瀹㈡埛璁㈠崟鍙�",
         "platWorkCode": "宸ュ崟鍙�",
         "platItemId": "琛屽彿",
-        "anfme": "鏁伴噺"
+        "projectCode": "椤圭洰鍙�",
+        "anfme": "鏁伴噺",
+        "workQty": "鎵ц鏁伴噺",
+        "qty": "瀹屾垚鏁伴噺",
+        "spec": "瑙勬牸",
+        "model": "鍨嬪彿"
       },
       "flowStepDialog": {
         "title": "娴佺▼姝ラ",
+        "create": "鏂板姝ラ",
+        "createTitle": "鏂板娴佺▼姝ラ",
+        "editTitle": "缂栬緫娴佺▼姝ラ",
+        "jumpCurrent": "璺宠浆褰撳墠",
         "currentTask": "褰撳墠浠诲姟",
         "flowInstanceNo": "娴佺▼瀹炰緥鍙�",
         "stepCode": "姝ラ缂栫爜",
@@ -2170,9 +2189,46 @@
         "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} 鍚楋紵",
diff --git a/rsf-design/src/router/core/RouteRegistry.js b/rsf-design/src/router/core/RouteRegistry.js
index 23b0219..29cd7e0 100644
--- a/rsf-design/src/router/core/RouteRegistry.js
+++ b/rsf-design/src/router/core/RouteRegistry.js
@@ -2,6 +2,9 @@
 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 = {}) {
@@ -86,15 +89,21 @@
    */
   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
+      })
     })
   }
 }
@@ -128,9 +137,41 @@
   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 }
diff --git a/rsf-design/src/router/guards/afterEach.js b/rsf-design/src/router/guards/afterEach.js
index f736bdc..5733090 100644
--- a/rsf-design/src/router/guards/afterEach.js
+++ b/rsf-design/src/router/guards/afterEach.js
@@ -3,10 +3,10 @@
 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) {
@@ -21,6 +21,9 @@
         resetPendingLoading()
       })
     }
+    nextTick(() => {
+      triggerHomeRouteWarmup(to.path, homePath.value || '/')
+    })
   })
 }
 export { setupAfterEachGuard }
diff --git a/rsf-design/src/router/guards/beforeEach.js b/rsf-design/src/router/guards/beforeEach.js
index 96c6a21..8db28b3 100644
--- a/rsf-design/src/router/guards/beforeEach.js
+++ b/rsf-design/src/router/guards/beforeEach.js
@@ -8,6 +8,7 @@
 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'
@@ -20,6 +21,8 @@
 let routeInitFailed = false
 let routeInitInProgress = false
 let pendingRouteLocation = null
+let pendingWarmupMenuList = null
+let homeWarmupTriggered = false
 function getPendingLoading() {
   return pendingLoading
 }
@@ -33,6 +36,8 @@
   routeInitFailed = false
   routeInitInProgress = false
   pendingRouteLocation = null
+  pendingWarmupMenuList = null
+  homeWarmupTriggered = false
 }
 function createRouteLocation(route) {
   return {
@@ -196,10 +201,12 @@
       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)
@@ -295,11 +302,29 @@
 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
 }
diff --git a/rsf-design/src/store/modules/setting.js b/rsf-design/src/store/modules/setting.js
index b737b40..cd2bdfc 100644
--- a/rsf-design/src/store/modules/setting.js
+++ b/rsf-design/src/store/modules/setting.js
@@ -3,7 +3,6 @@
 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(
@@ -25,19 +24,16 @@
     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) {
@@ -54,9 +50,6 @@
     })
     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
@@ -137,15 +130,6 @@
       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
     }
@@ -174,16 +158,12 @@
       refresh,
       watermarkVisible,
       customRadius,
-      holidayFireworksLoaded,
-      showFestivalText,
-      festivalDate,
       dualMenuShowText,
       containerWidth,
       getMenuTheme,
       isDark,
       getMenuOpenWidth,
       getCustomRadius,
-      isShowFireworks,
       switchMenuLayouts,
       setMenuOpenWidth,
       setGlopTheme,
@@ -209,9 +189,6 @@
       reload,
       setWatermarkVisible,
       setCustomRadius,
-      setholidayFireworksLoaded,
-      setShowFestivalText,
-      setFestivalDate,
       setDualMenuShowText
     }
   },
diff --git a/rsf-design/src/utils/sys/public-project-config.js b/rsf-design/src/utils/sys/public-project-config.js
new file mode 100644
index 0000000..e30d371
--- /dev/null
+++ b/rsf-design/src/utils/sys/public-project-config.js
@@ -0,0 +1,90 @@
+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
+}
diff --git a/rsf-design/src/utils/ui/iconify-loader.js b/rsf-design/src/utils/ui/iconify-loader.js
index e69de29..f62f06e 100644
--- a/rsf-design/src/utils/ui/iconify-loader.js
+++ b/rsf-design/src/utils/ui/iconify-loader.js
@@ -0,0 +1,163 @@
+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
+}
diff --git a/rsf-design/src/views/manager/task/index.vue b/rsf-design/src/views/manager/task/index.vue
index 261a216..b78d431 100644
--- a/rsf-design/src/views/manager/task/index.vue
+++ b/rsf-design/src/views/manager/task/index.vue
@@ -44,10 +44,7 @@
       />
     </ElCard>
 
-    <TaskFlowStepDialog
-      v-model:visible="flowStepDialogVisible"
-      :task-row="activeTaskRow"
-    />
+    <TaskFlowStepDialog v-model:visible="flowStepDialogVisible" :task-row="activeTaskRow" />
 
     <TaskDetailDrawer
       v-model:visible="detailDrawerVisible"
@@ -76,6 +73,7 @@
     fetchPickTask,
     fetchRemoveTask,
     fetchTaskAutoRunFlag,
+    fetchTaskDetail,
     fetchTaskItemPage,
     fetchTaskPage,
     fetchTopTask,
@@ -174,6 +172,12 @@
       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,
@@ -183,6 +187,12 @@
       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
     },
     {
@@ -215,9 +225,57 @@
       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
     },
     {
@@ -257,11 +315,15 @@
       }
 
       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') {
@@ -338,9 +400,13 @@
   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'
@@ -354,7 +420,11 @@
     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 {
@@ -369,28 +439,59 @@
 
     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
diff --git a/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue b/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
index 293108f..d67ff02 100644
--- a/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
+++ b/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
@@ -5,70 +5,127 @@
     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>
 
@@ -86,9 +143,31 @@
     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>
diff --git a/rsf-design/src/views/manager/task/modules/task-expand-panel.vue b/rsf-design/src/views/manager/task/modules/task-expand-panel.vue
index 80fa06f..2dad1ea 100644
--- a/rsf-design/src/views/manager/task/modules/task-expand-panel.vue
+++ b/rsf-design/src/views/manager/task/modules/task-expand-panel.vue
@@ -1,8 +1,12 @@
 <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
@@ -53,6 +57,12 @@
       showOverflowTooltip: true
     },
     {
+      prop: 'platOrderCode',
+      label: t('pages.task.expand.platOrderCode'),
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
       prop: 'platWorkCode',
       label: t('pages.task.expand.platWorkCode'),
       minWidth: 150,
@@ -62,6 +72,12 @@
       prop: 'platItemId',
       label: t('pages.task.expand.platItemId'),
       minWidth: 100,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'projectCode',
+      label: t('pages.task.expand.projectCode'),
+      minWidth: 140,
       showOverflowTooltip: true
     },
     {
@@ -94,12 +110,60 @@
       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,
diff --git a/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue b/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
index 11c5b2d..f6c3dcf 100644
--- a/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
+++ b/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
@@ -8,13 +8,27 @@
     @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
@@ -26,14 +40,87 @@
         @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({
@@ -50,13 +137,72 @@
   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(() => [
     {
@@ -112,8 +258,61 @@
       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)
@@ -162,15 +361,141 @@
     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()
     }
   )
diff --git a/rsf-design/src/views/manager/task/taskPage.helpers.js b/rsf-design/src/views/manager/task/taskPage.helpers.js
index c002b57..58cae51 100644
--- a/rsf-design/src/views/manager/task/taskPage.helpers.js
+++ b/rsf-design/src/views/manager/task/taskPage.helpers.js
@@ -42,6 +42,7 @@
 export function normalizeTaskRow(record = {}) {
   return {
     ...record,
+    taskId: record.id ?? '--',
     taskCode: record.taskCode || '-',
     taskStatusLabel: record['taskStatus$'] || '-',
     taskTypeLabel: record['taskType$'] || '-',
@@ -53,9 +54,17 @@
     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
   }
@@ -66,15 +75,25 @@
     ...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 || '-'
   }
 }
 
diff --git a/rsf-server/pom.xml b/rsf-server/pom.xml
index 454c873..63b7bce 100644
--- a/rsf-server/pom.xml
+++ b/rsf-server/pom.xml
@@ -53,6 +53,11 @@
 			<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>
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
index 81cca44..c28485d 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
@@ -3,6 +3,8 @@
 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;
@@ -11,7 +13,6 @@
 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;
@@ -19,6 +20,10 @@
 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閰嶇疆
@@ -30,8 +35,11 @@
 @EnableTransactionManagement
 public class MybatisPlusConfig {
 
+    private static volatile boolean jsqlParserConfigured = false;
+
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        configureJsqlParser();
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 
         // 娣诲姞涔愯閿佹彃浠�
@@ -75,6 +83,41 @@
         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;
+        }
+    }
+
     /**
      * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛鐨勭鎴穒d
      *

--
Gitblit v1.9.1