diff --git a/frontends/apps/simple-platform-vue/components.d.ts b/frontends/apps/simple-platform-vue/components.d.ts index a7b6129..eb8b509 100644 --- a/frontends/apps/simple-platform-vue/components.d.ts +++ b/frontends/apps/simple-platform-vue/components.d.ts @@ -80,7 +80,6 @@ declare module 'vue' { ReflectionManagerPanel: typeof import('./src/components/reflection/ReflectionManagerPanel.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - ScopeSwitcher: typeof import('./src/components/ScopeSwitcher.vue')['default'] SelectionDetailPanel: typeof import('./src/components/workbench/SelectionDetailPanel.vue')['default'] SitePickDialog: typeof import('./src/components/workbench/SitePickDialog.vue')['default'] ThemeCustomizer: typeof import('./src/components/ThemeCustomizer.vue')['default'] diff --git a/frontends/apps/simple-platform-vue/src/components/ScopeSwitcher.vue b/frontends/apps/simple-platform-vue/src/components/ScopeSwitcher.vue deleted file mode 100644 index 89f495d..0000000 --- a/frontends/apps/simple-platform-vue/src/components/ScopeSwitcher.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/frontends/apps/simple-platform-vue/src/components/wizard/WizardSetupBar.vue b/frontends/apps/simple-platform-vue/src/components/wizard/WizardSetupBar.vue new file mode 100644 index 0000000..8c76739 --- /dev/null +++ b/frontends/apps/simple-platform-vue/src/components/wizard/WizardSetupBar.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/frontends/apps/simple-platform-vue/src/utils/wizardSetup.ts b/frontends/apps/simple-platform-vue/src/utils/wizardSetup.ts new file mode 100644 index 0000000..e847f21 --- /dev/null +++ b/frontends/apps/simple-platform-vue/src/utils/wizardSetup.ts @@ -0,0 +1,340 @@ +/** 首次部署向导:步骤、草稿、配置页放行。 */ + +export const WIZARD_DRAFT_KEY = 'migu.wizard.draft' +export const WIZARD_SETUP_KEY = 'migu.wizard.inSetup' + +export type WizardStepId = + | 'nav' + | 'scenario' + | 'modules' + | 'vehicles' + | 'map' + | 'business' + +export interface WizardDraft { + platformType: string + navigationKinds: string[] + modules: string[] + scenarios: string[] + step: WizardStepId + inSetup: boolean + /** 配置清单勾选(key 见 WizardChecklistItem.key) */ + checked: string[] +} + +export interface WizardChecklistItem { + key: string + title: string + detail: string + route?: string + query?: Record + cta?: string +} + +export interface WizardStepDef { + id: WizardStepId + index: number + title: string + hint: string + /** 选型步 / 配置引导步 */ + kind: 'select' | 'setup' +} + +export const WIZARD_STEPS: WizardStepDef[] = [ + { id: 'nav', index: 1, title: '导航方式', hint: '选择现场实际使用的定位方式', kind: 'select' }, + { id: 'scenario', index: 2, title: '业务场景', hint: '决定后续设备与互锁怎么配', kind: 'select' }, + { id: 'modules', index: 3, title: '功能模块', hint: '按需裁剪仓储 / 拣选等能力', kind: 'select' }, + { id: 'vehicles', index: 4, title: '车辆配置', hint: '添加车辆并填写运行参数', kind: 'setup' }, + { id: 'map', index: 5, title: '地图配置', hint: '站点、路径与功能参数', kind: 'setup' }, + { id: 'business', index: 6, title: '业务配置', hint: '按场景接入设备与互锁', kind: 'setup' } +] + +/** 向导进行中允许进入的业务页(需同时 inSetup=true)。 */ +export const WIZARD_SETUP_ROUTES = new Set([ + 'admin-cars', + 'admin-map-editor', + 'admin-maps', + 'admin-tracks', + 'admin-config-facility', + 'admin-task-templates', + 'admin-project-properties' +]) + +export const NAV_SCENE: Record = { + magnetic: 'scene.mag', + qrcode: 'scene.qrlidar', + laser: 'scene.qrlidar' +} + +const VEHICLE_CHECKLIST: WizardChecklistItem[] = [ + { + key: 'veh-add', + title: '添加车辆', + detail: '打开车辆管理 → 右上角「新建车辆」,从已加载车型实例化至少一台车。没有车则地图监控与任务无法绑定实车。', + route: 'admin-cars', + cta: '去添加车辆' + }, + { + key: 'veh-params', + title: '填写车辆参数', + detail: '编号、名称、车型、IP / 通讯地址必须填写;电池、速度、尺寸等运行参数按车型补齐后保存。', + route: 'admin-cars', + cta: '去填参数' + }, + { + key: 'veh-list', + title: '确认列表可见', + detail: '保存后车辆列表中能看到该车,状态不是空列表。后续站点占用、任务下发都依赖这台车。', + route: 'admin-cars', + cta: '查看车辆列表' + } +] + +const MAP_CHECKLIST: WizardChecklistItem[] = [ + { + key: 'map-sites', + title: '配置站点', + detail: '在地图编辑器布置工位、充电、待命、互锁等站点,并核对接驳点 / 站点属性。', + route: 'admin-map-editor', + cta: '去布站点' + }, + { + key: 'map-paths', + title: '绘制路径', + detail: '用直线 / 曲线把站点连起来,保证车辆从待命到工位、充电点均可到达。', + route: 'admin-map-editor', + cta: '去画路径' + }, + { + key: 'map-params', + title: '功能参数', + detail: '按导航方式补站点功能参数:锁点、限速、交管、允许停车等。可在场景/路径表里核对。', + route: 'admin-tracks', + cta: '去核路径表' + } +] + +const SCENARIO_CHECKLIST: Record = { + 'tpl-sps': [ + { + key: 'sps-device', + title: '添加上下线机构', + detail: '进入设备管理 → 设备实例,添加上线机构、下线机构(或对应 SPS 台架设备)。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去添加机构' + }, + { + key: 'sps-interlock-site', + title: '配置互锁站点', + detail: '把机构对应的互锁站点绑到地图上的上线/下线工位,避免车未到位就放行机构。', + route: 'admin-map-editor', + cta: '去绑互锁站点' + }, + { + key: 'sps-comm', + title: '设备通讯参数', + detail: '填写协议(如 Modbus / PLC)、IP、端口,确认设备实例已启用、能通讯。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去配通讯' + }, + { + key: 'sps-addr', + title: '互锁点地址', + detail: '配置互锁点(锁点)地址参数,与现场 PLC / 机构到位、放行信号对齐。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去配互锁地址' + } + ], + 'tpl-pack': [ + { + key: 'pack-device', + title: '工位对接设备', + detail: '在设备管理中添加产线工位对接设备。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去添加设备' + }, + { + key: 'pack-interlock', + title: '互锁站点', + detail: '绑定工位互锁站点,避免车与设备抢位。', + route: 'admin-map-editor', + cta: '去绑站点' + }, + { + key: 'pack-comm', + title: '通讯与地址', + detail: '配置设备通讯参数及互锁点地址。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去配参数' + } + ], + 'tpl-loop': [ + { + key: 'loop-sites', + title: '环线站点', + detail: '确认环线进出站、循环路径已在地图中闭合,避免堵车。', + route: 'admin-map-editor', + cta: '去核地图' + } + ], + 'tpl-p2p': [ + { + key: 'p2p-sites', + title: '取放货站点', + detail: '明确取货点与放货点,并在地图中标好。', + route: 'admin-map-editor', + cta: '去标站点' + }, + { + key: 'p2p-tpl', + title: '任务模板', + detail: '可在任务编排中配置点对点搬运模板。', + route: 'admin-task-templates', + cta: '去配任务模板' + } + ] +} + +const GENERIC_BUSINESS: WizardChecklistItem[] = [ + { + key: 'biz-device', + title: '设备接入', + detail: '如现场有门、充电桩、机构等,在设备管理中添加并配置通讯。', + route: 'admin-config-facility', + query: { tab: 'devices' }, + cta: '去设备管理' + } +] + +export function emptyDraft(): WizardDraft { + return { + platformType: 'standard', + navigationKinds: [], + modules: ['wms'], + scenarios: [], + step: 'nav', + inSetup: false, + checked: [] + } +} + +export function loadWizardDraft(): WizardDraft { + try { + const raw = sessionStorage.getItem(WIZARD_DRAFT_KEY) + if (!raw) return emptyDraft() + const parsed = JSON.parse(raw) as Partial + const base = emptyDraft() + return { + ...base, + ...parsed, + navigationKinds: Array.isArray(parsed.navigationKinds) ? parsed.navigationKinds : [], + modules: Array.isArray(parsed.modules) ? parsed.modules : base.modules, + scenarios: Array.isArray(parsed.scenarios) ? parsed.scenarios : [], + step: WIZARD_STEPS.some((s) => s.id === parsed.step) ? (parsed.step as WizardStepId) : 'nav', + inSetup: !!parsed.inSetup, + checked: Array.isArray(parsed.checked) ? parsed.checked : [] + } + } catch { + return emptyDraft() + } +} + +export function saveWizardDraft(draft: WizardDraft) { + try { + sessionStorage.setItem(WIZARD_DRAFT_KEY, JSON.stringify(draft)) + if (draft.inSetup) sessionStorage.setItem(WIZARD_SETUP_KEY, '1') + else sessionStorage.removeItem(WIZARD_SETUP_KEY) + } catch { /* ignore */ } +} + +export function clearWizardDraft() { + try { + sessionStorage.removeItem(WIZARD_DRAFT_KEY) + sessionStorage.removeItem(WIZARD_SETUP_KEY) + } catch { /* ignore */ } +} + +export function isWizardSetupActive(): boolean { + try { + return sessionStorage.getItem(WIZARD_SETUP_KEY) === '1' + } catch { + return false + } +} + +export function stepDef(id: WizardStepId): WizardStepDef { + return WIZARD_STEPS.find((s) => s.id === id) ?? WIZARD_STEPS[0]! +} + +export function stepIndex(id: WizardStepId): number { + return WIZARD_STEPS.findIndex((s) => s.id === id) +} + +export function nextStep(id: WizardStepId): WizardStepId | null { + const i = stepIndex(id) + return i >= 0 && i < WIZARD_STEPS.length - 1 ? WIZARD_STEPS[i + 1]!.id : null +} + +export function prevStep(id: WizardStepId): WizardStepId | null { + const i = stepIndex(id) + return i > 0 ? WIZARD_STEPS[i - 1]!.id : null +} + +const SIGNAL_SCENARIOS = ['tpl-sps', 'tpl-pack'] + +export function activeSceneIds(navKinds: string[], scenarios: string[] = []): string[] { + const out: string[] = [] + for (const k of navKinds) { + const id = NAV_SCENE[k] ?? `scene.${k}` + if (!out.includes(id)) out.push(id) + } + if (scenarios.some((s) => SIGNAL_SCENARIOS.includes(s)) && !out.includes('scene.signal')) { + out.push('scene.signal') + } + if (out.length > 0 && !out.includes('scene.device')) out.push('scene.device') + return out +} + +export function vehicleChecklist(): WizardChecklistItem[] { + return VEHICLE_CHECKLIST +} + +export function mapChecklist(): WizardChecklistItem[] { + return MAP_CHECKLIST +} + +export function businessChecklist(scenarios: string[]): WizardChecklistItem[] { + const items: WizardChecklistItem[] = [] + const seen = new Set() + for (const id of scenarios) { + const list = SCENARIO_CHECKLIST[id] + if (!list) continue + for (const it of list) { + if (seen.has(it.key)) continue + seen.add(it.key) + items.push(it) + } + } + return items.length ? items : GENERIC_BUSINESS +} + +export function scenarioSetupHint(scenarios: string[]): string { + if (scenarios.includes('tpl-sps')) { + return '已选 SPS:进入设备管理,添加上下线机构,再配互锁站点、通讯参数和互锁点地址。' + } + if (scenarios.includes('tpl-pack')) { + return '已选 Pack 产线:在设备管理中对接工位设备,并配置互锁站点与通讯。' + } + if (scenarios.includes('tpl-loop')) { + return '已选环线:确认环线路径闭合,并按需配置交管参数。' + } + if (scenarios.includes('tpl-p2p')) { + return '已选点对点:明确取放货站点,必要时配置任务模板。' + } + return '未选具体场景时,可按现场需要接入设备,或直接完成向导。' +} diff --git a/frontends/apps/simple-platform-vue/src/views/LoginView.vue b/frontends/apps/simple-platform-vue/src/views/LoginView.vue index 5afe5ac..543a709 100644 --- a/frontends/apps/simple-platform-vue/src/views/LoginView.vue +++ b/frontends/apps/simple-platform-vue/src/views/LoginView.vue @@ -36,7 +36,7 @@
登 录
-
登录以进入智能调度平台
+
使用已分配权限的账号进入平台
@@ -46,73 +46,13 @@ - + - -
- - -
-
- - - -
- SimpleLite 启动模式 - - - - -
-
- - -
-
-
- 下次自动按当前 scope 进入 + 记住用户名 高级 ›
@@ -141,7 +81,7 @@
- 任意非空用户名 + 任意密码即可登录;scope 决定进入 admin / monitor 视图;权限按 Mock 表生成。 + 入口由账号权限决定:管理账号进入配置与调度,运营账号进入已授权的监控页。
@@ -158,9 +98,8 @@ import { reactive, ref, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import type { FormInstance, FormRules } from 'element-plus' import { ElMessage } from 'element-plus' -import { User, Lock, Setting, Monitor, Cpu, Connection, QuestionFilled } from '@element-plus/icons-vue' +import { User, Lock } from '@element-plus/icons-vue' import { useAuthStore } from '@/stores/auth' -import type { LaunchMode, Scope } from '@/types/auth' const route = useRoute() const router = useRouter() @@ -182,10 +121,6 @@ const adv = ref([]) const form = reactive({ username: 'admin', password: 'admin', - scope: 'Platform' as Scope, - // 会话 N+1:默认「本地+Web」与改造前 Configuration.displayMode="web+local" 一致, - // 保证「我啥也不选」时的行为与改造前完全相同。 - launchMode: 'DesktopAndWeb' as LaunchMode, remember: true }) @@ -198,14 +133,12 @@ const adv2 = reactive({ const rules: FormRules = { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], - password: [{ required: true, message: '请输入密码', trigger: 'blur' }], - scope: [{ required: true, message: '请选择 scope', trigger: 'change' }], - launchMode: [{ required: true, message: '请选择启动模式', trigger: 'change' }] + password: [{ required: true, message: '请输入密码', trigger: 'blur' }] } const year = computed(() => new Date().getFullYear()) -function postLoginTarget(needsWizard?: boolean): string { +function postLoginTarget(needsWizard?: boolean, scope?: string): string { if (needsWizard) return '/wizard' const raw = route.query.redirect const redirect = Array.isArray(raw) ? raw[0] : raw @@ -217,7 +150,7 @@ function postLoginTarget(needsWizard?: boolean): string { ) { return redirect } - return form.scope === 'Platform' ? '/admin/dashboard' : '/monitor/map' + return scope === 'RCSMonitor' ? '/monitor/map' : '/admin/dashboard' } async function submit() { @@ -232,23 +165,21 @@ async function submit() { const resp = await auth.login({ username: form.username, password: form.password, - scope: form.scope, - launchMode: form.launchMode + // Simple3 仅 Web 宿主,固定 WebOnly(--display-mode=web) + launchMode: 'WebOnly' }) ElMessage.success(`欢迎,${auth.user?.displayName ?? form.username}`) - // 会话 N+1:如果后端返回了 launchWarning(如「检测到既有 SimpleLite 在跑、本次启动模式未生效」), - // 在登录成功的 toast 之后再追加一条警告条,确保用户感知到「实际行为」与「期望」之间的偏差。 if (resp.launchWarning) { ElMessage({ message: resp.launchWarning, type: 'warning', duration: 6000, showClose: true }) } else if (resp.runMode === 'Detached') { ElMessage({ - message: 'SimpleLite 未启动(Platform.Server 单独运行),/api/sl/* 相关功能将不可用。', + message: 'Simple3 未启动,/api/sl/* 相关功能将不可用。', type: 'warning', duration: 6000, showClose: true }) } - await router.push(postLoginTarget(resp.needsWizard)) + await router.push(postLoginTarget(resp.needsWizard, resp.scope)) } catch (e) { const msg = e instanceof Error ? e.message : String(e) ElMessage.error(`登录失败:${msg}`) @@ -557,58 +488,6 @@ async function submit() { .glass-form :deep(.el-input__prefix-inner > :first-child), .glass-form :deep(.el-input__suffix-inner) { color: #756d85; } -/* scope 双卡 */ -.scope-item :deep(.el-form-item__content) { width: 100%; } -.scope-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; width: 100%; } - -/* 启动模式(会话 N+1):与 scope 双卡共用 .scope-tab 样式,仅在外层做一些标题/留白调整 */ -.launch-mode-item :deep(.el-form-item__content) { display: flex; flex-direction: column; align-items: stretch; } -.launch-mode-head { - display: flex; align-items: center; gap: 6px; - margin-bottom: 8px; - font-size: 12px; - color: #756d85; - letter-spacing: 1px; -} -.launch-mode-title { font-weight: 500; } -.launch-mode-help { - color: #a8a0c0; - cursor: help; - font-size: 14px; -} -.launch-mode-help:hover { color: #7543e8; } -.launch-wrap { margin-bottom: 4px; } -.launch-tip { max-width: 280px; line-height: 1.6; font-size: 12px; } -.launch-tip > div + div { margin-top: 6px; } -.scope-tab { - appearance: none; - background: #f6f3fb; - border: 1px solid rgba(40, 33, 58, 0.1); - border-radius: 12px; - padding: 14px 14px; - display: flex; align-items: center; gap: 12px; - color: #28213a; - cursor: pointer; - transition: all .2s; - text-align: left; -} -.scope-tab:hover { - background: #fbf9fd; - border-color: rgba(117, 67, 232, 0.3); - color: #28213a; - transform: translateY(-1px); -} -.scope-tab.active { - background: linear-gradient(135deg, #7543e8 0%, #9b7cff 100%); - border-color: transparent; - color: #fff; - box-shadow: 0 8px 20px rgba(117, 67, 232, 0.28); -} -.scope-tab .el-icon { font-size: 20px; } -.scope-meta { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; } -.scope-name { font-size: 14px; font-weight: 600; } -.scope-desc { font-size: 11px; opacity: 0.75; margin-top: 2px; } - /* 行 - 记住选择 & 高级 */ .row { display: flex; align-items: center; } .row-between { justify-content: space-between; margin: -6px 0 10px; } diff --git a/frontends/apps/simple-platform-vue/src/views/WizardView.vue b/frontends/apps/simple-platform-vue/src/views/WizardView.vue index 8441b54..5f0a278 100644 --- a/frontends/apps/simple-platform-vue/src/views/WizardView.vue +++ b/frontends/apps/simple-platform-vue/src/views/WizardView.vue @@ -11,77 +11,174 @@
平台配置向导
-
先选导航方式,再选业务场景与功能模块;保存后进入车辆与地图配置
+
{{ current.hint }}
{{ auth.user?.displayName ?? auth.user?.username ?? '' }}
+
    +
  1. + {{ s.index }} + {{ s.title }} +
  2. +
+
正在加载配置选项…
-
-
-
-
- 1 -

导航方式

至少选 1 项 -
-
- -
-
+
+ +
+
+

选择导航方式

至少选 1 项 +
+

按现场实际定位方式勾选,系统会据此加载对应内核场景插件。

+
+ +
+
-
-
- 2 -

业务场景

可多选,可暂不选 -
-
选「SPS 物料配送」或「电池 Pack 自动化产线」才会加载 signal 插件
-
- -
-
暂无场景模板,可跳过这一步
-
+ +
+
+

选择业务场景

可多选,也可暂不选 +
+

场景决定后面「业务配置」要做什么。例如 SPS 会引导你去设备管理添加上下线机构并配置互锁。

+
+ +
+
-
-
- 3 -

功能模块

可多选,可暂不选 -
-
- +
+
+ + +
+
+

车辆配置

+
+

必须添加车辆并补齐参数,否则地图监控与任务无法绑定实车。

+
    +
  1. + -
- -
+
+
{{ it.title }}
+
{{ it.detail }}
+
+ + + +
+ 前往车辆管理 + 必须添加车辆并补齐参数。配完后点顶栏「返回向导」,或勾选清单后点「下一步」。 +
+ + + +
+
+

地图配置

+
+

在画布上布置站点、绘制路径,并按导航方式填写站点功能参数。

+
    +
  1. + +
    +
    {{ it.title }}
    +
    {{ it.detail }}
    +
    + +
  2. +
+
+ 前往地图编辑 + 场景 / 路径表 +
+
+ + +
+
+

配置业务场景

+
+

{{ businessHint }}

+
    +
  1. + +
    +
    {{ it.title }}
    +
    {{ it.detail }}
    +
    + +
  2. +
+
+ 前往设备管理 +
+
@@ -89,9 +186,21 @@
退出登录
- 保存后进入车辆与地图配置,不选导航方式无法继续 - - 下一步:进入配置 + 上一步 + {{ nextCta }} + + 完成并进入平台
@@ -100,13 +209,31 @@