新增:首页工作流执行必选计费方式(重跑回显上次选择);修复:退出登录整页刷新解决重新登录后动态菜单404
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,15 @@ export function getPricingConfig(params: { subjectType: string; subjectId: strin
|
||||
});
|
||||
}
|
||||
|
||||
/** 计价对象可选计费方式(workflow/business 使用;model 计价方式由配置规则决定,不调用) */
|
||||
export function getChargeModes(params: { subjectType: string; subjectId: string }) {
|
||||
return request({
|
||||
url: '/shop-user-trade/pricing/controller/subjects/charge-modes',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增/更新计价配置(id > 0 更新,0 新增;rules 传费率对象;id 兼容雪花字符串避免精度丢失) */
|
||||
export function savePricingConfig(data: {
|
||||
id: string | number;
|
||||
|
||||
@@ -203,11 +203,12 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(async () => {
|
||||
.then(() => {
|
||||
// 手动退出登录也只清理登录态缓存,保留主题、语言等本地配置。
|
||||
Session.clearAuth();
|
||||
// 显式回到登录页,避免保留之前受保护页面的重定向参数
|
||||
await router.replace('/login');
|
||||
// 整页跳转登录页(刷新重建路由实例):SPA 内 router.replace 不会重置已注册的动态路由,
|
||||
// 重新登录时 setAddRoute 的 hasRoute 判断会跳过新菜单注册,出现"菜单显示但点击 404"(如价格管理页)
|
||||
window.location.href = import.meta.env.BASE_URL + '#/login';
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (path === 'wareHouse') {
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
|
||||
<div v-show="!collapsed" class="workflow-form-scroll">
|
||||
<el-form label-position="top" class="workflow-form">
|
||||
<!-- 计费方式:必选、不设置默认值;可选项来自计价对象 charge-modes 接口(workflow) -->
|
||||
<el-form-item label="计费方式" required>
|
||||
<el-select v-model="chargeMode" :disabled="isDisabled" placeholder="请选择计费方式" class="w100">
|
||||
<el-option v-for="opt in chargeModes" :key="opt.mode" :label="opt.name" :value="opt.mode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="detail?.nodeInputParams">
|
||||
<template v-for="node in detail.nodeInputParams" :key="node.id || node.nodeCode">
|
||||
<template v-if="hasFormConfig(node)">
|
||||
@@ -188,6 +194,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { ArrowDown, CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
|
||||
import PatchTemplateEditor from '/@/components/patchTemplate/PatchTemplateEditor.vue';
|
||||
import { uploadFile } from '/@/api/common/upload';
|
||||
import { getChargeModes } from '/@/api/trade/pricing';
|
||||
import { collectHomeFormFields } from '../utils/flowDsl';
|
||||
import type { WorkflowNodeStep } from '../utils/wsMessage';
|
||||
|
||||
@@ -209,7 +216,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'submit', payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates: any[] }): void;
|
||||
(e: 'submit', payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates: any[]; chargeMode: string }): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -229,6 +236,33 @@ const formFileNames = reactive<Record<string, string | string[]>>({});
|
||||
const uploadingFields = reactive<Record<string, boolean>>({});
|
||||
const templates = ref<any[]>([]);
|
||||
|
||||
// ===== 计费方式:必选、不设置默认值(新建为空强制选择;回显重跑恢复上次选择) =====
|
||||
interface ChargeModeOption {
|
||||
mode: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const chargeMode = ref('');
|
||||
const chargeModes = ref<ChargeModeOption[]>([]);
|
||||
/** 计费方式可选项缓存:workflow 计价对象固定(subjectType/subjectId 均 workflow),模块级共享避免每张卡片重复请求 */
|
||||
let workflowChargeModesCache: ChargeModeOption[] | null = null;
|
||||
|
||||
const loadChargeModes = async () => {
|
||||
if (workflowChargeModesCache) {
|
||||
chargeModes.value = workflowChargeModesCache;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res: any = await getChargeModes({ subjectType: 'workflow', subjectId: 'workflow' });
|
||||
const list: ChargeModeOption[] = res?.data?.chargeModes || [];
|
||||
chargeModes.value = list;
|
||||
workflowChargeModesCache = list;
|
||||
} catch {
|
||||
// 计费方式拉取失败:全局拦截器已提示,下拉留空(必填校验会拦截提交)
|
||||
chargeModes.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 状态派生:失败 = 携带 formError;禁用编辑 = 定格(done/failed)卡片默认只读,
|
||||
// 但最后一张已执行卡片(editable)允许改参;执行中(submitting)一律禁用
|
||||
const isFailed = computed(() => !!props.formError);
|
||||
@@ -435,6 +469,11 @@ const hasFormFields = computed(() => {
|
||||
});
|
||||
|
||||
const validateFormFields = (): boolean => {
|
||||
// 计费方式必选、无默认值:未选择时阻止提交(含无表单参数的工作流)
|
||||
if (!chargeMode.value) {
|
||||
ElMessage.warning('请选择计费方式');
|
||||
return false;
|
||||
}
|
||||
if (!props.detail?.nodeInputParams) return true;
|
||||
for (const node of props.detail.nodeInputParams as any[]) {
|
||||
if (String(node?.nodeCode || '').toLowerCase() !== '__start__') continue;
|
||||
@@ -462,6 +501,10 @@ watch(
|
||||
Object.keys(fieldFiles).forEach((key) => delete fieldFiles[key]);
|
||||
Object.keys(formFileNames).forEach((key) => delete formFileNames[key]);
|
||||
Object.keys(uploadingFields).forEach((key) => delete uploadingFields[key]);
|
||||
// 计费方式:新建(handleWorkflowSelect 已清空后端自带 chargeMode)为空强制选择;
|
||||
// 回显重跑卡片从 flowContent.chargeMode 恢复上次选择
|
||||
chargeMode.value = detail?.flowContent?.chargeMode || '';
|
||||
loadChargeModes();
|
||||
// 尝试从执行详情恢复贴片模板
|
||||
// 提交路径:templates 写入开启贴片布局的节点内部(patchLayout === true 的 node.templates),顶层已删除;
|
||||
// 回显时 detail.nodeInputParams 即提交时的 nodes,从该处优先恢复;兼容旧数据(extension / detail / flowContent 顶层)
|
||||
@@ -543,6 +586,7 @@ const handleSubmit = () => {
|
||||
formValues: JSON.parse(JSON.stringify(formValues)),
|
||||
formFileNames: JSON.parse(JSON.stringify(formFileNames)),
|
||||
templates: JSON.parse(JSON.stringify(templates.value || [])),
|
||||
chargeMode: chargeMode.value,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -690,6 +690,8 @@ const handleWorkflowSelect = async (workflowId: string | null, isTemplate?: bool
|
||||
removeDraftFormCard();
|
||||
const res = await getWorkflowDetail(workflowId);
|
||||
selectedWorkflowDetail.value = res.data || null;
|
||||
// 新建表单不沿用后端工作流详情自带的 chargeMode(如缺省 per_item):计费方式必选且无默认值,强制用户选择
|
||||
if (res.data?.flowContent) delete res.data.flowContent.chargeMode;
|
||||
// 对话卡片化:选中工作流即推送一张可填表单卡片进入消息流
|
||||
if (res.data) pushFormCard(res.data);
|
||||
} catch {
|
||||
@@ -1042,7 +1044,7 @@ const runWorkflow = async (
|
||||
sid: string,
|
||||
sessionId: string,
|
||||
detail: any,
|
||||
opts: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[] },
|
||||
opts: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[]; chargeMode?: string },
|
||||
formMsg: ChatMessage
|
||||
) => {
|
||||
const curSession = historyList.value.find((h) => h.id === sid);
|
||||
@@ -1119,6 +1121,8 @@ const runWorkflow = async (
|
||||
// 构建 flowContent:templates 不再放最外层(删除 detail.flowContent 可能遗留的顶层 templates,统一走节点)
|
||||
const updatedFlowContent: any = { ...detail.flowContent, nodes: nodeInputParams };
|
||||
delete updatedFlowContent.templates;
|
||||
// 计费方式写入 flowContent(与 version 平级),后端按此计费;表单必填已拦截未选,此处仅兜底
|
||||
if (opts.chargeMode) updatedFlowContent.chargeMode = opts.chargeMode;
|
||||
|
||||
// 3. 工作流模型由后端按 flow 配置决定,无需前端取模型 id(普通对话取模型见 runChat)
|
||||
|
||||
@@ -1247,7 +1251,7 @@ const snapshotTemplatesToMsg = (msg: ChatMessage, templates: any[]) => {
|
||||
|
||||
const startWorkflowFromCard = async (
|
||||
msg: ChatMessage,
|
||||
payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[] }
|
||||
payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates?: any[]; chargeMode?: string }
|
||||
) => {
|
||||
// 定位卡片所在会话
|
||||
let sid = '';
|
||||
@@ -1269,6 +1273,12 @@ const startWorkflowFromCard = async (
|
||||
if (Array.isArray(payload.templates)) {
|
||||
snapshotTemplatesToMsg(msg, payload.templates);
|
||||
}
|
||||
// 计费方式写回消息对象 flowContent(快照):会话认领(virtual→UUID 触发 MainContent 重建)/历史回显重挂载时,
|
||||
// WorkflowFormCard 从 flowContent.chargeMode 恢复本次选择,避免重挂载后计费方式被清空
|
||||
if (payload.chargeMode) {
|
||||
if (!msg.form.flowContent || typeof msg.form.flowContent !== 'object') msg.form.flowContent = {};
|
||||
msg.form.flowContent.chargeMode = payload.chargeMode;
|
||||
}
|
||||
|
||||
// 标记本轮发送中:工作流执行期间 InputBar 显示停止按钮(isGenerating 依赖 sendingSessions)
|
||||
sendingSessions[sid] = true;
|
||||
|
||||
Reference in New Issue
Block a user