From f11bd0d8d9c1190340a445a67df9e43cdbde0a3e Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 27 八月 2026 11:21:26 +0800
Subject: [PATCH] feat(mes): 基础参数与工序绑定前端 + 报工填写参数字段

---
 src/views/im/home/pages/contact/friend-request-detail.vue |  229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 229 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/pages/contact/friend-request-detail.vue b/src/views/im/home/pages/contact/friend-request-detail.vue
new file mode 100644
index 0000000..235af5c
--- /dev/null
+++ b/src/views/im/home/pages/contact/friend-request-detail.vue
@@ -0,0 +1,229 @@
+<script lang="ts" setup>
+import type { FriendRequest, User } from '../../types'
+
+import { computed, ref } from 'vue'
+
+import { prompt } from '#/packages/effects/common-ui/src'
+import { DICT_TYPE } from '#/packages/constants/src'
+import { getDictLabel } from '#/packages/effects/hooks/src'
+
+import { Button, Input, message } from 'ant-design-vue'
+
+import { getCurrentUserId } from '#/views/im/utils/auth'
+
+import { ImFriendRequestHandleResult } from '../../../utils/constants'
+import { UserAvatar } from '../../components/user'
+import { UserInfo } from '../../components/user'
+import { useFriendStore } from '../../store/friendStore'
+
+defineOptions({ name: 'ImContactFriendRequestDetail' })
+
+const props = defineProps<{
+  request: FriendRequest
+}>()
+
+const emit = defineEmits<{
+  chat: [peerUserId: number]
+}>()
+
+const friendStore = useFriendStore()
+
+/** 褰撳墠鐧诲綍鐢ㄦ埛缂栧彿锛涚敤 computed 鍖呬竴灞傦紝鍒囪处鍙峰悗闅� wsCache 閲嶅彇锛岄伩鍏嶉《灞傛眰鍊煎湪 keep-alive 瀹炰緥閲屾寔鏈夋棫 id */
+const currentUserId = computed(() => getCurrentUserId())
+
+/** 鏄笉鏄垜鍙戣捣鐨勶紙fromUserId === currentUserId锛� */
+const iSentIt = computed(() => props.request.fromUserId === currentUserId.value)
+
+/** 鏄惁銆屽凡鎷掔粷銆嶆�侊細妯℃澘閲屽澶勭敤鍒帮紝computed 涓�娆$渷寰楀埌澶勫啓鏋氫妇姣斿 */
+const refused = computed(
+  () => props.request.handleResult === ImFriendRequestHandleResult.REFUSED
+)
+
+/** 鏄惁銆屽凡閫氳繃銆嶆�侊細杞蛋 UserInfo 濂藉弸璇︽儏鍏ュ彛 */
+const agreed = computed(
+  () => props.request.handleResult === ImFriendRequestHandleResult.AGREED
+)
+
+/** 瀵圭鐨勭敤鎴风紪鍙� / 鏄电О / 澶村儚 */
+const peerUserId = computed(() =>
+  iSentIt.value ? props.request.toUserId : props.request.fromUserId
+)
+const peerNickname = computed(() =>
+  iSentIt.value
+    ? props.request.toNickname || String(props.request.toUserId)
+    : props.request.fromNickname || String(props.request.fromUserId)
+)
+const peerAvatar = computed(() =>
+  iSentIt.value ? props.request.toAvatar : props.request.fromAvatar
+)
+
+/** 閫忕粰 UserInfo 鐨勬渶灏忕敤鎴蜂俊鎭紱UserInfo 鍐呴儴浼氭寜 id 璋� getSimpleUser 琛ラ綈鎬у埆 / 閮ㄩ棬 */
+const peerUser = computed<User>(() => ({
+  id: peerUserId.value,
+  nickname: peerNickname.value,
+  avatar: peerAvatar.value
+}))
+
+// 鍚勮嚜鐨� loading 鐢ㄤ簬鎸夐挳 spinner 鏄剧ず锛沺rocessing 鏄法鎸夐挳浜掓枼閿侊紝閬垮厤鍚屾剰 / 鎷掔粷骞跺彂鎻愪氦鍚屼竴鐢宠
+const agreeing = ref(false)
+const refusing = ref(false)
+const processing = ref(false)
+
+/** 鍚屾剰鐢宠锛氫簰鏂ラ攣 + 鐘舵�佷簩娆℃牎楠岋紝閬垮厤骞跺彂 / 鏈嶅姟绔凡澶勭悊鍚庡啀娆℃彁浜� */
+async function handleAgree() {
+  if (processing.value) {
+    return
+  }
+  if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
+    return
+  }
+  processing.value = true
+  agreeing.value = true
+  try {
+    await friendStore.agreeFriendRequest(props.request.id)
+    message.success('宸插悓鎰忓ソ鍙嬬敵璇�')
+  } finally {
+    agreeing.value = false
+    processing.value = false
+  }
+}
+
+/** 鎷掔粷鐢宠锛氬脊 prompt 鏀堕泦鍙�夋嫆缁濈悊鐢憋紙鐐瑰彇娑堝垯涓锛夛紝闅忓悗璋� store 钀藉簱 + 鎻愮ず */
+async function handleRefuse() {
+  if (processing.value) {
+    return
+  }
+  if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
+    return
+  }
+  // 1. 寮� prompt 鏀堕泦鎷掔粷鐞嗙敱锛堟渶澶� 255 瀛楋級锛涚敤鎴风偣銆屽彇娑堛�嶄細 reject锛屼腑姝㈠悗缁祦绋�
+  let handleContent: string | undefined
+  try {
+    const result = await prompt<string>({
+      beforeClose(scope) {
+        if (!scope.isConfirm) {
+          return
+        }
+        if ((scope.value || '').length > 255) {
+          message.error('鏈�澶� 255 涓瓧绗�')
+          return false
+        }
+      },
+      cancelText: '鍙栨秷',
+      component: Input.TextArea,
+      componentProps: {
+        allowClear: true,
+        maxlength: 255,
+        placeholder: '涓嶅~鍒欎笉鍛婄煡瀵规柟鍘熷洜',
+        rows: 3
+      },
+      content: '',
+      defaultValue: '',
+      confirmText: '鎷掔粷',
+      modelPropName: 'value',
+      title: '鎷掔粷濂藉弸鐢宠'
+    })
+    handleContent = result || undefined
+  } catch {
+    return
+  }
+  // 2. prompt 鏈熼棿鐘舵�佸彲鑳借璺ㄧ鏀规垚 AGREED / REFUSED锛屽啀鏍¢獙涓�娆¢伩鍏嶉噸澶嶆彁浜�
+  if (processing.value) {
+    return
+  }
+  if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
+    return
+  }
+  processing.value = true
+  refusing.value = true
+  try {
+    await friendStore.refuseFriendRequest(props.request.id, handleContent)
+    message.success('宸叉嫆缁濆ソ鍙嬬敵璇�')
+  } finally {
+    refusing.value = false
+    processing.value = false
+  }
+}
+</script>
+
+<template>
+  <!--
+    鏂扮殑鏈嬪弸璇︽儏闈㈡澘
+    - 宸查�氳繃 鈫� 鐩存帴璧� UserInfo 濂藉弸璇︽儏锛岃窡閫氳褰曢噷鐐瑰紑濂藉弸鐨勪綋楠屽畬鍏ㄤ竴鑷�
+    - 鏈鐞� / 宸叉嫆缁� 鈫� 鐢宠鎬侀潰鏉匡細澶村儚 + 鐢宠/鎷掔粷瀵硅瘽姘旀场 + 鏉ユ簮 + 鎿嶄綔鎸夐挳
+  -->
+  <div v-if="agreed" class="flex justify-center pt-12 px-6">
+    <div class="w-full max-w-[320px]">
+      <UserInfo
+        :user="peerUser"
+        :display-name="friendStore.getFriend(peerUserId)?.displayName || ''"
+        relation="friend"
+        @chat="emit('chat', peerUserId)"
+      />
+    </div>
+  </div>
+
+  <div v-else class="flex flex-col items-center px-6 pt-12">
+    <UserAvatar
+      :id="peerUserId"
+      :url="peerAvatar"
+      :name="peerNickname"
+      :size="64"
+      :clickable="false"
+    />
+    <div class="mt-3 text-base font-semibold text-[var(--ant-color-text)]">
+      {{ peerNickname }}
+    </div>
+
+    <!-- 鐢宠鐞嗙敱鍧楋細浠垮井淇$伆搴曟皵娉★紝鎸夌敵璇锋柟韬唤鍓嶇紑锛涢暱鏂囨湰 break-words 鎶樿 -->
+    <div
+      v-if="request.applyContent"
+      class="w-full max-w-[420px] mt-6 px-3.5 py-3 rounded-md bg-[var(--ant-color-fill-secondary)] text-13px text-[var(--ant-color-text)] break-words"
+    >
+      <span class="text-[var(--ant-color-text-secondary)]">
+        {{ iSentIt ? '鎴�' : peerNickname }}:
+      </span>
+      <span class="ml-1">{{ request.applyContent }}</span>
+    </div>
+    <!-- 鏉ユ簮琛岋細label 宸︺�乿alue 鍙筹紝瀵归綈寰俊銆屾潵婧愩�嶈 -->
+    <div
+      v-if="request.addSource"
+      class="w-full max-w-[420px] mt-3 flex items-center text-13px text-[var(--ant-color-text-secondary)]"
+    >
+      <span class="w-16 flex-shrink-0 whitespace-nowrap">鏉ユ簮</span>
+      <span class="text-[var(--ant-color-text)]">
+        {{ getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, request.addSource) }}
+      </span>
+    </div>
+    <!-- 鎷掔粷鐞嗙敱锛歭abel 宸︺�乿alue 鍙筹紱闀挎枃鏈� break-words 鑷姩鎶樿 -->
+    <div
+      v-if="refused && request.handleContent"
+      class="w-full max-w-[420px] mt-3 flex items-start text-13px text-[var(--ant-color-text-secondary)]"
+    >
+      <span class="w-16 flex-shrink-0 whitespace-nowrap">鎷掔粷鐞嗙敱</span>
+      <span class="flex-1 min-w-0 break-words text-[var(--ant-color-text)]">
+        {{ request.handleContent }}
+      </span>
+    </div>
+
+    <!-- 鎿嶄綔鎸夐挳 -->
+    <div class="w-full max-w-[420px] mt-8 flex justify-center">
+      <!-- 鎴戝彂璧� + 绛夊緟涓細绂佺敤銆岀瓑寰呭鏂归獙璇併�� -->
+      <Button
+        v-if="iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED"
+        disabled
+      >
+        绛夊緟瀵规柟楠岃瘉
+      </Button>
+      <!-- 鍒汉鍔犳垜 + 绛夊緟涓細鍚屾剰 / 鎷掔粷 -->
+      <template v-if="!iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED">
+        <Button @click="handleRefuse" :loading="refusing" :disabled="processing">鎷掔粷</Button>
+        <Button type="primary" @click="handleAgree" :loading="agreeing" :disabled="processing">
+          鍚屾剰
+        </Button>
+      </template>
+      <!-- 宸叉嫆缁濓細鍗犱綅绂佺敤鎸夐挳 -->
+      <Button v-if="refused" disabled>宸叉嫆缁�</Button>
+    </div>
+  </div>
+</template>

--
Gitblit v1.9.3