From a9d97b150701e634bdb751eab277696abd136cca Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 16 六月 2026 14:39:47 +0800
Subject: [PATCH] 君歌app 1.依照web端功能修改

---
 src/pages/oa/ReimburseManage/_components/ReimburseApprovalFlowEditor.vue |  245 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 245 insertions(+), 0 deletions(-)

diff --git a/src/pages/oa/ReimburseManage/_components/ReimburseApprovalFlowEditor.vue b/src/pages/oa/ReimburseManage/_components/ReimburseApprovalFlowEditor.vue
new file mode 100644
index 0000000..77bd712
--- /dev/null
+++ b/src/pages/oa/ReimburseManage/_components/ReimburseApprovalFlowEditor.vue
@@ -0,0 +1,245 @@
+<!--
+  鎶ラ攢瀹℃壒娴佺▼锛堝彲鎼滅储閫変汉锛岀偣閫夊嵆纭锛�
+-->
+<template>
+  <view class="flow-wrap">
+    <view v-for="(item, index) in innerList"
+          :key="item._uid"
+          class="flow-node-block">
+      <view class="flow-node-card">
+        <view class="node-header">
+          <view class="node-level-badge">{{ index + 1 }}</view>
+          <text class="node-level-text">绗瑊{ levelLabel(index + 1) }}绾у鎵�</text>
+          <view v-if="innerList.length > 1"
+                class="node-delete"
+                @click="remove(index)">
+            <up-icon name="trash"
+                     size="16"
+                     color="#f56c6c" />
+          </view>
+        </view>
+        <view class="approver-row"
+              @click="openPicker(index)">
+          <view class="approver-avatar"
+                :style="{ backgroundColor: avatarColor(item.approverName) }">
+            {{ (item.approverName || '+').charAt(0) }}
+          </view>
+          <view class="approver-meta">
+            <text class="approver-name">{{ item.approverName || '鐐瑰嚮閫夋嫨瀹℃壒浜�' }}</text>
+            <text class="approver-hint">鏀寔鎼滅储濮撳悕鎴栧伐鍙�</text>
+          </view>
+          <up-icon name="arrow-right"
+                   size="14"
+                   color="#c0c4cc" />
+        </view>
+      </view>
+      <view v-if="index < innerList.length - 1"
+            class="flow-connector">
+        <view class="flow-connector-line" />
+      </view>
+    </view>
+    <view class="add-node-bar"
+          @click="addNode">
+      <up-icon name="plus-circle"
+               size="18"
+               color="#2979ff" />
+      <text>娣诲姞瀹℃壒绾ф</text>
+    </view>
+
+    <OaUserSearchPicker v-model:show="pickerShow"
+                        v-model="pickerUserId"
+                        title="閫夋嫨瀹℃壒浜�"
+                        :users="userOptions"
+                        :show-self-quick="false"
+                        @select="onUserSelected" />
+  </view>
+</template>
+
+<script setup>
+  import { ref, watch } from "vue";
+  import OaUserSearchPicker from "../../_components/OaUserSearchPicker.vue";
+  import { userAvatarColor } from "../../_utils/userPickerUtils.js";
+
+  const props = defineProps({
+    modelValue: { type: Array, default: () => [] },
+    userOptions: { type: Array, default: () => [] },
+  });
+  const emit = defineEmits(["update:modelValue"]);
+
+  const innerList = ref([]);
+  const pickerShow = ref(false);
+  const pickerUserId = ref("");
+  const editingIndex = ref(-1);
+
+  function newUid() {
+    return `n_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
+  }
+
+  function levelLabel(n) {
+    const t = ["涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�", "涓�", "鍏�"];
+    return t[n - 1] || String(n);
+  }
+
+  function avatarColor(name) {
+    return userAvatarColor(name);
+  }
+
+  function mapIn(rows) {
+    return (rows || []).map((n, i) => ({
+      _uid: n._uid || newUid(),
+      nodeOrder: n.nodeOrder ?? i + 1,
+      signMode: n.signMode || "countersign",
+      approverId: n.approverId ?? "",
+      approverName: n.approverName || "",
+      id: n.id,
+      templateId: n.templateId,
+    }));
+  }
+
+  function mapOut() {
+    return innerList.value.map((n, i) => ({
+      nodeOrder: i + 1,
+      signMode: n.signMode || "countersign",
+      approverId: n.approverId,
+      approverName: n.approverName,
+      id: n.id,
+      templateId: n.templateId,
+    }));
+  }
+
+  function syncEmit() {
+    emit("update:modelValue", mapOut());
+  }
+
+  watch(
+    () => props.modelValue,
+    v => {
+      innerList.value = mapIn(v);
+      if (!innerList.value.length) {
+        innerList.value = [
+          { _uid: newUid(), nodeOrder: 1, signMode: "countersign", approverId: "", approverName: "" },
+        ];
+      }
+    },
+    { immediate: true, deep: true }
+  );
+
+  function addNode() {
+    innerList.value.push({
+      _uid: newUid(),
+      nodeOrder: innerList.value.length + 1,
+      signMode: "countersign",
+      approverId: "",
+      approverName: "",
+    });
+    syncEmit();
+  }
+
+  function remove(index) {
+    if (innerList.value.length <= 1) {
+      uni.showToast({ title: "鑷冲皯淇濈暀涓�涓鎵硅妭鐐�", icon: "none" });
+      return;
+    }
+    innerList.value.splice(index, 1);
+    syncEmit();
+  }
+
+  function openPicker(index) {
+    editingIndex.value = index;
+    pickerUserId.value = innerList.value[index]?.approverId || "";
+    pickerShow.value = true;
+  }
+
+  function onUserSelected(u) {
+    const node = innerList.value[editingIndex.value];
+    if (!node) return;
+    node.approverId = u.userId ?? u.id;
+    node.approverName = u.nickName || u.userName || "";
+    syncEmit();
+  }
+</script>
+
+<style scoped lang="scss">
+  .flow-node-card {
+    background: #f8f9fb;
+    border-radius: 10px;
+    padding: 12px;
+    border: 1px solid #eef0f3;
+  }
+  .node-header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+  }
+  .node-level-badge {
+    width: 22px;
+    height: 22px;
+    border-radius: 50%;
+    background: #2979ff;
+    color: #fff;
+    font-size: 12px;
+    text-align: center;
+    line-height: 22px;
+    margin-right: 8px;
+  }
+  .node-level-text {
+    flex: 1;
+    font-size: 14px;
+    color: #303133;
+    font-weight: 500;
+  }
+  .approver-row {
+    display: flex;
+    align-items: center;
+    padding: 10px 12px;
+    background: #fff;
+    border-radius: 8px;
+  }
+  .approver-avatar {
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    color: #fff;
+    font-size: 15px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+  .approver-meta {
+    flex: 1;
+    margin-left: 10px;
+    min-width: 0;
+  }
+  .approver-name {
+    display: block;
+    font-size: 15px;
+    color: #303133;
+  }
+  .approver-hint {
+    display: block;
+    font-size: 12px;
+    color: #c0c4cc;
+    margin-top: 2px;
+  }
+  .flow-connector {
+    display: flex;
+    justify-content: center;
+    padding: 6px 0;
+  }
+  .flow-connector-line {
+    width: 2px;
+    height: 14px;
+    background: #dcdfe6;
+  }
+  .add-node-bar {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    padding: 14px 0 4px;
+    color: #2979ff;
+    font-size: 14px;
+  }
+</style>

--
Gitblit v1.9.3