@yugu-ui/flow-ui 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { default as FlowConfig } from './config/FlowConfig';
2
2
  export { default as FlowConfigField } from './config/FlowConfigField';
3
3
  export { default as FlowConfigMore } from './config/FlowConfigMore';
4
4
  export { default as FlowLayout } from './timeline/FlowLayout';
5
+ export { default as FlowNodesPanel } from './timeline/FlowNodesPanel';
6
+ export type { FlowTimelineData, FlowStatusTag } from './timeline/timeline-mapping';
5
7
  export { default as FlowNode } from './timeline/FlowNode';
6
8
  export { default as FlowAvatar } from './timeline/FlowAvatar';
7
9
  export { default as ApprovalModal } from './timeline/ApprovalModal';
package/dist/index.js CHANGED
@@ -5959,8 +5959,254 @@ var ApprovalModal_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
5959
5959
  }
5960
5960
  }), [["__scopeId", "data-v-fb3bd9c6"]]);
5961
5961
  //#endregion
5962
+ //#region src/base-ui/base-card-item.vue?vue&type=script&setup=true&lang.ts
5963
+ var _hoisted_1$5 = { class: "header" };
5964
+ var _hoisted_2$3 = { class: "header-left" };
5965
+ var _hoisted_3$2 = { class: "title" };
5966
+ var _hoisted_4$2 = {
5967
+ key: 0,
5968
+ class: "subtitle"
5969
+ };
5970
+ var _hoisted_5$1 = { class: "header-right" };
5971
+ //#endregion
5972
+ //#region src/base-ui/base-card-item.vue
5973
+ var base_card_item_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
5974
+ name: "BaseCardItem",
5975
+ inheritAttrs: false,
5976
+ __name: "base-card-item",
5977
+ props: {
5978
+ title: { default: "" },
5979
+ subtitle: { default: "" },
5980
+ contentPadding: { default: 16 }
5981
+ },
5982
+ setup(__props) {
5983
+ const props = __props;
5984
+ const slots = useSlots();
5985
+ const contentStyle = computed(() => ({ padding: typeof props.contentPadding === "number" ? `${props.contentPadding}px` : props.contentPadding }));
5986
+ const showHeader = computed(() => {
5987
+ return !!props.title || !!props.subtitle || !!slots["subtitle"] || !!slots["header-extra"];
5988
+ });
5989
+ return (_ctx, _cache) => {
5990
+ return openBlock(), createBlock(unref(NCard), mergeProps({
5991
+ class: "base-card-item base-card-item-spacing",
5992
+ "content-class": "base-card-item-content",
5993
+ "header-class": "base-card-item-header",
5994
+ "content-style": contentStyle.value
5995
+ }, _ctx.$attrs), createSlots({
5996
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, void 0, true)]),
5997
+ _: 2
5998
+ }, [showHeader.value ? {
5999
+ name: "header",
6000
+ fn: withCtx(() => [createElementVNode("div", _hoisted_1$5, [createElementVNode("div", _hoisted_2$3, [createElementVNode("span", _hoisted_3$2, toDisplayString(__props.title), 1), renderSlot(_ctx.$slots, "subtitle", {}, () => [__props.subtitle ? (openBlock(), createElementBlock("span", _hoisted_4$2, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)], true)]), createElementVNode("div", _hoisted_5$1, [renderSlot(_ctx.$slots, "header-extra", {}, void 0, true)])])]),
6001
+ key: "0"
6002
+ } : void 0]), 1040, ["content-style"]);
6003
+ };
6004
+ }
6005
+ }), [["__scopeId", "data-v-e9115957"]]);
6006
+ //#endregion
6007
+ //#region src/timeline/timeline-mapping.ts
6008
+ /** 固定尾节点:节点有数据时渲染追加(display 下状态按流程是否走完覆盖),接口返回与提交数据均不包含 */
6009
+ var END_NODE = {
6010
+ type: "end",
6011
+ name: "结束",
6012
+ desc: "流程结束",
6013
+ status: "pending",
6014
+ users: []
6015
+ };
6016
+ /** 人员处理记录映射:一行 records 对应一条展示记录(finished=0 未完成行过滤)——result 优先:result=2 同意;result=3 或 is_reject=1 驳回行按记录操作人判定方向(record.user_id=挂载人员 id 主动驳回「驳回到 rejecters.name」、否则被驳回「被 user_name 驳回」、名字均缺且 flowRejected=true 展示「驳回到 发起人」、否则「驳回」);result=0 不展示 */
6017
+ function toUserRecords(user, flowRejected) {
6018
+ const toFiles = (files) => files?.length ? files.map((file) => ({
6019
+ name: file.original_filename || file.file_name || "附件",
6020
+ url: file.url
6021
+ })) : void 0;
6022
+ const rows = [];
6023
+ (Array.isArray(user.records) ? user.records : []).forEach((record) => {
6024
+ if (record.finished === 0) return;
6025
+ const isSelf = record.user_id != null && String(record.user_id) === String(user.id);
6026
+ const rejectTo = isSelf ? record.rejecters?.name : void 0;
6027
+ const rejectBy = isSelf ? void 0 : record.user_name;
6028
+ const base = {
6029
+ time: record.assertion_time ? formatTime(record.assertion_time, "YYYY-MM-DD HH:mm") : void 0,
6030
+ opinion: record.suggest || void 0,
6031
+ attachments: toFiles(record.attachments)
6032
+ };
6033
+ if (record.result === 2) {
6034
+ rows.push({
6035
+ type: "approve",
6036
+ ...base
6037
+ });
6038
+ return;
6039
+ }
6040
+ if (record.is_reject !== 1 && record.result !== 3) return;
6041
+ rows.push({
6042
+ type: "reject",
6043
+ ...base,
6044
+ rejectNode: rejectTo || (!rejectTo && !rejectBy && flowRejected ? "发起人" : void 0),
6045
+ rejectBy
6046
+ });
6047
+ });
6048
+ return rows;
6049
+ }
6050
+ /** 已处理记录(result=2/3 或 is_reject=1,与 toUserRecords 口径一致)中处理时间(assertion_time 毫秒时间戳)最新的一条是否为驳回(result=3 或 is_reject=1);空集合返回 false,records 数组顺序不可靠必须按时间数值比较 */
6051
+ function isLatestRowsReject(rows) {
6052
+ if (!rows.length) return false;
6053
+ const latest = rows.reduce((acc, cur) => (cur.assertion_time ?? 0) >= (acc.assertion_time ?? 0) ? cur : acc, rows[0]);
6054
+ return latest.result === 3 || latest.is_reject === 1;
6055
+ }
6056
+ /** display 模式审批时间线映射:节点快照直接映射 FlowNodeItem——发起人节点独立判定:流程被驳回到发起人(finished=1 且 status=-1)标红、其余一律完成绿色;其余节点按实例 finished、节点自带 finished(1=已完成)推导:进行中节点优先取第一个「未完成且 status=1 待处理」的节点,无激活节点时兜底认第一个存在待处理任务行(users.records 中 result=0 且 finished=0)的节点为当前节点,仍无则回退第一个未完成节点,有激活/兜底节点时其后节点一律待开始(剥离上一轮残留),流程被驳回终止时其余节点一律灰色待开始;人员状态按本人最新一条操作记录展示(已处理记录 result=2/3 或 is_reject=1 中 assertion_time 最新一条:驳回标红、同意标绿),无操作记录随节点状态(进行中节点淡黄、其余灰色未处理) */
6057
+ function mapTimelineNodes(data) {
6058
+ const d = data;
6059
+ if (!d?.nodes?.length) return [];
6060
+ const instanceFinished = d.finished === 1;
6061
+ const instanceRejected = instanceFinished && d.status === -1;
6062
+ const activeIndex = d.nodes.findIndex((item) => item.status === 1 && item.finished !== 1);
6063
+ const pendingIndex = !instanceFinished && activeIndex < 0 ? d.nodes.findIndex((item) => item.users.some((user) => (Array.isArray(user.records) ? user.records : []).some((record) => record.result === 0 && record.finished === 0))) : -1;
6064
+ const currentIndex = activeIndex > -1 ? activeIndex : pendingIndex > -1 ? pendingIndex : instanceFinished ? -1 : d.nodes.findIndex((item) => item.finished !== 1);
6065
+ return d.nodes.map((node, index) => {
6066
+ const forcePending = !instanceFinished && (activeIndex > -1 || pendingIndex > -1) && index > currentIndex;
6067
+ const nodeStatus = node.type === "start" ? instanceRejected ? "rejected" : "done" : instanceRejected ? "pending" : forcePending ? "pending" : !instanceFinished && index === currentIndex ? "doing" : instanceFinished || node.finished === 1 || currentIndex > -1 && index < currentIndex ? "done" : "pending";
6068
+ const users = node.users.map((user) => {
6069
+ const records = toUserRecords(user, instanceRejected);
6070
+ const rawRows = Array.isArray(user.records) ? user.records : [];
6071
+ const processedRows = rawRows.filter((record) => record.result === 2 || record.result === 3 || record.is_reject === 1);
6072
+ const status = processedRows.length ? isLatestRowsReject(processedRows) ? "rejected" : "done" : nodeStatus === "doing" ? "doing" : nodeStatus === "done" && node.mode === "orsign" ? "pending" : nodeStatus;
6073
+ return {
6074
+ id: user.id,
6075
+ name: user.name,
6076
+ status,
6077
+ pendingTask: rawRows.some((record) => record.result === 0 && record.finished === 0),
6078
+ ...records.length ? { records } : {}
6079
+ };
6080
+ });
6081
+ return {
6082
+ type: node.type,
6083
+ id: node.node_id,
6084
+ status: nodeStatus,
6085
+ name: node.name,
6086
+ desc: node.desc ?? void 0,
6087
+ time: node.created_time && (node.finished === 1 || nodeStatus === "done" || nodeStatus === "rejected") ? formatTime(node.created_time, "YYYY-MM-DD HH:mm") : void 0,
6088
+ mode: node.mode,
6089
+ selfSelect: node.selfSelect,
6090
+ users
6091
+ };
6092
+ });
6093
+ }
6094
+ /** 组装完整节点链:有数据统一追加 END 结束节点,无数据为空数组走空态;display(非 form)流程全部完成时 END 状态同步 done;末节点已是 end(后端快照自带 end、或调用方传入已组装过 END 的节点链,如 FlowLayout → FlowNodesPanel)时幂等跳过,防止结束节点重复渲染 */
6095
+ function withEndNode(baseNodes, mode) {
6096
+ if (!baseNodes.length) return [];
6097
+ if (baseNodes[baseNodes.length - 1].type === "end") return baseNodes;
6098
+ const allDone = mode !== "form" && baseNodes.every((node) => node.status === "done");
6099
+ return [...baseNodes, {
6100
+ ...END_NODE,
6101
+ status: allDone ? "done" : "pending"
6102
+ }];
6103
+ }
6104
+ /** 折叠面板标题流程状态标签:仅 display 模式且有详情数据时推导——finished=1 且 status=-1 已拒绝(驳回到发起人终止)、finished=1 已同意、未结束进行中;form 发起模式无实例状态不展示,数据未返回前隐藏避免先展示「进行中」再跳变 */
6105
+ function getFlowStatusTag(mode, data) {
6106
+ if (mode === "form" || !data) return void 0;
6107
+ if (data.finished === 1) return data.status === -1 ? {
6108
+ text: "已拒绝",
6109
+ type: "error"
6110
+ } : {
6111
+ text: "已同意",
6112
+ type: "success"
6113
+ };
6114
+ return {
6115
+ text: "进行中",
6116
+ type: "warning"
6117
+ };
6118
+ }
6119
+ //#endregion
6120
+ //#region src/timeline/FlowNodesPanel.vue?vue&type=script&setup=true&lang.ts
6121
+ var _hoisted_1$4 = { class: "flow-node__title" };
6122
+ var _hoisted_2$2 = { class: "flow-node__list" };
6123
+ //#endregion
6124
+ //#region src/timeline/FlowNodesPanel.vue
6125
+ var FlowNodesPanel_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
6126
+ name: "FlowNodesPanel",
6127
+ __name: "FlowNodesPanel",
6128
+ props: /* @__PURE__ */ mergeModels({
6129
+ data: {},
6130
+ nodes: {},
6131
+ mode: {},
6132
+ loading: {
6133
+ type: Boolean,
6134
+ default: false
6135
+ },
6136
+ title: { default: "审批流程" },
6137
+ statusTag: {}
6138
+ }, {
6139
+ "selectedUsersMap": { default: () => ({}) },
6140
+ "selectedUsersMapModifiers": {}
6141
+ }),
6142
+ emits: ["update:selectedUsersMap"],
6143
+ setup(__props) {
6144
+ const props = __props;
6145
+ /** 自选审批人节点已选人员(key=节点名) */
6146
+ const selectedUsersMap = useModel(__props, "selectedUsersMap");
6147
+ /** 面板渲染节点链:有数据时追加 END 结束节点,无数据为空数组走空态 */
6148
+ const panelNodes = computed(() => {
6149
+ if (props.mode === "form") return withEndNode(props.nodes ?? [], "form");
6150
+ return withEndNode(props.data ? mapTimelineNodes(props.data) : props.nodes ?? [], "display");
6151
+ });
6152
+ /** 折叠面板标题流程状态标签 */
6153
+ const panelStatusTag = computed(() => props.data ? getFlowStatusTag(props.mode, props.data) : props.statusTag);
6154
+ return (_ctx, _cache) => {
6155
+ const _component_NTag = NTag;
6156
+ const _component_NEmpty = NEmpty;
6157
+ const _component_NSpin = NSpin;
6158
+ const _component_NCollapseItem = NCollapseItem;
6159
+ const _component_NCollapse = NCollapse;
6160
+ return openBlock(), createBlock(base_card_item_default, { "content-padding": 0 }, {
6161
+ default: withCtx(() => [createVNode(_component_NCollapse, {
6162
+ class: "flow-node",
6163
+ "arrow-placement": "right",
6164
+ "default-expanded-names": ["flow"]
6165
+ }, {
6166
+ default: withCtx(() => [createVNode(_component_NCollapseItem, { name: "flow" }, {
6167
+ header: withCtx(() => [createElementVNode("span", _hoisted_1$4, [createTextVNode(toDisplayString(__props.title) + " ", 1), panelStatusTag.value ? (openBlock(), createBlock(_component_NTag, {
6168
+ key: 0,
6169
+ type: panelStatusTag.value.type,
6170
+ size: "small"
6171
+ }, {
6172
+ default: withCtx(() => [createTextVNode(toDisplayString(panelStatusTag.value.text), 1)]),
6173
+ _: 1
6174
+ }, 8, ["type"])) : createCommentVNode("", true)])]),
6175
+ default: withCtx(() => [createVNode(_component_NSpin, {
6176
+ show: __props.loading,
6177
+ class: "w-full"
6178
+ }, {
6179
+ default: withCtx(() => [createElementVNode("div", _hoisted_2$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(panelNodes.value, (node) => {
6180
+ return openBlock(), createBlock(FlowNode_default, {
6181
+ key: node.id ?? node.name,
6182
+ "selected-users": selectedUsersMap.value[node.name],
6183
+ "onUpdate:selectedUsers": ($event) => selectedUsersMap.value[node.name] = $event,
6184
+ node,
6185
+ mode: __props.mode
6186
+ }, null, 8, [
6187
+ "selected-users",
6188
+ "onUpdate:selectedUsers",
6189
+ "node",
6190
+ "mode"
6191
+ ]);
6192
+ }), 128)), !panelNodes.value.length ? (openBlock(), createBlock(_component_NEmpty, {
6193
+ key: 0,
6194
+ description: __props.mode === "form" ? "必填字段填写完整后展示流程节点" : "暂无流程节点"
6195
+ }, null, 8, ["description"])) : createCommentVNode("", true)])]),
6196
+ _: 1
6197
+ }, 8, ["show"])]),
6198
+ _: 1
6199
+ })]),
6200
+ _: 1
6201
+ })]),
6202
+ _: 1
6203
+ });
6204
+ };
6205
+ }
6206
+ }), [["__scopeId", "data-v-25e5146e"]]);
6207
+ //#endregion
5962
6208
  //#region src/base-ui/yg-confirm.vue?vue&type=script&setup=true&lang.ts
5963
- var _hoisted_1$5 = { class: "yg-confirm" };
6209
+ var _hoisted_1$3 = { class: "yg-confirm" };
5964
6210
  //#endregion
5965
6211
  //#region src/base-ui/yg-confirm.vue
5966
6212
  var yg_confirm_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -6048,7 +6294,7 @@ var yg_confirm_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6048
6294
  }
6049
6295
  });
6050
6296
  return (_ctx, _cache) => {
6051
- return openBlock(), createElementBlock("div", _hoisted_1$5, [__props.triggerType === "button" ? (openBlock(), createBlock(unref(NButton), mergeProps({ key: 0 }, buttonProps.value, { onClick: handleOpen }), createSlots({
6297
+ return openBlock(), createElementBlock("div", _hoisted_1$3, [__props.triggerType === "button" ? (openBlock(), createBlock(unref(NButton), mergeProps({ key: 0 }, buttonProps.value, { onClick: handleOpen }), createSlots({
6052
6298
  default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, void 0, true)]),
6053
6299
  _: 2
6054
6300
  }, [_ctx.$slots.icon ? {
@@ -6092,7 +6338,7 @@ var yg_confirm_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6092
6338
  }), [["__scopeId", "data-v-a65cb6d5"]]);
6093
6339
  //#endregion
6094
6340
  //#region src/base-ui/base-card.vue?vue&type=script&setup=true&lang.ts
6095
- var _hoisted_1$4 = {
6341
+ var _hoisted_1$2 = {
6096
6342
  key: 0,
6097
6343
  class: "base-card-footer"
6098
6344
  };
@@ -6164,7 +6410,7 @@ var base_card_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
6164
6410
  }, {
6165
6411
  default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, void 0, true)]),
6166
6412
  _: 3
6167
- }, 8, ["show"]), showFooter.value ? (openBlock(), createElementBlock("div", _hoisted_1$4, [renderSlot(_ctx.$slots, "footer", {}, () => [createVNode(unref(NSpace), { size: 12 }, {
6413
+ }, 8, ["show"]), showFooter.value ? (openBlock(), createElementBlock("div", _hoisted_1$2, [renderSlot(_ctx.$slots, "footer", {}, () => [createVNode(unref(NSpace), { size: 12 }, {
6168
6414
  default: withCtx(() => [
6169
6415
  renderSlot(_ctx.$slots, "footer-extra", {}, void 0, true),
6170
6416
  __props.cancelConfig?.show && __props.cancelConfig?.doubleConfirm ? (openBlock(), createBlock(yg_confirm_default, {
@@ -6221,51 +6467,6 @@ var base_card_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
6221
6467
  }
6222
6468
  }), [["__scopeId", "data-v-956107d8"]]);
6223
6469
  //#endregion
6224
- //#region src/base-ui/base-card-item.vue?vue&type=script&setup=true&lang.ts
6225
- var _hoisted_1$3 = { class: "header" };
6226
- var _hoisted_2$3 = { class: "header-left" };
6227
- var _hoisted_3$2 = { class: "title" };
6228
- var _hoisted_4$2 = {
6229
- key: 0,
6230
- class: "subtitle"
6231
- };
6232
- var _hoisted_5$1 = { class: "header-right" };
6233
- //#endregion
6234
- //#region src/base-ui/base-card-item.vue
6235
- var base_card_item_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
6236
- name: "BaseCardItem",
6237
- inheritAttrs: false,
6238
- __name: "base-card-item",
6239
- props: {
6240
- title: { default: "" },
6241
- subtitle: { default: "" },
6242
- contentPadding: { default: 16 }
6243
- },
6244
- setup(__props) {
6245
- const props = __props;
6246
- const slots = useSlots();
6247
- const contentStyle = computed(() => ({ padding: typeof props.contentPadding === "number" ? `${props.contentPadding}px` : props.contentPadding }));
6248
- const showHeader = computed(() => {
6249
- return !!props.title || !!props.subtitle || !!slots["subtitle"] || !!slots["header-extra"];
6250
- });
6251
- return (_ctx, _cache) => {
6252
- return openBlock(), createBlock(unref(NCard), mergeProps({
6253
- class: "base-card-item base-card-item-spacing",
6254
- "content-class": "base-card-item-content",
6255
- "header-class": "base-card-item-header",
6256
- "content-style": contentStyle.value
6257
- }, _ctx.$attrs), createSlots({
6258
- default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, void 0, true)]),
6259
- _: 2
6260
- }, [showHeader.value ? {
6261
- name: "header",
6262
- fn: withCtx(() => [createElementVNode("div", _hoisted_1$3, [createElementVNode("div", _hoisted_2$3, [createElementVNode("span", _hoisted_3$2, toDisplayString(__props.title), 1), renderSlot(_ctx.$slots, "subtitle", {}, () => [__props.subtitle ? (openBlock(), createElementBlock("span", _hoisted_4$2, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)], true)]), createElementVNode("div", _hoisted_5$1, [renderSlot(_ctx.$slots, "header-extra", {}, void 0, true)])])]),
6263
- key: "0"
6264
- } : void 0]), 1040, ["content-style"]);
6265
- };
6266
- }
6267
- }), [["__scopeId", "data-v-e9115957"]]);
6268
- //#endregion
6269
6470
  //#region src/adapter/wflow.ts
6270
6471
  /** 获取流程配置详情(GET /wflow/config/{config_id}) */
6271
6472
  function fetchGetWflowConfigDetail(configId) {
@@ -6307,12 +6508,8 @@ function fetchWflowUndoReject(data) {
6307
6508
  });
6308
6509
  }
6309
6510
  //#endregion
6310
- //#region src/timeline/FlowLayout.vue?vue&type=script&setup=true&lang.ts
6311
- var _hoisted_1$2 = { class: "flow-node__title" };
6312
- var _hoisted_2$2 = { class: "flow-node__list" };
6313
- //#endregion
6314
6511
  //#region src/timeline/FlowLayout.vue
6315
- var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
6512
+ var FlowLayout_default = /* @__PURE__ */ defineComponent({
6316
6513
  name: "FlowLayout",
6317
6514
  __name: "FlowLayout",
6318
6515
  props: {
@@ -6338,20 +6535,7 @@ var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6338
6535
  /** 是否展示驳回操作:仅 display 模式且 undo_finished=0(未处理);form 发起模式无驳回 */
6339
6536
  const canReject = computed(() => props.mode !== "form" && props.timelineData?.undo_finished === 0);
6340
6537
  /** 折叠面板标题流程状态标签:仅 display 模式展示,颜色映射 已同意=success / 已拒绝=error / 进行中=warning */
6341
- const flowStatusTag = computed(() => {
6342
- if (props.mode === "form" || !props.timelineData) return void 0;
6343
- if (props.timelineData.finished === 1) return props.timelineData.status === -1 ? {
6344
- text: "已拒绝",
6345
- type: "error"
6346
- } : {
6347
- text: "已同意",
6348
- type: "success"
6349
- };
6350
- return {
6351
- text: "进行中",
6352
- type: "warning"
6353
- };
6354
- });
6538
+ const flowStatusTag = computed(() => getFlowStatusTag(props.mode, props.timelineData));
6355
6539
  /** 关闭:通知消费页(display=关闭详情页,form=取消发起);页签关闭逻辑由消费页承担 */
6356
6540
  function handleClose() {
6357
6541
  emit("close");
@@ -6452,14 +6636,6 @@ var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6452
6636
  }
6453
6637
  emit("success");
6454
6638
  }
6455
- /** 固定尾节点:form/display 模式节点有数据时渲染追加(display 下状态按流程是否走完覆盖),接口返回与提交数据均不包含 */
6456
- const END_NODE = {
6457
- type: "end",
6458
- name: "结束",
6459
- desc: "流程结束",
6460
- status: "pending",
6461
- users: []
6462
- };
6463
6639
  /** form 模式节点链:条件字段全部填写后经 fetchFlowNodes 请求所得(不同条件值得到不同节点),为空时节点区展示空态提示;不含 end 结束节点 */
6464
6640
  const formNodes = ref([]);
6465
6641
  /** form 模式节点请求中:节点区 NSpin loading(仅节点接口请求期间为 true,display 模式恒为 false) */
@@ -6509,100 +6685,11 @@ var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6509
6685
  onMounted(() => {
6510
6686
  flowConfigReady = initFlowConfig();
6511
6687
  });
6512
- /** 人员处理记录映射:一行 records 对应一条展示记录(finished=0 未完成行过滤)——result 优先:result=2 同意;result=3 或 is_reject=1 驳回行按记录操作人判定方向(record.user_id=挂载人员 id 主动驳回「驳回到 rejecters.name」、否则被驳回「被 user_name 驳回」、名字均缺且 flowRejected=true 展示「驳回到 发起人」、否则「驳回」);result=0 不展示 */
6513
- function toUserRecords(user, flowRejected) {
6514
- const toFiles = (files) => files?.length ? files.map((file) => ({
6515
- name: file.original_filename || file.file_name || "附件",
6516
- url: file.url
6517
- })) : void 0;
6518
- const rows = [];
6519
- (Array.isArray(user.records) ? user.records : []).forEach((record) => {
6520
- if (record.finished === 0) return;
6521
- const isSelf = record.user_id != null && String(record.user_id) === String(user.id);
6522
- const rejectTo = isSelf ? record.rejecters?.name : void 0;
6523
- const rejectBy = isSelf ? void 0 : record.user_name;
6524
- const base = {
6525
- time: record.assertion_time ? formatTime(record.assertion_time, "YYYY-MM-DD HH:mm") : void 0,
6526
- opinion: record.suggest || void 0,
6527
- attachments: toFiles(record.attachments)
6528
- };
6529
- if (record.result === 2) {
6530
- rows.push({
6531
- type: "approve",
6532
- ...base
6533
- });
6534
- return;
6535
- }
6536
- if (record.is_reject !== 1 && record.result !== 3) return;
6537
- rows.push({
6538
- type: "reject",
6539
- ...base,
6540
- rejectNode: rejectTo || (!rejectTo && !rejectBy && flowRejected ? "发起人" : void 0),
6541
- rejectBy
6542
- });
6543
- });
6544
- return rows;
6545
- }
6546
- /** 已处理记录(result=2/3 或 is_reject=1,与 toUserRecords 口径一致)中处理时间(assertion_time 毫秒时间戳)最新的一条是否为驳回(result=3 或 is_reject=1);空集合返回 false,records 数组顺序不可靠必须按时间数值比较 */
6547
- function isLatestRowsReject(rows) {
6548
- if (!rows.length) return false;
6549
- const latest = rows.reduce((acc, cur) => (cur.assertion_time ?? 0) >= (acc.assertion_time ?? 0) ? cur : acc, rows[0]);
6550
- return latest.result === 3 || latest.is_reject === 1;
6551
- }
6552
- /** display 模式审批时间线:节点快照直接映射 FlowNodeItem——发起人节点独立判定:流程被驳回到发起人(finished=1 且 status=-1)标红、其余一律完成绿色;其余节点按实例 finished、节点自带 finished(1=已完成)推导:进行中节点优先取第一个「未完成且 status=1 待处理」的节点,无激活节点时兜底认第一个存在待处理任务行(users.records 中 result=0 且 finished=0)的节点为当前节点,仍无则回退第一个未完成节点,有激活/兜底节点时其后节点一律待开始(剥离上一轮残留),流程被驳回终止时其余节点一律灰色待开始;人员状态按本人最新一条操作记录展示(已处理记录 result=2/3 或 is_reject=1 中 assertion_time 最新一条:驳回标红、同意标绿),无操作记录随节点状态(进行中节点淡黄、其余灰色未处理) */
6553
- const displayNodes = computed(() => {
6554
- const d = props.timelineData;
6555
- if (!d?.nodes?.length) return [];
6556
- const instanceFinished = d.finished === 1;
6557
- const instanceRejected = instanceFinished && d.status === -1;
6558
- const activeIndex = d.nodes.findIndex((item) => item.status === 1 && item.finished !== 1);
6559
- const pendingIndex = !instanceFinished && activeIndex < 0 ? d.nodes.findIndex((item) => item.users.some((user) => (Array.isArray(user.records) ? user.records : []).some((record) => record.result === 0 && record.finished === 0))) : -1;
6560
- const currentIndex = activeIndex > -1 ? activeIndex : pendingIndex > -1 ? pendingIndex : instanceFinished ? -1 : d.nodes.findIndex((item) => item.finished !== 1);
6561
- return d.nodes.map((node, index) => {
6562
- const forcePending = !instanceFinished && (activeIndex > -1 || pendingIndex > -1) && index > currentIndex;
6563
- const nodeStatus = node.type === "start" ? instanceRejected ? "rejected" : "done" : instanceRejected ? "pending" : forcePending ? "pending" : !instanceFinished && index === currentIndex ? "doing" : instanceFinished || node.finished === 1 || currentIndex > -1 && index < currentIndex ? "done" : "pending";
6564
- const users = node.users.map((user) => {
6565
- const records = toUserRecords(user, instanceRejected);
6566
- const rawRows = Array.isArray(user.records) ? user.records : [];
6567
- const processedRows = rawRows.filter((record) => record.result === 2 || record.result === 3 || record.is_reject === 1);
6568
- const status = processedRows.length ? isLatestRowsReject(processedRows) ? "rejected" : "done" : nodeStatus === "doing" ? "doing" : nodeStatus === "done" && node.mode === "orsign" ? "pending" : nodeStatus;
6569
- return {
6570
- id: user.id,
6571
- name: user.name,
6572
- status,
6573
- pendingTask: rawRows.some((record) => record.result === 0 && record.finished === 0),
6574
- ...records.length ? { records } : {}
6575
- };
6576
- });
6577
- return {
6578
- type: node.type,
6579
- id: node.node_id,
6580
- status: nodeStatus,
6581
- name: node.name,
6582
- desc: node.desc ?? void 0,
6583
- time: node.created_time && (node.finished === 1 || nodeStatus === "done" || nodeStatus === "rejected") ? formatTime(node.created_time, "YYYY-MM-DD HH:mm") : void 0,
6584
- mode: node.mode,
6585
- selfSelect: node.selfSelect,
6586
- users
6587
- };
6588
- });
6589
- });
6688
+ /** display 模式审批时间线:节点快照直接映射 FlowNodeItem(映射规则详见 timeline-mapping.mapTimelineNodes) */
6689
+ const displayNodes = computed(() => mapTimelineNodes(props.timelineData));
6590
6690
  /** 按渲染模式取节点链:form=发起节点链、display=审批时间线(均为组件内部处理所得),有数据时统一追加 END 结束节点,无数据为空数组走空态 */
6591
- const flowNodes = computed(() => {
6592
- const baseNodes = props.mode === "form" ? formNodes.value : displayNodes.value;
6593
- if (!baseNodes.length) return [];
6594
- const allDone = props.mode !== "form" && baseNodes.every((node) => node.status === "done");
6595
- return [...baseNodes, {
6596
- ...END_NODE,
6597
- status: allDone ? "done" : "pending"
6598
- }];
6599
- });
6691
+ const flowNodes = computed(() => withEndNode(props.mode === "form" ? formNodes.value : displayNodes.value, props.mode));
6600
6692
  return (_ctx, _cache) => {
6601
- const _component_NTag = NTag;
6602
- const _component_NEmpty = NEmpty;
6603
- const _component_NSpin = NSpin;
6604
- const _component_NCollapseItem = NCollapseItem;
6605
- const _component_NCollapse = NCollapse;
6606
6693
  const _component_NButton = NButton;
6607
6694
  return openBlock(), createBlock(base_card_default, {
6608
6695
  loading: __props.loading,
@@ -6619,55 +6706,25 @@ var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6619
6706
  onCancel: handleClose
6620
6707
  }, createSlots({
6621
6708
  default: withCtx(() => [
6622
- renderSlot(_ctx.$slots, "default", {}, void 0, true),
6623
- createVNode(base_card_item_default, { "content-padding": 0 }, {
6624
- default: withCtx(() => [createVNode(_component_NCollapse, {
6625
- class: "flow-node",
6626
- "arrow-placement": "right",
6627
- "default-expanded-names": ["flow"]
6628
- }, {
6629
- default: withCtx(() => [createVNode(_component_NCollapseItem, { name: "flow" }, {
6630
- header: withCtx(() => [createElementVNode("span", _hoisted_1$2, [_cache[1] || (_cache[1] = createTextVNode(" 审批流程 ", -1)), flowStatusTag.value ? (openBlock(), createBlock(_component_NTag, {
6631
- key: 0,
6632
- type: flowStatusTag.value.type,
6633
- size: "small"
6634
- }, {
6635
- default: withCtx(() => [createTextVNode(toDisplayString(flowStatusTag.value.text), 1)]),
6636
- _: 1
6637
- }, 8, ["type"])) : createCommentVNode("", true)])]),
6638
- default: withCtx(() => [createVNode(_component_NSpin, {
6639
- show: flowNodesLoading.value,
6640
- class: "w-full"
6641
- }, {
6642
- default: withCtx(() => [createElementVNode("div", _hoisted_2$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(flowNodes.value, (node) => {
6643
- return openBlock(), createBlock(FlowNode_default, {
6644
- key: node.id ?? node.name,
6645
- "selected-users": selfSelectedUsersMap.value[node.name],
6646
- "onUpdate:selectedUsers": ($event) => selfSelectedUsersMap.value[node.name] = $event,
6647
- node,
6648
- mode: __props.mode
6649
- }, null, 8, [
6650
- "selected-users",
6651
- "onUpdate:selectedUsers",
6652
- "node",
6653
- "mode"
6654
- ]);
6655
- }), 128)), !flowNodes.value.length ? (openBlock(), createBlock(_component_NEmpty, {
6656
- key: 0,
6657
- description: __props.mode === "form" ? "必填字段填写完整后展示流程节点" : "暂无流程节点"
6658
- }, null, 8, ["description"])) : createCommentVNode("", true)])]),
6659
- _: 1
6660
- }, 8, ["show"])]),
6661
- _: 1
6662
- })]),
6663
- _: 1
6664
- })]),
6665
- _: 1
6666
- }),
6709
+ renderSlot(_ctx.$slots, "default"),
6710
+ createVNode(FlowNodesPanel_default, {
6711
+ "selected-users-map": selfSelectedUsersMap.value,
6712
+ "onUpdate:selectedUsersMap": _cache[0] || (_cache[0] = ($event) => selfSelectedUsersMap.value = $event),
6713
+ nodes: flowNodes.value,
6714
+ mode: __props.mode,
6715
+ loading: flowNodesLoading.value,
6716
+ "status-tag": flowStatusTag.value
6717
+ }, null, 8, [
6718
+ "selected-users-map",
6719
+ "nodes",
6720
+ "mode",
6721
+ "loading",
6722
+ "status-tag"
6723
+ ]),
6667
6724
  canReject.value ? (openBlock(), createBlock(ApprovalModal_default, {
6668
6725
  key: 0,
6669
6726
  visible: approvalVisible.value,
6670
- "onUpdate:visible": _cache[0] || (_cache[0] = ($event) => approvalVisible.value = $event),
6727
+ "onUpdate:visible": _cache[1] || (_cache[1] = ($event) => approvalVisible.value = $event),
6671
6728
  type: approvalType.value,
6672
6729
  nodes: flowNodes.value,
6673
6730
  onSubmitted: handleApprovalSubmit
@@ -6691,7 +6748,7 @@ var FlowLayout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
6691
6748
  } : void 0]), 1032, ["loading", "confirm-config"]);
6692
6749
  };
6693
6750
  }
6694
- }), [["__scopeId", "data-v-1ed133b8"]]);
6751
+ });
6695
6752
  //#endregion
6696
6753
  //#region src/flow-list/modules/FlowListItem.vue?vue&type=script&setup=true&lang.ts
6697
6754
  var _hoisted_1$1 = {
@@ -6910,4 +6967,4 @@ var flow_list_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
6910
6967
  }
6911
6968
  }), [["__scopeId", "data-v-3484a8df"]]);
6912
6969
  //#endregion
6913
- export { ApprovalModal_default as ApprovalModal, FLOW_UI_KEY, FlowAvatar_default as FlowAvatar, FlowConfig_default as FlowConfig, FlowConfigField_default as FlowConfigField, FlowConfigMore_default as FlowConfigMore, FlowLayout_default as FlowLayout, flow_list_default as FlowList, FlowNode_default as FlowNode, person_selector_default as PersonSelector, createFlowUI, fetchGetWflowConfigDetail, fetchGetWflowFlowNodes, fetchLaunchWflowFlow, fetchWflowUndoApprove, fetchWflowUndoReject, getRemoteOptions, isRemoteField, useFlowUI };
6970
+ export { ApprovalModal_default as ApprovalModal, FLOW_UI_KEY, FlowAvatar_default as FlowAvatar, FlowConfig_default as FlowConfig, FlowConfigField_default as FlowConfigField, FlowConfigMore_default as FlowConfigMore, FlowLayout_default as FlowLayout, flow_list_default as FlowList, FlowNode_default as FlowNode, FlowNodesPanel_default as FlowNodesPanel, person_selector_default as PersonSelector, createFlowUI, fetchGetWflowConfigDetail, fetchGetWflowFlowNodes, fetchLaunchWflowFlow, fetchWflowUndoApprove, fetchWflowUndoReject, getRemoteOptions, isRemoteField, useFlowUI };
package/dist/style.css CHANGED
@@ -2120,58 +2120,6 @@ to {
2120
2120
  width: 100%;
2121
2121
  }
2122
2122
 
2123
- /* Developed collaboratively with vscode - 改动代码行数: 3行 */
2124
- .yg-confirm[data-v-a65cb6d5] {
2125
- display: inline-block;
2126
- }
2127
-
2128
- /* Developed collaboratively with vscode - 改动代码行数: 7行 */
2129
- .base-card-wrapper[data-v-956107d8] {
2130
- height: 100%;
2131
- background: #f0f2f5;
2132
- display: flex;
2133
- flex-direction: column;
2134
- }
2135
- .dark .base-card-wrapper[data-v-956107d8] {
2136
- background: #141414;
2137
- }
2138
-
2139
- /* Developed collaboratively with vscode - 改动代码行数: 6行 */
2140
- .base-card-content[data-v-956107d8] {
2141
- flex: 1;
2142
- overflow: auto;
2143
- padding: 16px;
2144
- }
2145
- /* NSpin 容器需继承 flex:1 撑满剩余空间 */
2146
- .base-card-content[data-v-956107d8] .n-spin-container {
2147
- height: 100%;
2148
- }
2149
-
2150
- /* Developed collaboratively with vscode - 改动代码行数: 10行 */
2151
- /* BaseCardItem 之间的间距 */
2152
- .base-card-content[data-v-956107d8] .base-card-item-spacing {
2153
- margin-bottom: 16px !important;
2154
- }
2155
- .base-card-content[data-v-956107d8] .base-card-item-spacing:last-child {
2156
- margin-bottom: 0 !important;
2157
- }
2158
-
2159
- /* Developed collaboratively with vscode - 改动代码行数: 20行 */
2160
- .base-card-footer[data-v-956107d8] {
2161
- flex-shrink: 0;
2162
- height: 56px;
2163
- background: #ffffff;
2164
- box-shadow: 0 -8px 12px 0 rgba(0, 0, 0, 0.04);
2165
- display: flex;
2166
- align-items: center;
2167
- justify-content: flex-end;
2168
- padding: 0 24px;
2169
- }
2170
- .dark .base-card-footer[data-v-956107d8] {
2171
- background: #1e1e1e;
2172
- box-shadow: 0 -8px 12px 0 rgba(255, 255, 255, 0.04);
2173
- }
2174
-
2175
2123
  .base-card-item[data-v-e9115957] {
2176
2124
  background: var(--n-color) !important;
2177
2125
  padding: 0 !important;
@@ -2228,41 +2176,94 @@ to {
2228
2176
  gap: 12px;
2229
2177
  }
2230
2178
 
2231
- .flow-node[data-v-1ed133b8] {
2179
+ /* Developed collaboratively with vscode - 改动代码行数: 31行(折叠面板样式自 FlowLayout.vue 迁入:单个折叠项间距/标题行布局/标题字号与底边框/内容区留白) */
2180
+ .flow-node[data-v-25e5146e] {
2232
2181
  /* 单个折叠项:去掉默认上外边距,上下间距统一由 header 内边距控制 */
2233
2182
  }
2234
- .flow-node[data-v-1ed133b8] .n-collapse-item {
2183
+ .flow-node[data-v-25e5146e] .n-collapse-item {
2235
2184
  margin: 0;
2236
2185
  }
2237
- .flow-node[data-v-1ed133b8] {
2186
+ .flow-node[data-v-25e5146e] {
2238
2187
  /* 标题行:文字左对齐、箭头右对齐;左右留白放在 header-main 上,保证 header 底边框顶满两侧 */
2239
2188
  }
2240
- .flow-node[data-v-1ed133b8] .n-collapse-item__header-main {
2189
+ .flow-node[data-v-25e5146e] .n-collapse-item__header-main {
2241
2190
  justify-content: space-between;
2242
2191
  padding: 0 16px;
2243
2192
  }
2244
- .flow-node[data-v-1ed133b8] {
2245
- /* Developed collaboratively with vscode - 改动代码行数: 5行(标题插槽包装:标题文案与状态标签横向排列、垂直居中;自身为 header-main 单一 flex 项,不参与 space-between 分离) */
2193
+ .flow-node[data-v-25e5146e] {
2194
+ /* 标题插槽包装:标题文案与状态标签横向排列、垂直居中;自身为 header-main 单一 flex 项,不参与 space-between 分离 */
2246
2195
  }
2247
- .flow-node .flow-node__title[data-v-1ed133b8] {
2196
+ .flow-node .flow-node__title[data-v-25e5146e] {
2248
2197
  display: inline-flex;
2249
2198
  align-items: center;
2250
2199
  gap: 8px;
2251
2200
  }
2252
- .flow-node[data-v-1ed133b8] {
2201
+ .flow-node[data-v-25e5146e] {
2253
2202
  /* 标题字号增大,上下等距内边距 + 底部分隔边框;用 first-child 提高优先级,覆盖 naive-ui 对首个折叠项 header 强制的 padding-top: 0 */
2254
2203
  }
2255
- .flow-node[data-v-1ed133b8] .n-collapse-item:first-child > .n-collapse-item__header {
2204
+ .flow-node[data-v-25e5146e] .n-collapse-item:first-child > .n-collapse-item__header {
2256
2205
  padding: 16px 0;
2257
2206
  font-size: 16px;
2258
2207
  border-bottom: 1px solid var(--n-divider-color, rgba(0, 0, 0, 0.09));
2259
2208
  }
2260
- .flow-node[data-v-1ed133b8] {
2209
+ .flow-node[data-v-25e5146e] {
2261
2210
  /* 内容区留白与标题行对齐 */
2262
2211
  }
2263
- .flow-node[data-v-1ed133b8] .n-collapse-item__content-wrapper {
2212
+ .flow-node[data-v-25e5146e] .n-collapse-item__content-wrapper {
2264
2213
  padding: 0 16px 16px;
2265
2214
  }
2215
+ /* Developed collaboratively with vscode - 改动代码行数: 3行 */
2216
+ .yg-confirm[data-v-a65cb6d5] {
2217
+ display: inline-block;
2218
+ }
2219
+
2220
+ /* Developed collaboratively with vscode - 改动代码行数: 7行 */
2221
+ .base-card-wrapper[data-v-956107d8] {
2222
+ height: 100%;
2223
+ background: #f0f2f5;
2224
+ display: flex;
2225
+ flex-direction: column;
2226
+ }
2227
+ .dark .base-card-wrapper[data-v-956107d8] {
2228
+ background: #141414;
2229
+ }
2230
+
2231
+ /* Developed collaboratively with vscode - 改动代码行数: 6行 */
2232
+ .base-card-content[data-v-956107d8] {
2233
+ flex: 1;
2234
+ overflow: auto;
2235
+ padding: 16px;
2236
+ }
2237
+ /* NSpin 容器需继承 flex:1 撑满剩余空间 */
2238
+ .base-card-content[data-v-956107d8] .n-spin-container {
2239
+ height: 100%;
2240
+ }
2241
+
2242
+ /* Developed collaboratively with vscode - 改动代码行数: 10行 */
2243
+ /* BaseCardItem 之间的间距 */
2244
+ .base-card-content[data-v-956107d8] .base-card-item-spacing {
2245
+ margin-bottom: 16px !important;
2246
+ }
2247
+ .base-card-content[data-v-956107d8] .base-card-item-spacing:last-child {
2248
+ margin-bottom: 0 !important;
2249
+ }
2250
+
2251
+ /* Developed collaboratively with vscode - 改动代码行数: 20行 */
2252
+ .base-card-footer[data-v-956107d8] {
2253
+ flex-shrink: 0;
2254
+ height: 56px;
2255
+ background: #ffffff;
2256
+ box-shadow: 0 -8px 12px 0 rgba(0, 0, 0, 0.04);
2257
+ display: flex;
2258
+ align-items: center;
2259
+ justify-content: flex-end;
2260
+ padding: 0 24px;
2261
+ }
2262
+ .dark .base-card-footer[data-v-956107d8] {
2263
+ background: #1e1e1e;
2264
+ box-shadow: 0 -8px 12px 0 rgba(255, 255, 255, 0.04);
2265
+ }
2266
+
2266
2267
  .flow-item[data-v-aa89ccbf] {
2267
2268
  display: flex;
2268
2269
  align-items: center;
@@ -0,0 +1,32 @@
1
+ import { FlowNodeItem } from './FlowNode';
2
+ import { FlowStatusTag, FlowTimelineData } from './timeline-mapping';
3
+ import { SelectedItem } from '../person-selector/types';
4
+ type __VLS_Props = {
5
+ /** 原始详情数据(display 场景推荐):内部映射为审批时间线并自动推导标题状态标签 */
6
+ data?: FlowTimelineData;
7
+ /** 已构建节点链:直接渲染(form 模式传请求所得节点链;display 场景传已映射时间线,未传 data 时生效,状态标签需配 statusTag) */
8
+ nodes?: FlowNodeItem[];
9
+ /** 渲染模式:display=审批时间线(缺省按 display 处理)/ form=发起节点(自选审批人为人员选择器、空态文案按模式区分) */
10
+ mode?: 'display' | 'form';
11
+ /** 节点区加载中:NSpin 遮罩(form 模式请求节点期间由外部传入,display 默认 false) */
12
+ loading?: boolean;
13
+ /** 折叠面板标题 */
14
+ title?: string;
15
+ /** 标题状态标签:data 模式自动推导无需传;nodes 模式需外部传入 */
16
+ statusTag?: FlowStatusTag;
17
+ };
18
+ type __VLS_ModelProps = {
19
+ /** 自选审批人节点已选人员(key=节点名) */
20
+ 'selectedUsersMap'?: Record<string, SelectedItem[]>;
21
+ };
22
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
23
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
24
+ "update:selectedUsersMap": (value: Record<string, SelectedItem[]>) => any;
25
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
26
+ "onUpdate:selectedUsersMap"?: ((value: Record<string, SelectedItem[]>) => any) | undefined;
27
+ }>, {
28
+ title: string;
29
+ loading: boolean;
30
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
31
+ declare const _default: typeof __VLS_export;
32
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { FlowNodeItem, FlowNodeUserRecord } from './FlowNode';
2
+ import { WflowTodoDetail, WflowTodoRecord } from '../adapter/wflow';
3
+ /** display 模式原始详情数据:节点快照自带任务状态与每人处理结果 + status/finished 实例状态 */
4
+ export type FlowTimelineData = Pick<WflowTodoDetail, 'nodes' | 'finished' | 'status'>;
5
+ /** 流程状态标签(getFlowStatusTag 返回):颜色映射 已同意=success / 已拒绝=error / 进行中=warning */
6
+ export interface FlowStatusTag {
7
+ text: string;
8
+ type: 'success' | 'error' | 'warning';
9
+ }
10
+ /** 固定尾节点:节点有数据时渲染追加(display 下状态按流程是否走完覆盖),接口返回与提交数据均不包含 */
11
+ export declare const END_NODE: FlowNodeItem;
12
+ /** 人员处理记录映射:一行 records 对应一条展示记录(finished=0 未完成行过滤)——result 优先:result=2 同意;result=3 或 is_reject=1 驳回行按记录操作人判定方向(record.user_id=挂载人员 id 主动驳回「驳回到 rejecters.name」、否则被驳回「被 user_name 驳回」、名字均缺且 flowRejected=true 展示「驳回到 发起人」、否则「驳回」);result=0 不展示 */
13
+ export declare function toUserRecords(user: {
14
+ id?: number;
15
+ records?: WflowTodoRecord[];
16
+ }, flowRejected?: boolean): FlowNodeUserRecord[];
17
+ /** 已处理记录(result=2/3 或 is_reject=1,与 toUserRecords 口径一致)中处理时间(assertion_time 毫秒时间戳)最新的一条是否为驳回(result=3 或 is_reject=1);空集合返回 false,records 数组顺序不可靠必须按时间数值比较 */
18
+ export declare function isLatestRowsReject(rows: WflowTodoRecord[]): boolean;
19
+ /** display 模式审批时间线映射:节点快照直接映射 FlowNodeItem——发起人节点独立判定:流程被驳回到发起人(finished=1 且 status=-1)标红、其余一律完成绿色;其余节点按实例 finished、节点自带 finished(1=已完成)推导:进行中节点优先取第一个「未完成且 status=1 待处理」的节点,无激活节点时兜底认第一个存在待处理任务行(users.records 中 result=0 且 finished=0)的节点为当前节点,仍无则回退第一个未完成节点,有激活/兜底节点时其后节点一律待开始(剥离上一轮残留),流程被驳回终止时其余节点一律灰色待开始;人员状态按本人最新一条操作记录展示(已处理记录 result=2/3 或 is_reject=1 中 assertion_time 最新一条:驳回标红、同意标绿),无操作记录随节点状态(进行中节点淡黄、其余灰色未处理) */
20
+ export declare function mapTimelineNodes(data?: FlowTimelineData): FlowNodeItem[];
21
+ /** 组装完整节点链:有数据统一追加 END 结束节点,无数据为空数组走空态;display(非 form)流程全部完成时 END 状态同步 done;末节点已是 end(后端快照自带 end、或调用方传入已组装过 END 的节点链,如 FlowLayout → FlowNodesPanel)时幂等跳过,防止结束节点重复渲染 */
22
+ export declare function withEndNode(baseNodes: FlowNodeItem[], mode?: 'display' | 'form'): FlowNodeItem[];
23
+ /** 折叠面板标题流程状态标签:仅 display 模式且有详情数据时推导——finished=1 且 status=-1 已拒绝(驳回到发起人终止)、finished=1 已同意、未结束进行中;form 发起模式无实例状态不展示,数据未返回前隐藏避免先展示「进行中」再跳变 */
24
+ export declare function getFlowStatusTag(mode: 'display' | 'form' | undefined, data?: FlowTimelineData): FlowStatusTag | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yugu-ui/flow-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "堉古商城流程组件库:流程设计器(config)/ 审批时间线(timeline)/ 流程列表(FlowList)",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -20,10 +20,6 @@
20
20
  },
21
21
  "./style.css": "./dist/style.css"
22
22
  },
23
- "scripts": {
24
- "build": "vite build --config vite.lib.config.ts",
25
- "typecheck": "vue-tsc --noEmit --skipLibCheck"
26
- },
27
23
  "peerDependencies": {
28
24
  "naive-ui": ">=2.40.0",
29
25
  "vue": ">=3.3.0"
@@ -54,5 +50,9 @@
54
50
  "publishConfig": {
55
51
  "access": "public",
56
52
  "registry": "https://registry.npmjs.org/"
53
+ },
54
+ "scripts": {
55
+ "build": "vite build --config vite.lib.config.ts",
56
+ "typecheck": "vue-tsc --noEmit --skipLibCheck"
57
57
  }
58
- }
58
+ }