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