From 168c5f65c936eb707ea232057e808cf940a0c255 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 15 七月 2026 17:53:23 +0800
Subject: [PATCH] 生产工作台功能开发

---
 src/views/mes/workbench/index.vue                              |  430 ++++++
 src/views/mes/workbench/components/tabs/ReturnIssueTab.vue     |  409 ++++++
 src/api/mes/pro/task/index.ts                                  |   30 
 src/views/mes/workbench/types.ts                               |   14 
 src/views/mes/workbench/components/TaskListPanel.vue           |  353 +++++
 src/views/mes/workbench/components/TemplateSelectDialog.vue    |  262 +++
 src/views/mes/workbench/components/tabs/ProductIssueTab.vue    |  389 +++++
 src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue |  292 ++++
 src/views/mes/workbench/components/tabs/FeedbackTab.vue        |  331 ++++
 src/layouts/basic.vue                                          |   10 
 src/views/mes/workbench/components/WorkstationPanel.vue        |  265 +++
 src/api/mes/pro/feedback/index.ts                              |    7 
 src/router/routes/core.ts                                      |   15 
 src/views/mes/workbench/components/WorkstationSelectDialog.vue |  258 +++
 src/views/mes/workbench/components/tabs/RecordParamTab.vue     |  699 ++++++++++
 src/api/mes/pro/workorderparamrecord/index.ts                  |   88 +
 src/views/mes/workbench/components/tabs/TaskDetailTab.vue      |  190 ++
 17 files changed, 4,042 insertions(+), 0 deletions(-)

diff --git a/src/api/mes/pro/feedback/index.ts b/src/api/mes/pro/feedback/index.ts
index 0536c94..fe7605d 100644
--- a/src/api/mes/pro/feedback/index.ts
+++ b/src/api/mes/pro/feedback/index.ts
@@ -111,3 +111,10 @@
 export function approveFeedback(id: number) {
   return requestClient.put<boolean>(`/mes/pro/feedback/approve?id=${id}`);
 }
+
+/** 鎸変换鍔℃煡璇㈡姤宸ュ巻鍙� */
+export function getFeedbackListByTask(taskId: number) {
+  return requestClient.get<MesProFeedbackApi.Feedback[]>(
+    `/mes/pro/feedback/list-by-task?taskId=${taskId}`,
+  );
+}
diff --git a/src/api/mes/pro/task/index.ts b/src/api/mes/pro/task/index.ts
index e8f6dec..2dba85e 100644
--- a/src/api/mes/pro/task/index.ts
+++ b/src/api/mes/pro/task/index.ts
@@ -54,6 +54,26 @@
     statuses?: number[];
     status?: number;
   }
+
+  /** 寰呮姤宸ュ彴璐︿换鍔★紙pending-feedback-page 鍝嶅簲锛� */
+  export interface PendingFeedbackTask extends Task {
+    pendingQuantity?: number; // 寰呮姤宸ユ暟閲忥紙= 鎺掍骇 - 宸茬敓浜э級
+    inTransitQuantity?: number; // 鍦ㄩ�旀姤宸ユ暟閲忥紙鑽夌/瀹℃壒涓�/寰呮楠屽悎璁★級
+    reportableQuantity?: number; // 鍙姤宸ユ暟閲忥紙= 鎺掍骇 - 宸茬敓浜� - 鍦ㄩ�旓級
+    feedbackRate?: number; // 鎶ュ伐瀹屾垚鐜�(%)
+  }
+
+  /** 寰呮姤宸ュ彴璐﹀垎椤垫煡璇㈠弬鏁� */
+  export interface PendingFeedbackPageParams extends PageParam {
+    processId?: number;
+    routeId?: number;
+    workOrderId?: number;
+    workstationId?: number;
+    code?: string;
+    name?: string;
+    checkFlag?: boolean;
+    createTime?: string[];
+  }
 }
 
 /** 鏌ヨ鐢熶骇浠诲姟鍒嗛〉 */
@@ -93,3 +113,13 @@
 export function getGanttTaskList(params: any) {
   return requestClient.get<any[]>('/mes/pro/task/gantt-list', { params });
 }
+
+/** 鏌ヨ寰呮姤宸ュ彴璐︼紙鍒嗛〉锛� */
+export function getPendingFeedbackPage(
+  params: MesProTaskApi.PendingFeedbackPageParams,
+) {
+  return requestClient.get<PageResult<MesProTaskApi.PendingFeedbackTask>>(
+    '/mes/pro/task/pending-feedback-page',
+    { params },
+  );
+}
diff --git a/src/api/mes/pro/workorderparamrecord/index.ts b/src/api/mes/pro/workorderparamrecord/index.ts
new file mode 100644
index 0000000..e54b717
--- /dev/null
+++ b/src/api/mes/pro/workorderparamrecord/index.ts
@@ -0,0 +1,88 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesProWorkOrderParamRecordApi {
+  /** 鍙傛暟璁板綍鏄庣粏 */
+  export interface ParamRecordDetail {
+    id?: number;
+    templateDetailId?: number;
+    paramCode?: string;
+    paramName?: string;
+    paramType?: number;
+    unitMeasureId?: number;
+    unitMeasureName?: string;
+    paramValue?: string;
+    remark?: string;
+  }
+
+  /** 宸ュ崟鍙傛暟璁板綍 */
+  export interface WorkOrderParamRecord {
+    id?: number;
+    workOrderId?: number;
+    templateId?: number;
+    templateCode?: string;
+    templateName?: string;
+    recordNo?: string;
+    remark?: string;
+    createTime?: string;
+    details?: ParamRecordDetail[];
+  }
+
+  /** 鍒涘缓/鏇存柊璇锋眰鍙傛暟 */
+  export interface SaveParamRecord {
+    id?: number;
+    templateId?: number;
+    recordNo?: string;
+    remark?: string;
+    details: {
+      templateDetailId: number;
+      paramValue?: string;
+    }[];
+  }
+}
+
+/** 鎸夊伐鍗曞垎椤垫煡璇㈠弬鏁拌褰� */
+export function getWorkOrderParamRecordPage(
+  params: PageParam & { workOrderId: number; recordNo?: string; templateName?: string; createTime?: string[] },
+) {
+  return requestClient.get<PageResult<MesProWorkOrderParamRecordApi.WorkOrderParamRecord>>(
+    '/mes/pro/work-order-param-record/page-by-work-order',
+    { params },
+  );
+}
+
+/** 鑾峰彇鍙傛暟璁板綍璇︽儏 */
+export function getWorkOrderParamRecord(id: number) {
+  return requestClient.get<MesProWorkOrderParamRecordApi.WorkOrderParamRecord>(
+    `/mes/pro/work-order-param-record/get?id=${id}`,
+  );
+}
+
+/** 鍒涘缓鍙傛暟璁板綍 */
+export function createWorkOrderParamRecord(
+  data: MesProWorkOrderParamRecordApi.SaveParamRecord,
+  workOrderId: number,
+) {
+  return requestClient.post<number>(
+    `/mes/pro/work-order-param-record/create?workOrderId=${workOrderId}`,
+    data,
+  );
+}
+
+/** 鏇存柊鍙傛暟璁板綍 */
+export function updateWorkOrderParamRecord(
+  data: MesProWorkOrderParamRecordApi.SaveParamRecord,
+) {
+  return requestClient.put<boolean>(
+    '/mes/pro/work-order-param-record/update',
+    data,
+  );
+}
+
+/** 鍒犻櫎鍙傛暟璁板綍 */
+export function deleteWorkOrderParamRecord(id: number) {
+  return requestClient.delete<boolean>(
+    `/mes/pro/work-order-param-record/delete?id=${id}`,
+  );
+}
diff --git a/src/layouts/basic.vue b/src/layouts/basic.vue
index b6231e5..ee2b152 100644
--- a/src/layouts/basic.vue
+++ b/src/layouts/basic.vue
@@ -303,6 +303,16 @@
         />
       </div>
     </template> -->
+    <template #header-right-170>
+      <button
+        type="button"
+        class="flex cursor-pointer items-center gap-1.5 rounded-md border-none bg-primary/15 px-3 py-1.5 text-sm font-medium text-primary transition-colors hover:bg-primary/25"
+        @click="router.push({ name: 'MesWorkbench' })"
+      >
+        <IconifyIcon icon="lucide:factory" class="size-4" />
+        <span>宸ヤ綔鍙�</span>
+      </button>
+    </template>
     <template #extra>
       <AuthenticationLoginExpiredModal
         v-model:open="accessStore.loginExpired"
diff --git a/src/router/routes/core.ts b/src/router/routes/core.ts
index f9aec30..7e14b88 100644
--- a/src/router/routes/core.ts
+++ b/src/router/routes/core.ts
@@ -110,6 +110,21 @@
     ],
   },
   /**
+   * 鐢熶骇宸ヤ綔鍙� - 鍏ㄥ睆鐙珛椤甸潰锛屼笉浣跨敤 BasicLayout
+   */
+  {
+    component: () => import('#/views/mes/workbench/index.vue'),
+    meta: {
+      hideInBreadcrumb: true,
+      hideInMenu: true,
+      hideInTab: true,
+      ignoreAccess: true,
+      title: '鐢熶骇宸ヤ綔鍙�',
+    },
+    name: 'MesWorkbench',
+    path: '/mes/workbench',
+  },
+  /**
    * 鐢ㄤ簬 bpm 绉诲姩绔祦绋嬭〃鍗� web-view 鐨勫祵鍏�
    */
   {
diff --git a/src/views/mes/workbench/components/TaskListPanel.vue b/src/views/mes/workbench/components/TaskListPanel.vue
new file mode 100644
index 0000000..24d1f72
--- /dev/null
+++ b/src/views/mes/workbench/components/TaskListPanel.vue
@@ -0,0 +1,353 @@
+<script lang="ts" setup>
+import type { PendingTask } from '../../types';
+
+import { computed, ref } from 'vue';
+
+import { Tag } from 'ant-design-vue';
+
+defineOptions({ name: 'WorkbenchTaskListPanel' });
+
+const props = defineProps<{
+  list: PendingTask[];
+  selectedId?: number;
+  loading?: boolean;
+}>();
+
+const emit = defineEmits<{
+  select: [task: PendingTask];
+}>();
+
+const searchKeyword = ref('');
+
+const filteredList = computed(() => {
+  const kw = searchKeyword.value.trim().toLowerCase();
+  if (!kw) return props.list;
+  return props.list.filter((row) => {
+    const fields = [
+      row.code,
+      row.workOrderCode,
+      row.workOrderName,
+      row.itemCode,
+      row.itemName,
+      row.processName,
+    ];
+    return fields.some((f) => String(f ?? '').toLowerCase().includes(kw));
+  });
+});
+
+function getProgressColor(rate?: number): string {
+  if (rate === undefined || rate === null) return '#1890ff';
+  if (rate >= 100) return '#52c41a';
+  if (rate >= 60) return '#1890ff';
+  return '#faad14';
+}
+</script>
+
+<template>
+  <section class="task-list-panel">
+    <div class="task-list-panel__head">
+      <span class="task-list-panel__title">宸ュ崟鍒楄〃</span>
+      <Tag color="blue">{{ list.length }}</Tag>
+    </div>
+
+    <div class="task-list-panel__search">
+      <input
+        v-model="searchKeyword"
+        class="task-list-panel__input"
+        placeholder="璁㈠崟/浜у搧/宸ュ簭"
+      />
+    </div>
+
+    <div v-if="loading" class="task-list-panel__empty">鍔犺浇涓�...</div>
+    <div v-else-if="!list.length" class="task-list-panel__empty">
+      鏆傛棤寰呮姤宸ヤ换鍔�
+    </div>
+    <div v-else-if="!filteredList.length" class="task-list-panel__empty">
+      鏃犲尮閰嶅伐鍗�
+    </div>
+
+    <div v-else class="task-list-panel__scroll">
+      <div
+        v-for="item in filteredList"
+        :key="item.id"
+        class="task-card"
+        :class="{ 'task-card--active': item.id === selectedId }"
+        @click="emit('select', item)"
+      >
+        <div class="task-card__accent" />
+        <div class="task-card__inner">
+          <div class="task-card__header">
+            <span class="task-card__code">{{ item.code }}</span>
+            <Tag
+              v-if="item.checkFlag"
+              color="orange"
+              class="task-card__check-tag"
+            >
+              璐ㄦ
+            </Tag>
+            <span
+              class="task-card__rate"
+              :style="{ color: getProgressColor(item.feedbackRate) }"
+            >
+              {{ item.feedbackRate ?? 0 }}%
+            </span>
+          </div>
+
+          <div class="task-card__progress-row">
+            <span class="task-card__progress-label">鎶ュ伐杩涘害</span>
+            <span class="task-card__progress-qty">
+              {{ item.producedQuantity ?? 0 }} / {{ item.quantity ?? 0 }}
+            </span>
+          </div>
+          <div class="task-card__progress-bar">
+            <div
+              class="task-card__progress-fill"
+              :style="{
+                width: `${Math.min(item.feedbackRate ?? 0, 100)}%`,
+                backgroundColor: getProgressColor(item.feedbackRate),
+              }"
+            />
+          </div>
+
+          <div class="task-card__info">
+            <div class="task-card__info-row">
+              <span class="task-card__info-label">璁㈠崟</span>
+              <span class="task-card__info-value">
+                {{ item.workOrderCode }} {{ item.workOrderName }}
+              </span>
+            </div>
+            <div class="task-card__info-row">
+              <span class="task-card__info-label">宸ュ簭</span>
+              <span class="task-card__info-value">{{ item.processName }}</span>
+            </div>
+            <div class="task-card__info-row">
+              <span class="task-card__info-label">浜у搧</span>
+              <span class="task-card__info-value" :title="item.itemName">
+                {{ item.itemCode }} {{ item.itemName }}
+              </span>
+            </div>
+            <div class="task-card__info-row">
+              <span class="task-card__info-label">鍙姤宸�</span>
+              <span class="task-card__info-value task-card__info-value--highlight">
+                {{ item.reportableQuantity ?? 0 }} {{ item.unitMeasureName }}
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+.task-list-panel {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #f0f0f0;
+  box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
+  overflow: hidden;
+}
+
+.task-list-panel__head {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 12px 14px;
+  flex-shrink: 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.task-list-panel__title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.task-list-panel__search {
+  flex-shrink: 0;
+  padding: 10px 14px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.task-list-panel__input {
+  width: 100%;
+  height: 36px;
+  padding: 0 12px;
+  border: 1px solid #d9d9d9;
+  border-radius: 6px;
+  font-size: 14px;
+  outline: none;
+  transition: border-color 0.2s;
+
+  &:focus {
+    border-color: var(--ant-primary-color, #1890ff);
+    box-shadow: 0 0 0 2px rgb(24 144 255 / 10%);
+  }
+
+  &::placeholder {
+    color: #bfbfbf;
+  }
+}
+
+.task-list-panel__empty {
+  padding: 32px 0;
+  text-align: center;
+  color: #8c8c8c;
+  font-size: 14px;
+}
+
+.task-list-panel__scroll {
+  flex: 1;
+  min-height: 0;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.task-card {
+  position: relative;
+  margin-bottom: 10px;
+  border-radius: 8px;
+  cursor: pointer;
+  border: 1px solid #f0f0f0;
+  background: #fff;
+  transition: all 0.15s ease;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  &:hover {
+    border-color: var(--ant-primary-color, #1890ff);
+  }
+
+  &--active {
+    border-color: var(--ant-primary-color, #1890ff);
+    box-shadow: 0 1px 8px rgb(24 144 255 / 15%);
+
+    .task-card__accent {
+      opacity: 1;
+    }
+
+    .task-card__inner {
+      background: #f0f7ff;
+    }
+  }
+}
+
+.task-card__accent {
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 4px;
+  background: var(--ant-primary-color, #1890ff);
+  opacity: 0;
+  border-radius: 8px 0 0 8px;
+}
+
+.task-card__inner {
+  padding: 12px 14px 12px 16px;
+}
+
+.task-card__header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.task-card__code {
+  flex: 1;
+  min-width: 0;
+  font-weight: 600;
+  font-size: 14px;
+  color: var(--ant-primary-color, #1890ff);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.task-card__check-tag {
+  flex-shrink: 0;
+}
+
+.task-card__rate {
+  flex-shrink: 0;
+  font-size: 16px;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+}
+
+.task-card__progress-row {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  gap: 8px;
+  margin-bottom: 6px;
+}
+
+.task-card__progress-label {
+  font-size: 12px;
+  font-weight: 600;
+  color: #8c8c8c;
+}
+
+.task-card__progress-qty {
+  font-size: 13px;
+  font-weight: 600;
+  font-variant-numeric: tabular-nums;
+  color: #1a1a1a;
+}
+
+.task-card__progress-bar {
+  height: 8px;
+  background: #f0f0f0;
+  border-radius: 4px;
+  overflow: hidden;
+  margin-bottom: 10px;
+}
+
+.task-card__progress-fill {
+  height: 100%;
+  border-radius: 4px;
+  transition: width 0.3s ease;
+}
+
+.task-card__info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.task-card__info-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 13px;
+  line-height: 1.4;
+}
+
+.task-card__info-label {
+  flex-shrink: 0;
+  width: 48px;
+  color: #8c8c8c;
+}
+
+.task-card__info-value {
+  flex: 1;
+  min-width: 0;
+  color: #1a1a1a;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+
+  &--highlight {
+    color: var(--ant-primary-color, #1890ff);
+    font-weight: 600;
+  }
+}
+</style>
diff --git a/src/views/mes/workbench/components/TemplateSelectDialog.vue b/src/views/mes/workbench/components/TemplateSelectDialog.vue
new file mode 100644
index 0000000..d304ec6
--- /dev/null
+++ b/src/views/mes/workbench/components/TemplateSelectDialog.vue
@@ -0,0 +1,262 @@
+<script lang="ts" setup>
+import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template';
+
+import { computed, ref } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { IconifyIcon } from '@vben/icons';
+import { getDictOptions } from '@vben/hooks';
+
+import { Input, Modal, Spin, Tag } from 'ant-design-vue';
+
+import { getTemplatePage } from '#/api/mes/pro/processdesign/template';
+
+defineOptions({ name: 'WorkbenchTemplateSelectDialog' });
+
+const emit = defineEmits<{
+  select: [template: MesPdTemplateApi.Template];
+}>();
+
+const visible = ref(false);
+const loading = ref(false);
+const templates = ref<MesPdTemplateApi.Template[]>([]);
+const searchKeyword = ref('');
+
+/** 鎵撳紑寮圭獥 */
+async function open() {
+  visible.value = true;
+  searchKeyword.value = '';
+  await loadTemplates();
+}
+
+/** 鍔犺浇妯℃澘鍒楄〃 */
+async function loadTemplates() {
+  loading.value = true;
+  try {
+    const result = await getTemplatePage({ pageNo: 1, pageSize: 200 });
+    templates.value = result.list ?? [];
+  } finally {
+    loading.value = false;
+  }
+}
+
+/** 杩囨护鍚庣殑妯℃澘 */
+const filtered = computed(() => {
+  const kw = searchKeyword.value.trim().toLowerCase();
+  if (!kw) return templates.value;
+  return templates.value.filter((t) => {
+    const fields = [t.templateCode, t.templateName, t.remark];
+    return fields.some((f) => String(f ?? '').toLowerCase().includes(kw));
+  });
+});
+
+/** 妯℃澘绫诲瀷瀛楀吀 */
+const templateTypeOptions = getDictOptions(DICT_TYPE.MES_PD_TEMPLATE_TYPE, 'number') ?? [];
+
+function getTemplateTypeLabel(type?: number) {
+  const opt = templateTypeOptions.find((o: any) => o.value === type);
+  return opt?.label ?? '';
+}
+
+function getTemplateTypeColor(type?: number) {
+  const colors: Record<number, string> = { 1: 'blue', 2: 'green', 3: 'orange' };
+  return type ? (colors[type] ?? 'default') : 'default';
+}
+
+/** 閫夋嫨妯℃澘 */
+function handleSelect(t: MesPdTemplateApi.Template) {
+  emit('select', t);
+  visible.value = false;
+}
+
+defineExpose({ open });
+</script>
+
+<template>
+  <Modal
+    v-model:open="visible"
+    title="閫夋嫨宸ヨ壓鍙傛暟妯℃澘"
+    :footer="null"
+    width="900px"
+    :body-style="{ padding: '16px 20px', maxHeight: '70vh', overflow: 'auto' }"
+    centered
+    destroy-on-close
+  >
+    <!-- 鎼滅储妗� -->
+    <div class="tpl-select__search">
+      <Input
+        v-model:value="searchKeyword"
+        placeholder="鎼滅储妯℃澘缂栫爜/鍚嶇О"
+        allow-clear
+      >
+        <template #prefix>
+          <IconifyIcon icon="lucide:search" class="text-gray-400" />
+        </template>
+      </Input>
+    </div>
+
+    <Spin :spinning="loading">
+      <!-- 绌虹姸鎬� -->
+      <div v-if="!filtered.length && !loading" class="tpl-select__empty">
+        <IconifyIcon icon="lucide:inbox" class="tpl-select__empty-icon" />
+        <p>鏆傛棤宸ヨ壓鍙傛暟妯℃澘</p>
+      </div>
+
+      <!-- 鍗$墖缃戞牸 -->
+      <div v-else class="tpl-select__grid">
+        <button
+          v-for="t in filtered"
+          :key="t.id"
+          type="button"
+          class="tpl-card"
+          @click="handleSelect(t)"
+        >
+          <div class="tpl-card__head">
+            <span class="tpl-card__code">{{ t.templateCode }}</span>
+            <Tag v-if="t.templateType" :color="getTemplateTypeColor(t.templateType)">
+              {{ getTemplateTypeLabel(t.templateType) }}
+            </Tag>
+          </div>
+          <div class="tpl-card__name">{{ t.templateName }}</div>
+          <div v-if="t.details?.length" class="tpl-card__params">
+            <IconifyIcon icon="lucide:settings-2" class="tpl-card__params-icon" />
+            <span>{{ t.details.length }} 涓弬鏁�</span>
+          </div>
+          <div v-if="t.remark" class="tpl-card__remark">{{ t.remark }}</div>
+          <div class="tpl-card__action">
+            <IconifyIcon icon="lucide:pointer" />
+            鐐瑰嚮閫夋嫨
+          </div>
+        </button>
+      </div>
+    </Spin>
+  </Modal>
+</template>
+
+<style lang="scss" scoped>
+.tpl-select__search {
+  margin-bottom: 16px;
+}
+
+.tpl-select__empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  padding: 48px 0;
+  color: #8c8c8c;
+
+  p {
+    margin: 0;
+    font-size: 15px;
+  }
+}
+
+.tpl-select__empty-icon {
+  font-size: 40px;
+  opacity: 0.5;
+}
+
+.tpl-select__grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+  gap: 16px;
+}
+
+.tpl-card {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  padding: 16px;
+  border: 2px solid #e8e8e8;
+  border-radius: 12px;
+  text-align: left;
+  cursor: pointer;
+  background: linear-gradient(
+    145deg,
+    #f0fdf4 0%,
+    #dcfce7 52%,
+    #bbf7d0 100%
+  );
+  transition:
+    transform 0.18s ease,
+    box-shadow 0.18s ease,
+    border-color 0.18s ease;
+
+  &:hover {
+    transform: translateY(-2px);
+    border-color: #22c55e;
+    box-shadow: 0 8px 24px rgb(34 197 94 / 20%);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+.tpl-card__head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.tpl-card__code {
+  font-size: 18px;
+  font-weight: 800;
+  letter-spacing: 0.03em;
+  color: #166534;
+  line-height: 1.1;
+}
+
+.tpl-card__name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #14532d;
+  line-height: 1.3;
+}
+
+.tpl-card__params {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: #475569;
+}
+
+.tpl-card__params-icon {
+  font-size: 14px;
+  color: #64748b;
+}
+
+.tpl-card__remark {
+  font-size: 12px;
+  color: #6b7280;
+  line-height: 1.4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.tpl-card__action {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  padding: 8px;
+  border-radius: 8px;
+  background: rgb(34 197 94 / 12%);
+  border: 1px solid rgb(34 197 94 / 20%);
+  font-size: 14px;
+  font-weight: 600;
+  color: #16a34a;
+  transition: all 0.2s;
+  margin-top: auto;
+
+  .tpl-card:hover & {
+    background: rgb(34 197 94 / 20%);
+  }
+}
+</style>
diff --git a/src/views/mes/workbench/components/WorkstationPanel.vue b/src/views/mes/workbench/components/WorkstationPanel.vue
new file mode 100644
index 0000000..eb93fcb
--- /dev/null
+++ b/src/views/mes/workbench/components/WorkstationPanel.vue
@@ -0,0 +1,265 @@
+<script lang="ts" setup>
+import type { MesMdWorkstationApi } from '#/api/mes/md/workstation';
+
+import { IconifyIcon } from '@vben/icons';
+
+defineOptions({ name: 'WorkbenchWorkstationPanel' });
+
+defineProps<{
+  workstation?: MesMdWorkstationApi.Workstation;
+}>();
+
+const emit = defineEmits<{
+  change: [];
+}>();
+</script>
+
+<template>
+  <section
+    class="ws-panel"
+    :class="{ 'ws-panel--empty': !workstation }"
+  >
+    <div class="ws-panel__head">
+      <div class="ws-panel__title-row">
+        <IconifyIcon icon="lucide:map-pin" class="ws-panel__icon" />
+        <span class="ws-panel__title">宸ヤ綔绔�</span>
+      </div>
+      <button
+        v-if="workstation"
+        type="button"
+        class="ws-panel__switch"
+        @click="emit('change')"
+      >
+        鍒囨崲
+      </button>
+    </div>
+
+    <!-- 宸查�夊伐浣滅珯 -->
+    <div v-if="workstation" class="ws-panel__device">
+      <span class="ws-panel__code">{{ workstation.code }}</span>
+      <span class="ws-panel__name">{{ workstation.name }}</span>
+      <span v-if="workstation.processName" class="ws-panel__process">
+        {{ workstation.processName }}
+      </span>
+    </div>
+
+    <!-- 鏈�夊伐浣滅珯 -->
+    <div v-else class="ws-panel__placeholder">
+      <IconifyIcon icon="lucide:monitor" class="ws-panel__placeholder-icon" />
+      <p>灏氭湭閫夋嫨宸ヤ綔绔�</p>
+      <button type="button" class="ws-panel__select-btn" @click="emit('change')">
+        閫夋嫨宸ヤ綔绔�
+      </button>
+    </div>
+
+    <div v-if="workstation" class="ws-panel__meta">
+      <div class="ws-panel__meta-item">
+        <span class="ws-panel__meta-label">杞﹂棿</span>
+        <span class="ws-panel__meta-value">{{ workstation.workshopName ?? '鈥�' }}</span>
+      </div>
+      <div class="ws-panel__meta-item">
+        <span class="ws-panel__meta-label">鍦扮偣</span>
+        <span class="ws-panel__meta-value">{{ workstation.address ?? '鈥�' }}</span>
+      </div>
+    </div>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+$radius: 10px;
+$shadow: 0 2px 12px rgb(0 0 0 / 8%);
+
+.ws-panel {
+  flex-shrink: 0;
+  padding: 14px 16px;
+  border-radius: $radius;
+  color: #fff;
+  background: linear-gradient(
+    128deg,
+    #3b82f6 0%,
+    #2563eb 38%,
+    #1d4ed8 100%
+  );
+  box-shadow: $shadow;
+  border: 1px solid rgb(255 255 255 / 14%);
+
+  &--empty {
+    .ws-panel__meta {
+      opacity: 0.72;
+    }
+  }
+}
+
+.ws-panel__head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10px;
+  margin-bottom: 12px;
+}
+
+.ws-panel__title-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.ws-panel__icon {
+  font-size: 20px;
+  opacity: 0.95;
+}
+
+.ws-panel__title {
+  font-size: 17px;
+  font-weight: 600;
+  letter-spacing: 0.02em;
+}
+
+.ws-panel__switch {
+  flex-shrink: 0;
+  padding: 6px 14px;
+  border: 1px solid rgb(255 255 255 / 36%);
+  border-radius: 8px;
+  background: rgb(255 255 255 / 14%);
+  color: #fff;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:hover {
+    background: rgb(255 255 255 / 24%);
+  }
+}
+
+.ws-panel__device {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 12px;
+  padding: 12px 16px;
+  border-radius: 12px;
+  background: linear-gradient(
+    135deg,
+    rgb(255 255 255 / 24%) 0%,
+    rgb(255 255 255 / 10%) 100%
+  );
+  border: 2px solid rgb(255 255 255 / 28%);
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 22%),
+    0 8px 20px rgb(0 0 0 / 12%);
+}
+
+.ws-panel__code {
+  flex-shrink: 0;
+  font-size: 28px;
+  font-weight: 800;
+  letter-spacing: 0.04em;
+  line-height: 1;
+  color: #fff176;
+  text-shadow:
+    0 1px 2px rgb(0 0 0 / 35%),
+    0 0 12px rgb(255 241 118 / 45%);
+}
+
+.ws-panel__name {
+  flex: 1;
+  min-width: 0;
+  font-size: 17px;
+  font-weight: 700;
+  line-height: 1.2;
+  color: #fff9c4;
+  text-shadow: 0 1px 3px rgb(0 0 0 / 22%);
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.ws-panel__process {
+  flex-shrink: 0;
+  padding: 4px 12px;
+  border-radius: 999px;
+  font-size: 14px;
+  font-weight: 700;
+  background: rgb(255 193 7 / 92%);
+  color: #5c3d00;
+  box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
+}
+
+.ws-panel__placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 12px;
+  padding: 18px 12px;
+  border-radius: 12px;
+  background: rgb(0 0 0 / 12%);
+  border: 1px dashed rgb(255 255 255 / 28%);
+  text-align: center;
+
+  p {
+    margin: 0;
+    font-size: 15px;
+    opacity: 0.92;
+  }
+}
+
+.ws-panel__placeholder-icon {
+  font-size: 32px;
+  opacity: 0.85;
+}
+
+.ws-panel__select-btn {
+  padding: 10px 24px;
+  border: 1px solid rgb(255 255 255 / 40%);
+  border-radius: 8px;
+  background: rgb(255 255 255 / 18%);
+  color: #fff;
+  font-size: 15px;
+  font-weight: 700;
+  cursor: pointer;
+  transition: all 0.2s;
+  min-height: 44px;
+
+  &:hover {
+    background: rgb(255 255 255 / 28%);
+    border-color: rgb(255 255 255 / 55%);
+  }
+}
+
+.ws-panel__meta {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 10px;
+}
+
+.ws-panel__meta-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 10px;
+  padding: 8px 12px;
+  border-radius: 10px;
+  background: rgb(255 255 255 / 16%);
+  border: 1px solid rgb(255 255 255 / 22%);
+}
+
+.ws-panel__meta-label {
+  flex-shrink: 0;
+  font-size: 13px;
+  font-weight: 600;
+  opacity: 0.88;
+}
+
+.ws-panel__meta-value {
+  min-width: 0;
+  font-size: 14px;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+  color: #fff9c4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>
diff --git a/src/views/mes/workbench/components/WorkstationSelectDialog.vue b/src/views/mes/workbench/components/WorkstationSelectDialog.vue
new file mode 100644
index 0000000..6d5e41d
--- /dev/null
+++ b/src/views/mes/workbench/components/WorkstationSelectDialog.vue
@@ -0,0 +1,258 @@
+<script lang="ts" setup>
+import type { MesMdWorkstationApi } from '#/api/mes/md/workstation';
+
+import { computed, ref } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Modal, Spin } from 'ant-design-vue';
+
+import { getWorkstationPage } from '#/api/mes/md/workstation';
+
+defineOptions({ name: 'WorkbenchWorkstationSelectDialog' });
+
+const emit = defineEmits<{
+  select: [workstation: MesMdWorkstationApi.Workstation];
+}>();
+
+const visible = ref(false);
+const loading = ref(false);
+const workstations = ref<MesMdWorkstationApi.Workstation[]>([]);
+const searchKeyword = ref('');
+
+/** 鎵撳紑寮圭獥 */
+async function open() {
+  visible.value = true;
+  searchKeyword.value = '';
+  await loadWorkstations();
+}
+
+/** 鍔犺浇宸ヤ綔绔欏垪琛� */
+async function loadWorkstations() {
+  loading.value = true;
+  try {
+    const result = await getWorkstationPage({ pageNo: 1, pageSize: 200 });
+    workstations.value = result.list ?? [];
+  } finally {
+    loading.value = false;
+  }
+}
+
+/** 杩囨护鍚庣殑宸ヤ綔绔� */
+const filtered = computed(() => {
+  const kw = searchKeyword.value.trim().toLowerCase();
+  if (!kw) return workstations.value;
+  return workstations.value.filter((ws) => {
+    const fields = [ws.code, ws.name, ws.processName, ws.workshopName, ws.address];
+    return fields.some((f) => String(f ?? '').toLowerCase().includes(kw));
+  });
+});
+
+/** 閫夋嫨宸ヤ綔绔� */
+function handleSelect(ws: MesMdWorkstationApi.Workstation) {
+  emit('select', ws);
+  visible.value = false;
+}
+
+defineExpose({ open });
+</script>
+
+<template>
+  <Modal
+    v-model:open="visible"
+    title="閫夋嫨宸ヤ綔绔�"
+    :footer="null"
+    width="900px"
+    :body-style="{ padding: '16px 20px', maxHeight: '70vh', overflow: 'auto' }"
+    centered
+    destroy-on-close
+  >
+    <!-- 鎼滅储妗� -->
+    <div class="ws-select__search">
+      <Input
+        v-model:value="searchKeyword"
+        placeholder="鎼滅储宸ヤ綔绔欑紪鐮�/鍚嶇О/宸ュ簭/杞﹂棿"
+        allow-clear
+      >
+        <template #prefix>
+          <IconifyIcon icon="lucide:search" class="text-gray-400" />
+        </template>
+      </Input>
+    </div>
+
+    <Spin :spinning="loading">
+      <!-- 绌虹姸鎬� -->
+      <div v-if="!filtered.length && !loading" class="ws-select__empty">
+        <IconifyIcon icon="lucide:inbox" class="ws-select__empty-icon" />
+        <p>鏆傛棤宸ヤ綔绔欐暟鎹�</p>
+      </div>
+
+      <!-- 鍗$墖缃戞牸 -->
+      <div v-else class="ws-select__grid">
+        <button
+          v-for="ws in filtered"
+          :key="ws.id"
+          type="button"
+          class="ws-card"
+          @click="handleSelect(ws)"
+        >
+          <div class="ws-card__head">
+            <span class="ws-card__code">{{ ws.code }}</span>
+            <span v-if="ws.processName" class="ws-card__process">
+              {{ ws.processName }}
+            </span>
+          </div>
+          <div class="ws-card__name">{{ ws.name }}</div>
+          <div class="ws-card__info">
+            <div v-if="ws.workshopName" class="ws-card__info-row">
+              <IconifyIcon icon="lucide:building-2" class="ws-card__info-icon" />
+              <span>{{ ws.workshopName }}</span>
+            </div>
+            <div v-if="ws.address" class="ws-card__info-row">
+              <IconifyIcon icon="lucide:map-pin" class="ws-card__info-icon" />
+              <span>{{ ws.address }}</span>
+            </div>
+          </div>
+          <div class="ws-card__action">
+            <IconifyIcon icon="lucide:pointer" />
+            鐐瑰嚮閫夋嫨
+          </div>
+        </button>
+      </div>
+    </Spin>
+  </Modal>
+</template>
+
+<style lang="scss" scoped>
+.ws-select__search {
+  margin-bottom: 16px;
+}
+
+.ws-select__empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  padding: 48px 0;
+  color: #8c8c8c;
+
+  p {
+    margin: 0;
+    font-size: 15px;
+  }
+}
+
+.ws-select__empty-icon {
+  font-size: 40px;
+  opacity: 0.5;
+}
+
+.ws-select__grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+  gap: 16px;
+}
+
+.ws-card {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  padding: 16px;
+  border: 2px solid #e8e8e8;
+  border-radius: 12px;
+  text-align: left;
+  cursor: pointer;
+  background: linear-gradient(
+    145deg,
+    #eff6ff 0%,
+    #dbeafe 52%,
+    #bfdbfe 100%
+  );
+  transition:
+    transform 0.18s ease,
+    box-shadow 0.18s ease,
+    border-color 0.18s ease;
+
+  &:hover {
+    transform: translateY(-2px);
+    border-color: #3b82f6;
+    box-shadow: 0 8px 24px rgb(59 130 246 / 20%);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+.ws-card__head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.ws-card__code {
+  font-size: 22px;
+  font-weight: 800;
+  letter-spacing: 0.03em;
+  color: #1e40af;
+  line-height: 1.1;
+}
+
+.ws-card__process {
+  flex-shrink: 0;
+  padding: 4px 10px;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 700;
+  background: #3b82f6;
+  color: #fff;
+}
+
+.ws-card__name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1e3a5f;
+  line-height: 1.3;
+}
+
+.ws-card__info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-top: auto;
+}
+
+.ws-card__info-row {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: #475569;
+}
+
+.ws-card__info-icon {
+  font-size: 14px;
+  color: #64748b;
+  flex-shrink: 0;
+}
+
+.ws-card__action {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  padding: 8px;
+  border-radius: 8px;
+  background: rgb(59 130 246 / 12%);
+  border: 1px solid rgb(59 130 246 / 20%);
+  font-size: 14px;
+  font-weight: 600;
+  color: #2563eb;
+  transition: all 0.2s;
+
+  .ws-card:hover & {
+    background: rgb(59 130 246 / 20%);
+  }
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
new file mode 100644
index 0000000..578eaf9
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
@@ -0,0 +1,292 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProFeedbackApi } from '#/api/mes/pro/feedback';
+
+import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesProFeedbackStatusEnum,
+} from '@vben/constants';
+import { useVbenModal } from '@vben/common-ui';
+
+import { Button, message, Popconfirm, Space } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  approveFeedback,
+  deleteFeedback,
+  getFeedbackListByTask,
+  rejectFeedback,
+  submitFeedback,
+} from '#/api/mes/pro/feedback';
+import { $t } from '#/locales';
+
+import FeedbackForm from '../../../pro/feedback/modules/form.vue';
+
+defineOptions({ name: 'WorkbenchFeedbackHistoryTab' });
+
+const props = defineProps<{
+  taskId?: number;
+  /** 澶栭儴瑙﹀彂鍒锋柊 */
+  refreshKey?: number;
+}>();
+
+const emit = defineEmits<{ reported: [] }>();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: FeedbackForm,
+  destroyOnClose: true,
+});
+
+/** 鏌ョ湅璇︽儏 */
+function handleDetail(row: MesProFeedbackApi.Feedback) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫锛堜粎鑽夌锛� */
+function handleEdit(row: MesProFeedbackApi.Feedback) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎锛堜粎鑽夌锛� */
+async function handleDelete(row: MesProFeedbackApi.Feedback) {
+  await deleteFeedback(row.id!);
+  message.success('鍒犻櫎鎴愬姛');
+  refreshGrid();
+  emit('reported');
+}
+
+/** 鎻愪氦锛堜粎鑽夌锛� */
+async function handleSubmit(row: MesProFeedbackApi.Feedback) {
+  await submitFeedback(row.id!);
+  message.success('鎶ュ伐鍗曞凡鎻愪氦');
+  refreshGrid();
+  emit('reported');
+}
+
+/** 瀹℃壒閫氳繃锛堜粎瀹℃壒涓級 */
+async function handleApprove(row: MesProFeedbackApi.Feedback) {
+  const finished = await approveFeedback(row.id!);
+  message.success(
+    finished ? '鎶ュ伐鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
+  );
+  refreshGrid();
+  emit('reported');
+}
+
+/** 椹冲洖锛堜粎瀹℃壒涓級 */
+async function handleReject(row: MesProFeedbackApi.Feedback) {
+  await rejectFeedback(row.id!);
+  message.success('鎶ュ伐鍗曞凡椹冲洖');
+  refreshGrid();
+  emit('reported');
+}
+
+/** 鐘舵�佸父閲忎緵妯℃澘浣跨敤 */
+const STATUS_PREPARE = MesProFeedbackStatusEnum.PREPARE;
+const STATUS_APPROVING = MesProFeedbackStatusEnum.APPROVING;
+
+const gridHeight = ref(300);
+const gridContainerRef = ref<HTMLElement>();
+let resizeObserver: ResizeObserver | null = null;
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: [
+      {
+        field: 'code',
+        title: '鎶ヨ鍗曞彿',
+        width: 160,
+        slots: { default: 'code' },
+      },
+      { field: 'workOrderCode', title: '宸ュ崟缂栫爜', width: 140 },
+      { field: 'processName', title: '宸ュ簭', width: 100 },
+      { field: 'itemName', title: '浜у搧', minWidth: 120 },
+      { field: 'feedbackQuantity', title: '鎶ュ伐鏁伴噺', width: 100 },
+      { field: 'qualifiedQuantity', title: '鍚堟牸鍝�', width: 90 },
+      { field: 'unqualifiedQuantity', title: '涓嶈壇鍝�', width: 90 },
+      { field: 'feedbackUserNickname', title: '鎶ュ伐浜�', width: 100 },
+      { field: 'approveUserNickname', title: '瀹℃牳浜�', width: 100 },
+      {
+        field: 'status',
+        title: '鐘舵��',
+        width: 100,
+        cellRender: {
+          name: 'CellDict',
+          props: { type: DICT_TYPE.MES_PRO_FEEDBACK_STATUS },
+        },
+      },
+      {
+        field: 'feedbackTime',
+        title: '鎶ュ伐鏃堕棿',
+        width: 170,
+        formatter: 'formatDateTime',
+      },
+      {
+        title: '鎿嶄綔',
+        width: 260,
+        fixed: 'right',
+        slots: { default: 'actions' },
+      },
+    ],
+    height: gridHeight.value,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async () => {
+          if (!props.taskId) {
+            return [];
+          }
+          return await getFeedbackListByTask(props.taskId);
+        },
+      },
+    },
+    rowConfig: { isHover: true, keyField: 'id' },
+    toolbarConfig: { refresh: true },
+  } as VxeTableGridOptions<MesProFeedbackApi.Feedback>,
+});
+
+function updateGridHeight() {
+  if (gridContainerRef.value) {
+    const h = gridContainerRef.value.clientHeight;
+    if (h > 100 && h !== gridHeight.value) {
+      gridHeight.value = h;
+      gridApi.setGridOptions({ height: h });
+    }
+  }
+}
+
+onMounted(() => {
+  nextTick(() => {
+    updateGridHeight();
+    if (gridContainerRef.value) {
+      resizeObserver = new ResizeObserver(() => updateGridHeight());
+      resizeObserver.observe(gridContainerRef.value);
+    }
+  });
+});
+
+onBeforeUnmount(() => {
+  resizeObserver?.disconnect();
+  resizeObserver = null;
+});
+
+function refreshGrid() {
+  gridApi.query();
+}
+
+// 鐩戝惉 taskId 鍙樺寲鍒锋柊
+watch(
+  () => props.taskId,
+  () => {
+    if (props.taskId) {
+      gridApi.query();
+    }
+  },
+);
+
+// 鐩戝惉澶栭儴 refreshKey 鍙樺寲鍒锋柊
+watch(
+  () => props.refreshKey,
+  () => {
+    gridApi.query();
+  },
+);
+</script>
+
+<template>
+  <div class="feedback-history-tab">
+    <FormModal @success="refreshGrid" />
+
+    <div v-if="!taskId" class="feedback-history-tab__empty">
+      璇蜂粠宸︿晶宸ュ崟鍒楄〃閫夋嫨涓�鏉′换鍔℃煡鐪嬫姤宸ヨ褰�
+    </div>
+
+    <div v-else ref="gridContainerRef" class="feedback-history-tab__grid">
+      <Grid table-title="鎶ュ伐璁板綍">
+        <template #code="{ row }">
+          <Button type="link" @click="handleDetail(row)">
+            {{ row.code }}
+          </Button>
+        </template>
+        <template #actions="{ row }">
+          <Space :size="4" wrap>
+            <Button type="link" size="small" @click="handleDetail(row)">
+              {{ $t('common.view') }}
+            </Button>
+            <!-- 鑽夌锛氱紪杈戙�佸垹闄ゃ�佹彁浜� -->
+            <Button
+              v-if="row.status === STATUS_PREPARE"
+              type="link"
+              size="small"
+              @click="handleEdit(row)"
+            >
+              {{ $t('common.edit') }}
+            </Button>
+            <Popconfirm
+              v-if="row.status === STATUS_PREPARE"
+              title="纭鎻愪氦璇ユ姤宸ュ崟锛熸彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+              @confirm="handleSubmit(row)"
+            >
+              <Button type="link" size="small">
+                {{ $t('common.submit') }}
+              </Button>
+            </Popconfirm>
+            <Popconfirm
+              v-if="row.status === STATUS_PREPARE"
+              title="纭鍒犻櫎璇ユ姤宸ュ崟锛�"
+              @confirm="handleDelete(row)"
+            >
+              <Button type="link" size="small" danger>
+                {{ $t('common.delete') }}
+              </Button>
+            </Popconfirm>
+            <!-- 瀹℃壒涓細瀹℃壒閫氳繃銆侀┏鍥� -->
+            <Popconfirm
+              v-if="row.status === STATUS_APPROVING"
+              title="纭瀹℃壒閫氳繃璇ユ姤宸ュ崟锛�"
+              @confirm="handleApprove(row)"
+            >
+              <Button type="link" size="small">
+                {{ $t('common.approve') }}
+              </Button>
+            </Popconfirm>
+            <Popconfirm
+              v-if="row.status === STATUS_APPROVING"
+              title="纭椹冲洖璇ユ姤宸ュ崟锛�"
+              @confirm="handleReject(row)"
+            >
+              <Button type="link" size="small" danger>
+                椹冲洖
+              </Button>
+            </Popconfirm>
+          </Space>
+        </template>
+      </Grid>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.feedback-history-tab {
+  padding: 16px 20px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.feedback-history-tab__grid {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.feedback-history-tab__empty {
+  padding: 64px 0;
+  text-align: center;
+  color: #8c8c8c;
+  font-size: 16px;
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/FeedbackTab.vue b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
new file mode 100644
index 0000000..367c822
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -0,0 +1,331 @@
+<script lang="ts" setup>
+import type { PendingTask } from '../../../types';
+import type { FormType } from '../../../pro/feedback/data';
+
+import { computed, ref, watch } from 'vue';
+
+import {
+  MesAutoCodeRuleCode,
+  MesProFeedbackStatusEnum,
+} from '@vben/constants';
+import { useUserStore } from '@vben/stores';
+
+import { Button, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import {
+  approveFeedback,
+  createFeedback,
+  getFeedback,
+  rejectFeedback,
+  submitFeedback,
+  updateFeedback,
+} from '#/api/mes/pro/feedback';
+import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../../../pro/feedback/data';
+
+defineOptions({ name: 'WorkbenchFeedbackTab' });
+
+const props = defineProps<{
+  task?: PendingTask | null;
+  /** 澶栭儴瑙﹀彂鍒锋柊锛堝鎶ュ伐鎴愬姛鍚庡埛鏂板垪琛級 */
+  refreshKey?: number;
+}>();
+
+const emit = defineEmits<{
+  reported: [];
+}>();
+
+const userStore = useUserStore();
+const formType = ref<FormType>('create');
+const feedbackId = ref<number>();
+const feedbackStatus = ref<number>();
+const originalSnapshot = ref('');
+
+const isEditable = computed(() =>
+  ['create', 'submit', 'update'].includes(formType.value),
+);
+const canSubmit = computed(
+  () =>
+    isEditable.value &&
+    feedbackStatus.value === MesProFeedbackStatusEnum.PREPARE,
+);
+const canApprove = computed(() => formType.value === 'approve');
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鏍规嵁 checkFlag 瀵归綈鏁伴噺 */
+function alignQuantity(data: any) {
+  if (data.checkFlag) {
+    data.uncheckQuantity = data.feedbackQuantity;
+    data.qualifiedQuantity = 0;
+    data.unqualifiedQuantity = 0;
+    data.laborScrapQuantity = 0;
+    data.materialScrapQuantity = 0;
+    data.otherScrapQuantity = 0;
+  } else {
+    data.feedbackQuantity =
+      (data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0);
+    data.uncheckQuantity = 0;
+  }
+}
+
+async function getAlignedData() {
+  const data = (await formApi.getValues()) as any;
+  alignQuantity(data);
+  return data;
+}
+
+/** 鍔犺浇 checkFlag */
+async function resolveCheckFlag(routeId?: number, processId?: number) {
+  if (!routeId || !processId) return true;
+  try {
+    const rp = await getRouteProcessByRouteAndProcess(routeId, processId);
+    return rp?.checkFlag ?? false;
+  } catch {
+    return true;
+  }
+}
+
+/** 浠诲姟鍙樻洿鏃堕噸缃〃鍗� */
+async function resetForTask(task: PendingTask) {
+  formType.value = 'create';
+  feedbackId.value = undefined;
+  feedbackStatus.value = undefined;
+  originalSnapshot.value = '';
+
+  formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+
+  const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE);
+  const checkFlag = await resolveCheckFlag(task.routeId, task.processId);
+
+  await formApi.setValues({
+    code,
+    type: 1,
+    feedbackTime: Date.now(),
+    feedbackUserId: userStore.userInfo?.id,
+    taskId: task.id,
+    workOrderId: task.workOrderId,
+    routeId: task.routeId,
+    processId: task.processId,
+    workstationId: task.workstationId,
+    itemId: task.itemId,
+    itemCode: task.itemCode,
+    itemName: task.itemName,
+    itemSpecification: task.itemSpecification,
+    unitMeasureName: task.unitMeasureName,
+    scheduledQuantity: task.quantity,
+    checkFlag,
+  });
+
+  originalSnapshot.value = JSON.stringify(await getAlignedData());
+}
+
+/** 淇濆瓨 */
+async function handleSave() {
+  const { valid } = await formApi.validate();
+  if (!valid) return;
+
+  const data = await getAlignedData();
+
+  if (formType.value === 'create') {
+    const id = await createFeedback(data);
+    feedbackId.value = id;
+    feedbackStatus.value = MesProFeedbackStatusEnum.PREPARE;
+    formType.value = 'update';
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    await formApi.setFieldValue('id', id);
+    message.success($t('common.createSuccess'));
+  } else {
+    await updateFeedback(data);
+    message.success($t('common.updateSuccess'));
+  }
+  originalSnapshot.value = JSON.stringify(data);
+  emit('reported');
+}
+
+/** 鎻愪氦 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid) return;
+
+  const data = await getAlignedData();
+  if (JSON.stringify(data) !== originalSnapshot.value) {
+    await updateFeedback(data);
+    originalSnapshot.value = JSON.stringify(data);
+  }
+  await submitFeedback(feedbackId.value!);
+  message.success('鎶ヨ鍗曞凡鎻愪氦');
+  // 閲嶇疆琛ㄥ崟涓烘柊寤烘ā寮�
+  if (props.task) {
+    await resetForTask(props.task);
+  }
+  emit('reported');
+}
+
+/** 瀹℃壒閫氳繃 */
+async function handleApprove() {
+  if (!feedbackId.value) return;
+  const finished = await approveFeedback(feedbackId.value);
+  message.success(
+    finished ? '鎶ヨ鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
+  );
+  if (props.task) {
+    await resetForTask(props.task);
+  }
+  emit('reported');
+}
+
+/** 瀹℃壒椹冲洖 */
+async function handleReject() {
+  if (!feedbackId.value) return;
+  await rejectFeedback(feedbackId.value);
+  message.success('鎶ヨ鍗曞凡椹冲洖');
+  if (props.task) {
+    await resetForTask(props.task);
+  }
+  emit('reported');
+}
+
+/** 鍔犺浇宸叉湁鎶ュ伐鍗曡繘琛岀紪杈� */
+async function loadFeedback(id: number) {
+  const data = await getFeedback(id);
+  feedbackId.value = data.id;
+  feedbackStatus.value = data.status;
+  formType.value = 'update';
+  formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+  const checkFlag = await resolveCheckFlag(data.routeId, data.processId);
+  await formApi.setValues({ ...data, checkFlag });
+  originalSnapshot.value = JSON.stringify(await getAlignedData());
+}
+
+// 鐩戝惉浠诲姟鍙樻洿
+watch(
+  () => props.task,
+  (task) => {
+    if (task) {
+      resetForTask(task);
+    }
+  },
+  { immediate: true },
+);
+
+// 鏆撮湶鏂规硶渚涘閮ㄨ皟鐢�
+defineExpose({ loadFeedback, handleSave });
+</script>
+
+<template>
+  <div class="feedback-tab">
+    <div v-if="!task" class="feedback-tab__empty">
+      璇蜂粠宸︿晶宸ュ崟鍒楄〃閫夋嫨涓�鏉′换鍔�
+    </div>
+    <template v-else>
+      <div class="feedback-tab__header">
+        <h3 class="feedback-tab__title">
+          {{ formType === 'create' ? '鏂板缓鎶ュ伐' : '缂栬緫鎶ュ伐' }}
+        </h3>
+        <span v-if="task.workOrderCode" class="feedback-tab__sub">
+          宸ュ崟: {{ task.workOrderCode }} {{ task.workOrderName }}
+        </span>
+      </div>
+
+      <Form class="feedback-tab__form" />
+
+      <div class="feedback-tab__actions">
+        <Button
+          v-if="isEditable"
+          type="primary"
+          @click="handleSave"
+        >
+          淇濆瓨
+        </Button>
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ユ姤璁㈠崟锛熸彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">{{ $t('common.submit') }}</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="canApprove"
+          title="纭瀹℃壒閫氳繃璇ユ姤璁㈠崟锛�"
+          @confirm="handleApprove"
+        >
+          <Button type="primary">閫氳繃</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="canApprove"
+          title="纭椹冲洖璇ユ姤璁㈠崟锛�"
+          @confirm="handleReject"
+        >
+          <Button danger>涓嶉�氳繃</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.feedback-tab {
+  padding: 20px 24px;
+  min-height: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+}
+
+.feedback-tab__empty {
+  padding: 64px 0;
+  text-align: center;
+  color: #8c8c8c;
+  font-size: 16px;
+}
+
+.feedback-tab__header {
+  display: flex;
+  align-items: baseline;
+  gap: 16px;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.feedback-tab__title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.feedback-tab__sub {
+  font-size: 14px;
+  color: #8c8c8c;
+}
+
+.feedback-tab__form {
+  flex: 1;
+}
+
+.feedback-tab__actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  padding-top: 16px;
+  border-top: 1px solid #f0f0f0;
+  margin-top: 16px;
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
new file mode 100644
index 0000000..e59ad60
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
@@ -0,0 +1,389 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+
+import { h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+  MesWmProductIssueStatusEnum,
+} from '@vben/constants';
+import { IconifyIcon } from '@vben/icons';
+import { getDictOptions } from '@vben/hooks';
+
+import { Button, Divider, message, Popconfirm, Tag } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import {
+  cancelProductIssue,
+  createProductIssue,
+  deleteProductIssue,
+  getProductIssuePage,
+  submitProductIssue,
+  updateProductIssue,
+} from '#/api/mes/wm/productissue';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { $t } from '#/locales';
+
+defineOptions({ name: 'WorkbenchProductIssueTab' });
+
+const props = defineProps<{
+  /** 褰撳墠宸ヤ綔绔� ID */
+  workstationId?: number;
+  /** 褰撳墠宸ヤ綔绔欏悕绉� */
+  workstationName?: string;
+}>();
+
+// --- 琛ㄥ崟鐘舵�� ---
+const editingId = ref<number>();
+const isCreating = ref(false);
+
+/** 鏋勫缓琛ㄥ崟 schema */
+function buildFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '棰嗘枡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: { placeholder: '璇疯緭鍏ラ鏂欏崟缂栧彿' },
+      rules: 'required',
+      suffix: () =>
+        h(
+          Button,
+          {
+            type: 'default',
+            size: 'small',
+            onClick: async () => {
+              const code = await generateAutoCode(
+                MesAutoCodeRuleCode.WM_PRODUCT_ISSUE_CODE,
+              );
+              await formApi.setFieldValue('code', code);
+            },
+          },
+          { default: () => '鐢熸垚' },
+        ),
+    },
+    {
+      fieldName: 'name',
+      label: '棰嗘枡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: { placeholder: '璇疯緭鍏ラ鏂欏崟鍚嶇О' },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'workstationId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+    },
+    {
+      fieldName: 'requiredTime',
+      label: '闇�姹傛椂闂�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 2 },
+    },
+  ];
+}
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: buildFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鏂板缓棰嗘枡鍗� */
+async function handleCreate() {
+  isCreating.value = true;
+  editingId.value = undefined;
+  await formApi.resetForm();
+  // 棰勫~宸ヤ綔绔�
+  if (props.workstationId) {
+    await formApi.setFieldValue('workstationId', props.workstationId);
+  }
+}
+
+/** 淇濆瓨棰嗘枡鍗� */
+async function handleSave() {
+  const { valid } = await formApi.validate();
+  if (!valid) return;
+  const data =
+    (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+  try {
+    if (editingId.value) {
+      await updateProductIssue({ ...data, id: editingId.value });
+      message.success('淇敼鎴愬姛');
+    } else {
+      const id = await createProductIssue(data);
+      editingId.value = id;
+      await formApi.setFieldValue('id', id);
+      message.success('鍒涘缓鎴愬姛');
+    }
+    isCreating.value = false;
+    gridApi.query();
+  } catch {
+    message.error('淇濆瓨澶辫触');
+  }
+}
+
+/** 鍙栨秷缂栬緫 */
+function handleCancelEdit() {
+  isCreating.value = false;
+  editingId.value = undefined;
+  formApi.resetForm();
+}
+
+/** 鎻愪氦 */
+async function handleSubmitRow(row: MesWmProductIssueApi.ProductIssue) {
+  await submitProductIssue(row.id!);
+  message.success('鎻愪氦鎴愬姛');
+  gridApi.query();
+}
+
+/** 鍒犻櫎 */
+async function handleDeleteRow(row: MesWmProductIssueApi.ProductIssue) {
+  await deleteProductIssue(row.id!);
+  message.success('鍒犻櫎鎴愬姛');
+  gridApi.query();
+}
+
+/** 鍙栨秷 */
+async function handleCancelRow(row: MesWmProductIssueApi.ProductIssue) {
+  await cancelProductIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  gridApi.query();
+}
+
+// --- 鍒楄〃 ---
+const gridColumns: VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>['columns'] = [
+  { field: 'code', title: '棰嗘枡鍗曠紪鍙�', minWidth: 150 },
+  { field: 'name', title: '棰嗘枡鍗曞悕绉�', minWidth: 130 },
+  { field: 'workOrderCode', title: '鐢熶骇璁㈠崟', minWidth: 130 },
+  {
+    field: 'requiredTime',
+    title: '闇�姹傛椂闂�',
+    width: 170,
+    formatter: 'formatDateTime',
+  },
+  {
+    field: 'status',
+    title: '鐘舵��',
+    width: 100,
+    cellRender: {
+      name: 'CellDict',
+      props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
+    },
+  },
+  { title: '鎿嶄綔', width: 200, fixed: 'right', slots: { default: 'actions' } },
+];
+
+const gridHeight = ref(300);
+const listRef = ref<HTMLElement>();
+let resizeObserver: ResizeObserver | null = null;
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: gridColumns,
+    height: gridHeight.value,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getProductIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            workstationId: props.workstationId,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: { keyField: 'id', isHover: true },
+    toolbarConfig: { refresh: true },
+    pagerConfig: { pageSize: 10 },
+  } as VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>,
+});
+
+function updateGridHeight() {
+  if (listRef.value) {
+    const h = listRef.value.clientHeight;
+    if (h > 100 && h !== gridHeight.value) {
+      gridHeight.value = h;
+      gridApi.setGridOptions({ height: h });
+    }
+  }
+}
+
+onMounted(() => {
+  nextTick(() => {
+    updateGridHeight();
+    if (listRef.value) {
+      resizeObserver = new ResizeObserver(() => updateGridHeight());
+      resizeObserver.observe(listRef.value);
+    }
+  });
+});
+
+onBeforeUnmount(() => {
+  resizeObserver?.disconnect();
+  resizeObserver = null;
+});
+
+// 宸ヤ綔绔欏彉鍖栨椂鍒锋柊鍒楄〃
+watch(
+  () => props.workstationId,
+  () => gridApi.query(),
+);
+</script>
+
+<template>
+  <div class="issue-tab">
+    <!-- 椤堕儴锛氳〃鍗� / 鏂板缓鎸夐挳 -->
+    <div class="issue-tab__header">
+      <div class="issue-tab__header-left">
+        <IconifyIcon icon="lucide:clipboard-list" class="issue-tab__icon" />
+        <span class="issue-tab__title">棰嗘枡鍗�</span>
+        <Tag v-if="isCreating" color="blue">鏂板缓涓�</Tag>
+      </div>
+      <div class="issue-tab__header-actions">
+        <template v-if="!isCreating">
+          <Button type="primary" size="small" @click="handleCreate">
+            <template #icon><IconifyIcon icon="lucide:plus" /></template>
+            鏂板缓棰嗘枡鍗�
+          </Button>
+        </template>
+        <template v-else>
+          <Button type="primary" size="small" @click="handleSave">
+            <template #icon><IconifyIcon icon="lucide:save" /></template>
+            淇濆瓨
+          </Button>
+          <Button size="small" @click="handleCancelEdit">鍙栨秷</Button>
+        </template>
+      </div>
+    </div>
+
+    <!-- 琛ㄥ崟鍖哄煙锛堜粎鏂板缓鏃跺睍绀猴級 -->
+    <div v-if="isCreating" class="issue-tab__form">
+      <Form />
+    </div>
+
+    <Divider style="margin: 12px 0" />
+
+    <!-- 搴曢儴锛氶鏂欒褰曞垪琛� -->
+    <div ref="listRef" class="issue-tab__list">
+      <Grid table-title="棰嗘枡璁板綍">
+        <template #actions="{ row }">
+          <Popconfirm
+            v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
+            title="纭鎻愪氦锛熸彁浜ゅ悗涓嶈兘淇敼"
+            @confirm="handleSubmitRow(row)"
+          >
+            <Button type="link" size="small">鎻愪氦</Button>
+          </Popconfirm>
+          <Popconfirm
+            v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
+            :title="`纭鍒犻櫎 ${row.code}锛焋"
+            @confirm="handleDeleteRow(row)"
+          >
+            <Button type="link" size="small" danger>鍒犻櫎</Button>
+          </Popconfirm>
+          <Popconfirm
+            v-if="
+              row.status === MesWmProductIssueStatusEnum.APPROVING ||
+              row.status === MesWmProductIssueStatusEnum.APPROVED
+            "
+            title="纭鍙栨秷锛熷彇娑堝悗涓嶅彲鎭㈠"
+            @confirm="handleCancelRow(row)"
+          >
+            <Button type="link" size="small" danger>鍙栨秷</Button>
+          </Popconfirm>
+        </template>
+      </Grid>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.issue-tab {
+  padding: 20px 24px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.issue-tab__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.issue-tab__header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.issue-tab__icon {
+  font-size: 22px;
+  color: #1890ff;
+}
+
+.issue-tab__title {
+  font-size: 17px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.issue-tab__header-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.issue-tab__form {
+  margin-top: 16px;
+  padding: 16px;
+  background: #fafbfc;
+  border-radius: 10px;
+  border: 1px solid #f0f0f0;
+}
+
+.issue-tab__list {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/RecordParamTab.vue b/src/views/mes/workbench/components/tabs/RecordParamTab.vue
new file mode 100644
index 0000000..7ecea3a
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/RecordParamTab.vue
@@ -0,0 +1,699 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template';
+import type { MesProWorkOrderParamRecordApi } from '#/api/mes/pro/workorderparamrecord';
+
+import { nextTick, ref, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Button, Divider, message, Popconfirm, Tag } from 'ant-design-vue';
+
+import { useVbenForm, z } from '#/adapter/form';
+import { getTemplate } from '#/api/mes/pro/processdesign/template';
+import {
+  createWorkOrderParamRecord,
+  deleteWorkOrderParamRecord,
+  getWorkOrderParamRecord,
+  getWorkOrderParamRecordPage,
+  updateWorkOrderParamRecord,
+} from '#/api/mes/pro/workorderparamrecord';
+
+import TemplateSelectDialog from '../TemplateSelectDialog.vue';
+
+defineOptions({ name: 'WorkbenchRecordParamTab' });
+
+const props = defineProps<{
+  /** 褰撳墠宸ュ崟缂栧彿 */
+  workOrderId?: number;
+}>();
+
+// --- 妯″紡 ---
+type Mode = 'list' | 'create' | 'edit';
+const mode = ref<Mode>('list');
+const editingRecordId = ref<number>();
+
+// --- 妯℃澘鐘舵�� ---
+const selectedTemplate = ref<MesPdTemplateApi.Template>();
+const templateDetails = ref<MesPdTemplateApi.TemplateDetail[]>([]);
+const tplSelectDialogRef = ref<InstanceType<typeof TemplateSelectDialog>>();
+
+function openTemplateSelect() {
+  tplSelectDialogRef.value?.open();
+}
+
+/** 閫夋嫨妯℃澘鍚庡姞杞借鎯� */
+async function handleTemplateSelect(tpl: MesPdTemplateApi.Template) {
+  if (!tpl.id) return;
+  try {
+    const detail = await getTemplate(tpl.id);
+    selectedTemplate.value = detail;
+    templateDetails.value = detail.details ?? [];
+    await buildParamForm(templateDetails.value);
+  } catch {
+    message.error('鍔犺浇妯℃澘璇︽儏澶辫触');
+  }
+}
+
+// --- 鍔ㄦ�佸弬鏁拌〃鍗� ---
+const [ParamForm, paramFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 160,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-2',
+});
+
+/** 鏍规嵁妯℃澘鏄庣粏鏋勫缓鍙傛暟琛ㄥ崟 */
+async function buildParamForm(
+  details: MesPdTemplateApi.TemplateDetail[],
+  existingValues?: Map<number, string>,
+) {
+  // 鍏堟瀯寤哄熀纭�瀛楁锛坮ecordNo, remark锛�
+  const baseSchemas: VbenFormSchema[] = [
+    {
+      fieldName: '_recordNo',
+      label: '璁板綍搴忓彿',
+      component: 'Input',
+      componentProps: { placeholder: '璇疯緭鍏ヨ褰曞簭鍙�/鎵规鍙凤紙鍙�夛級' },
+    },
+    {
+      fieldName: '_remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: { placeholder: '璇疯緭鍏ュ娉紙鍙�夛級', rows: 2 },
+    },
+  ];
+
+  if (!details || details.length === 0) {
+    await paramFormApi.setState({ schema: baseSchemas });
+    return;
+  }
+
+  const paramSchemas: VbenFormSchema[] = details
+    .filter((d) => d != null)
+    .map((d) => {
+      const paramType = d.paramType ?? 1;
+      const isRequired = d.required ?? false;
+      const unitSuffix = d.unitMeasureName ? ` (${d.unitMeasureName})` : '';
+      const paramName = d.paramName ?? '';
+      const templateDetailId = d.id;
+
+      let component = 'Input';
+      const componentProps: Record<string, any> = {
+        placeholder: `璇疯緭鍏�${paramName}${unitSuffix}`,
+        maxLength: 100,
+      };
+
+      switch (paramType) {
+        case 1:
+        case 2:
+          component = 'InputNumber';
+          componentProps.controls = false;
+          componentProps.style = 'width: 100%';
+          break;
+        case 3:
+          component = 'Select';
+          componentProps.allowClear = true;
+          componentProps.options = [];
+          break;
+        case 4:
+          component = 'TimePicker';
+          componentProps.format = 'HH:mm:ss';
+          componentProps.valueFormat = 'HH:mm:ss';
+          break;
+        case 5:
+          component = 'DatePicker';
+          componentProps.format = 'YYYY-MM-DD';
+          componentProps.valueFormat = 'YYYY-MM-DD';
+          break;
+      }
+
+      let rules: any;
+      if (isRequired) {
+        if (paramType === 1 || paramType === 2) {
+          rules = z.number({ message: `${paramName}涓嶈兘涓虹┖` });
+        } else {
+          rules = z.string({ message: `${paramName}涓嶈兘涓虹┖` }).min(1, `${paramName}涓嶈兘涓虹┖`);
+        }
+      }
+
+      return {
+        fieldName: `param_${templateDetailId}`,
+        label: `${paramName}${unitSuffix}`,
+        component,
+        componentProps,
+        rules,
+        defaultValue: existingValues?.get(templateDetailId!),
+      } as VbenFormSchema;
+    });
+
+  await paramFormApi.resetForm();
+  await nextTick();
+  await paramFormApi.setState({ schema: [...baseSchemas, ...paramSchemas] });
+}
+
+/** 鏂板鍙傛暟璁板綍 */
+async function handleCreate() {
+  mode.value = 'create';
+  editingRecordId.value = undefined;
+  selectedTemplate.value = undefined;
+  templateDetails.value = [];
+  await paramFormApi.setState({ schema: [] });
+  await paramFormApi.resetForm();
+}
+
+/** 鎵撳紑妯℃澘閫夋嫨 */
+function handleSelectTemplate() {
+  tplSelectDialogRef.value?.open();
+}
+
+/** 鍙栨秷鏂板/缂栬緫 */
+function handleCancelForm() {
+  mode.value = 'list';
+  editingRecordId.value = undefined;
+  selectedTemplate.value = undefined;
+  templateDetails.value = [];
+  paramFormApi.resetForm();
+}
+
+/** 淇濆瓨鍙傛暟璁板綍 */
+async function handleSave() {
+  if (!selectedTemplate.value || !props.workOrderId) return;
+  const { valid } = await paramFormApi.validate();
+  if (!valid) {
+    message.warning('璇锋鏌ュ繀濉弬鏁�');
+    return;
+  }
+  const values = await paramFormApi.getValues();
+
+  // 鏋勫缓 details 鏁扮粍
+  const details = templateDetails.value
+    .filter((d) => d != null && d.id != null)
+    .map((d) => ({
+      templateDetailId: d.id!,
+      paramValue: values[`param_${d.id}`] != null ? String(values[`param_${d.id}`]) : undefined,
+    }));
+
+  // 鑾峰彇琛ㄥ崟涓殑 recordNo 鍜� remark
+  const recordNo = values._recordNo as string | undefined;
+  const remark = values._remark as string | undefined;
+
+  try {
+    if (mode.value === 'edit' && editingRecordId.value) {
+      await updateWorkOrderParamRecord({
+        id: editingRecordId.value,
+        templateId: selectedTemplate.value.id,
+        recordNo,
+        remark,
+        details,
+      });
+      message.success('鏇存柊鎴愬姛');
+    } else {
+      await createWorkOrderParamRecord(
+        { templateId: selectedTemplate.value.id, recordNo, remark, details },
+        props.workOrderId,
+      );
+      message.success('鍒涘缓鎴愬姛');
+    }
+    handleCancelForm();
+    refreshRecordList();
+  } catch {
+    message.error('淇濆瓨澶辫触');
+  }
+}
+
+/** 缂栬緫宸叉湁璁板綍 */
+async function handleEditRecord(recordId: number) {
+  try {
+    const record = await getWorkOrderParamRecord(recordId);
+    if (!record.templateId) return;
+    mode.value = 'edit';
+    editingRecordId.value = record.id;
+
+    // 鍔犺浇妯℃澘
+    const tpl = await getTemplate(record.templateId);
+    selectedTemplate.value = tpl;
+    templateDetails.value = tpl.details ?? [];
+
+    // 鏋勫缓宸叉湁鍊兼槧灏�
+    const valueMap = new Map<number, string>();
+    for (const d of record.details ?? []) {
+      if (d.templateDetailId != null && d.paramValue != null) {
+        valueMap.set(d.templateDetailId, d.paramValue);
+      }
+    }
+
+    await buildParamForm(templateDetails.value, valueMap);
+
+    // 璁剧疆 recordNo 鍜� remark
+    await paramFormApi.setFieldValue('_recordNo', record.recordNo);
+    await paramFormApi.setFieldValue('_remark', record.remark);
+  } catch {
+    message.error('鍔犺浇璁板綍璇︽儏澶辫触');
+  }
+}
+
+/** 鍒犻櫎璁板綍 */
+async function handleDeleteRecord(recordId: number) {
+  try {
+    await deleteWorkOrderParamRecord(recordId);
+    message.success('鍒犻櫎鎴愬姛');
+    refreshRecordList();
+  } catch {
+    message.error('鍒犻櫎澶辫触');
+  }
+}
+
+// --- 璁板綍鍒楄〃 ---
+const recordList = ref<MesProWorkOrderParamRecordApi.WorkOrderParamRecord[]>([]);
+const recordLoading = ref(false);
+
+async function refreshRecordList() {
+  if (!props.workOrderId) {
+    recordList.value = [];
+    return;
+  }
+  recordLoading.value = true;
+  try {
+    const result = await getWorkOrderParamRecordPage({
+      workOrderId: props.workOrderId,
+      pageNo: 1,
+      pageSize: 100,
+    });
+    recordList.value = result.list ?? [];
+  } finally {
+    recordLoading.value = false;
+  }
+}
+
+// 宸ュ崟鍙樻洿鏃跺姞杞借褰�
+watch(
+  () => props.workOrderId,
+  () => {
+    handleCancelForm();
+    refreshRecordList();
+  },
+);
+
+/** 鏍煎紡鍖栧弬鏁板�煎睍绀� */
+function formatParamDisplay(detail: MesProWorkOrderParamRecordApi.ParamRecordDetail) {
+  const unit = detail.unitMeasureName ? ` ${detail.unitMeasureName}` : '';
+  return `${detail.paramName}: ${detail.paramValue ?? '-'}${unit}`;
+}
+</script>
+
+<template>
+  <div class="record-param-tab">
+    <!-- 椤堕儴鎿嶄綔鏍� -->
+    <div class="record-param-tab__toolbar">
+      <div class="record-param-tab__toolbar-left">
+        <IconifyIcon icon="lucide:file-cog" class="record-param-tab__toolbar-icon" />
+        <span class="record-param-tab__toolbar-title">鐢熶骇璁板綍鍙傛暟</span>
+      </div>
+      <Button
+        v-if="mode === 'list'"
+        type="primary"
+        size="small"
+        :disabled="!workOrderId"
+        @click="handleCreate"
+      >
+        <template #icon><IconifyIcon icon="lucide:plus" /></template>
+        鏂板鍙傛暟璁板綍
+      </Button>
+      <div v-else class="record-param-tab__toolbar-actions">
+        <Tag :color="mode === 'create' ? 'blue' : 'orange'">
+          {{ mode === 'create' ? '鏂板缓' : '缂栬緫' }}
+        </Tag>
+        <Button type="primary" size="small" @click="handleSave">
+          <template #icon><IconifyIcon icon="lucide:save" /></template>
+          淇濆瓨
+        </Button>
+        <Button size="small" @click="handleCancelForm">鍙栨秷</Button>
+      </div>
+    </div>
+
+    <!-- 琛ㄥ崟鍖哄煙锛堟柊澧�/缂栬緫妯″紡锛� -->
+    <div v-if="mode !== 'list'" class="record-param-tab__form-card">
+      <!-- 妯℃澘閫夋嫨 -->
+      <div class="record-param-tab__template-row">
+        <span class="record-param-tab__template-label">鍙傛暟妯℃澘锛�</span>
+        <template v-if="selectedTemplate">
+          <Tag color="green">{{ selectedTemplate.templateName }}</Tag>
+          <span class="record-param-tab__template-code">{{ selectedTemplate.templateCode }}</span>
+          <Button
+            v-if="mode === 'create'"
+            size="small"
+            type="link"
+            @click="handleSelectTemplate"
+          >
+            鏇存崲妯℃澘
+          </Button>
+        </template>
+        <Button v-else type="dashed" size="small" @click="handleSelectTemplate">
+          <template #icon><IconifyIcon icon="lucide:folder-open" /></template>
+          閫夋嫨妯℃澘
+        </Button>
+      </div>
+
+      <!-- 璁板綍缂栧彿 + 澶囨敞 -->
+      <div class="record-param-tab__meta-row">
+        <ParamForm />
+      </div>
+
+      <!-- 鏃犳ā鏉挎彁绀� -->
+      <div v-if="!selectedTemplate" class="record-param-tab__no-template">
+        <IconifyIcon icon="lucide:arrow-up-left" class="record-param-tab__no-template-icon" />
+        <span>璇峰厛閫夋嫨涓�涓弬鏁版ā鏉�</span>
+      </div>
+
+      <!-- 鍙傛暟琛ㄥ崟宸插姞杞� -->
+      <div
+        v-else-if="templateDetails.length > 0"
+        class="record-param-tab__param-info"
+      >
+        <span class="record-param-tab__param-count">
+          鍏� {{ templateDetails.length }} 椤瑰弬鏁帮紝
+          {{ templateDetails.filter((d) => d.required).length }} 椤瑰繀濉�
+        </span>
+      </div>
+      <div v-else class="record-param-tab__no-template">
+        <span>璇ユā鏉挎殏鏃犲弬鏁版槑缁�</span>
+      </div>
+    </div>
+
+    <Divider style="margin: 12px 0" />
+
+    <!-- 璁板綍鍒楄〃 -->
+    <div class="record-param-tab__list">
+      <div class="record-param-tab__list-header">
+        <span class="record-param-tab__list-title">
+          鍙傛暟璁板綍
+          <span class="record-param-tab__list-count">({{ recordList.length }})</span>
+        </span>
+        <Button
+          v-if="workOrderId"
+          size="small"
+          :loading="recordLoading"
+          @click="refreshRecordList"
+        >
+          <template #icon><IconifyIcon icon="lucide:refresh-cw" /></template>
+          鍒锋柊
+        </Button>
+      </div>
+
+      <!-- 鏃犲伐鍗曟彁绀� -->
+      <div v-if="!workOrderId" class="record-param-tab__empty">
+        <IconifyIcon icon="lucide:alert-circle" class="record-param-tab__empty-icon" />
+        <p>璇峰厛浠庡乏渚ч�夋嫨涓�鏉″伐鍗曚换鍔�</p>
+      </div>
+
+      <!-- 绌哄垪琛� -->
+      <div v-else-if="recordList.length === 0" class="record-param-tab__empty">
+        <IconifyIcon icon="lucide:inbox" class="record-param-tab__empty-icon" />
+        <p>鏆傛棤鍙傛暟璁板綍锛岀偣鍑讳笂鏂�"鏂板鍙傛暟璁板綍"寮�濮�</p>
+      </div>
+
+      <!-- 璁板綍鍗$墖鍒楄〃 -->
+      <div v-else class="record-param-tab__cards">
+        <div
+          v-for="record in recordList"
+          :key="record.id"
+          class="record-param-tab__card"
+        >
+          <div class="record-param-tab__card-head">
+            <div class="record-param-tab__card-meta">
+              <Tag color="green">{{ record.templateName }}</Tag>
+              <span v-if="record.recordNo" class="record-param-tab__card-no">
+                {{ record.recordNo }}
+              </span>
+              <span class="record-param-tab__card-time">{{ record.createTime }}</span>
+            </div>
+            <div class="record-param-tab__card-actions">
+              <Button type="link" size="small" @click="handleEditRecord(record.id!)">
+                缂栬緫
+              </Button>
+              <Popconfirm
+                title="纭鍒犻櫎璇ュ弬鏁拌褰曪紵鍒犻櫎鍚庝笉鍙仮澶�"
+                @confirm="handleDeleteRecord(record.id!)"
+              >
+                <Button type="link" size="small" danger>鍒犻櫎</Button>
+              </Popconfirm>
+            </div>
+          </div>
+          <div v-if="record.remark" class="record-param-tab__card-remark">
+            澶囨敞锛歿{ record.remark }}
+          </div>
+          <div class="record-param-tab__card-params">
+            <span
+              v-for="d in record.details"
+              :key="d.id"
+              class="record-param-tab__param-tag"
+            >
+              {{ formatParamDisplay(d) }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 妯℃澘閫夋嫨寮圭獥 -->
+    <TemplateSelectDialog
+      ref="tplSelectDialogRef"
+      @select="handleTemplateSelect"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.record-param-tab {
+  padding: 20px 24px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+}
+
+// --- 椤堕儴宸ュ叿鏍� ---
+.record-param-tab__toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  flex-shrink: 0;
+}
+
+.record-param-tab__toolbar-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.record-param-tab__toolbar-icon {
+  font-size: 22px;
+  color: #22c55e;
+}
+
+.record-param-tab__toolbar-title {
+  font-size: 17px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.record-param-tab__toolbar-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+// --- 琛ㄥ崟鍗$墖 ---
+.record-param-tab__form-card {
+  margin-top: 16px;
+  padding: 16px 20px;
+  background: #fafbfc;
+  border-radius: 10px;
+  border: 1px solid #e8e8e8;
+  flex-shrink: 0;
+}
+
+.record-param-tab__template-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.record-param-tab__template-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #595959;
+  flex-shrink: 0;
+}
+
+.record-param-tab__template-code {
+  font-size: 12px;
+  color: #8c8c8c;
+}
+
+.record-param-tab__meta-row {
+  margin-top: 8px;
+}
+
+.record-param-tab__no-template {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 20px 0;
+  color: #8c8c8c;
+  font-size: 14px;
+  justify-content: center;
+}
+
+.record-param-tab__no-template-icon {
+  font-size: 18px;
+  opacity: 0.6;
+}
+
+.record-param-tab__param-info {
+  margin-top: 8px;
+}
+
+.record-param-tab__param-count {
+  font-size: 13px;
+  color: #8c8c8c;
+}
+
+// --- 璁板綍鍒楄〃 ---
+.record-param-tab__list {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.record-param-tab__list-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+  flex-shrink: 0;
+}
+
+.record-param-tab__list-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.record-param-tab__list-count {
+  font-size: 13px;
+  font-weight: 400;
+  color: #8c8c8c;
+  margin-left: 6px;
+}
+
+// --- 绌虹姸鎬� ---
+.record-param-tab__empty {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  color: #8c8c8c;
+
+  p {
+    margin: 0;
+    font-size: 14px;
+  }
+}
+
+.record-param-tab__empty-icon {
+  font-size: 36px;
+  opacity: 0.4;
+}
+
+// --- 璁板綍鍗$墖 ---
+.record-param-tab__cards {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  overflow: auto;
+}
+
+.record-param-tab__card {
+  padding: 12px 16px;
+  background: #fff;
+  border: 1px solid #f0f0f0;
+  border-radius: 8px;
+  transition: box-shadow 0.2s;
+
+  &:hover {
+    box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
+  }
+}
+
+.record-param-tab__card-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.record-param-tab__card-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.record-param-tab__card-no {
+  font-size: 13px;
+  font-weight: 500;
+  color: #595959;
+  padding: 2px 8px;
+  background: #f5f5f5;
+  border-radius: 4px;
+}
+
+.record-param-tab__card-time {
+  font-size: 12px;
+  color: #8c8c8c;
+}
+
+.record-param-tab__card-actions {
+  display: flex;
+  gap: 4px;
+  flex-shrink: 0;
+}
+
+.record-param-tab__card-remark {
+  margin-top: 6px;
+  font-size: 13px;
+  color: #8c8c8c;
+}
+
+.record-param-tab__card-params {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+  margin-top: 8px;
+}
+
+.record-param-tab__param-tag {
+  display: inline-flex;
+  align-items: center;
+  padding: 3px 10px;
+  border-radius: 4px;
+  font-size: 12px;
+  background: #f6ffed;
+  color: #389e0d;
+  border: 1px solid #d9f7be;
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
new file mode 100644
index 0000000..50ca5ef
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
@@ -0,0 +1,409 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+
+import { h, markRaw, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+  MesWmReturnIssueStatusEnum,
+} from '@vben/constants';
+import { IconifyIcon } from '@vben/icons';
+import { getDictOptions } from '@vben/hooks';
+
+import { Button, Divider, message, Popconfirm, Tag } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import {
+  cancelReturnIssue,
+  createReturnIssue,
+  deleteReturnIssue,
+  getReturnIssuePage,
+  submitReturnIssue,
+  updateReturnIssue,
+} from '#/api/mes/wm/returnissue';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { $t } from '#/locales';
+
+defineOptions({ name: 'WorkbenchReturnIssueTab' });
+
+const props = defineProps<{
+  /** 褰撳墠宸ヤ綔绔� ID */
+  workstationId?: number;
+  /** 褰撳墠宸ヤ綔绔欏悕绉� */
+  workstationName?: string;
+}>();
+
+// --- 琛ㄥ崟鐘舵�� ---
+const editingId = ref<number>();
+const isCreating = ref(false);
+
+/** 鏋勫缓琛ㄥ崟 schema */
+function buildFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫�鏂欏崟缂栧彿',
+      component: 'Input',
+      componentProps: { placeholder: '璇疯緭鍏ラ��鏂欏崟缂栧彿' },
+      rules: 'required',
+      suffix: () =>
+        h(
+          Button,
+          {
+            type: 'default',
+            size: 'small',
+            onClick: async () => {
+              const code = await generateAutoCode(
+                MesAutoCodeRuleCode.WM_RETURN_ISSUE_CODE,
+              );
+              await formApi.setFieldValue('code', code);
+            },
+          },
+          { default: () => '鐢熸垚' },
+        ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫�鏂欏崟鍚嶇О',
+      component: 'Input',
+      componentProps: { placeholder: '璇疯緭鍏ラ��鏂欏崟鍚嶇О' },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '閫�鏂欑被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨閫�鏂欑被鍨�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'workstationId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: { placeholder: '璇烽�夋嫨宸ヤ綔绔�' },
+    },
+    {
+      fieldName: 'returnDate',
+      label: '閫�鏂欐棩鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 2 },
+    },
+  ];
+}
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: buildFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鏂板缓閫�鏂欏崟 */
+async function handleCreate() {
+  isCreating.value = true;
+  editingId.value = undefined;
+  await formApi.resetForm();
+  if (props.workstationId) {
+    await formApi.setFieldValue('workstationId', props.workstationId);
+  }
+}
+
+/** 淇濆瓨閫�鏂欏崟 */
+async function handleSave() {
+  const { valid } = await formApi.validate();
+  if (!valid) return;
+  const data =
+    (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+  try {
+    if (editingId.value) {
+      await updateReturnIssue({ ...data, id: editingId.value });
+      message.success('淇敼鎴愬姛');
+    } else {
+      const id = await createReturnIssue(data);
+      editingId.value = id;
+      await formApi.setFieldValue('id', id);
+      message.success('鍒涘缓鎴愬姛');
+    }
+    isCreating.value = false;
+    gridApi.query();
+  } catch {
+    message.error('淇濆瓨澶辫触');
+  }
+}
+
+/** 鍙栨秷缂栬緫 */
+function handleCancelEdit() {
+  isCreating.value = false;
+  editingId.value = undefined;
+  formApi.resetForm();
+}
+
+/** 鎻愪氦 */
+async function handleSubmitRow(row: MesWmReturnIssueApi.ReturnIssue) {
+  await submitReturnIssue(row.id!);
+  message.success('鎻愪氦鎴愬姛');
+  gridApi.query();
+}
+
+/** 鍒犻櫎 */
+async function handleDeleteRow(row: MesWmReturnIssueApi.ReturnIssue) {
+  await deleteReturnIssue(row.id!);
+  message.success('鍒犻櫎鎴愬姛');
+  gridApi.query();
+}
+
+/** 鍙栨秷 */
+async function handleCancelRow(row: MesWmReturnIssueApi.ReturnIssue) {
+  await cancelReturnIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  gridApi.query();
+}
+
+// --- 鍒楄〃 ---
+const gridColumns: VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>['columns'] = [
+  { field: 'code', title: '閫�鏂欏崟缂栧彿', minWidth: 150 },
+  { field: 'name', title: '閫�鏂欏崟鍚嶇О', minWidth: 130 },
+  {
+    field: 'type',
+    title: '閫�鏂欑被鍨�',
+    width: 100,
+    cellRender: {
+      name: 'CellDict',
+      props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE },
+    },
+  },
+  { field: 'workOrderCode', title: '鐢熶骇璁㈠崟', minWidth: 130 },
+  {
+    field: 'returnDate',
+    title: '閫�鏂欐棩鏈�',
+    width: 170,
+    formatter: 'formatDateTime',
+  },
+  {
+    field: 'status',
+    title: '鐘舵��',
+    width: 100,
+    cellRender: {
+      name: 'CellDict',
+      props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_STATUS },
+    },
+  },
+  { title: '鎿嶄綔', width: 200, fixed: 'right', slots: { default: 'actions' } },
+];
+
+const gridHeight = ref(300);
+const listRef = ref<HTMLElement>();
+let resizeObserver: ResizeObserver | null = null;
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: gridColumns,
+    height: gridHeight.value,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getReturnIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            workstationId: props.workstationId,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: { keyField: 'id', isHover: true },
+    toolbarConfig: { refresh: true },
+    pagerConfig: { pageSize: 10 },
+  } as VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>,
+});
+
+function updateGridHeight() {
+  if (listRef.value) {
+    const h = listRef.value.clientHeight;
+    if (h > 100 && h !== gridHeight.value) {
+      gridHeight.value = h;
+      gridApi.setGridOptions({ height: h });
+    }
+  }
+}
+
+onMounted(() => {
+  nextTick(() => {
+    updateGridHeight();
+    if (listRef.value) {
+      resizeObserver = new ResizeObserver(() => updateGridHeight());
+      resizeObserver.observe(listRef.value);
+    }
+  });
+});
+
+onBeforeUnmount(() => {
+  resizeObserver?.disconnect();
+  resizeObserver = null;
+});
+
+// 宸ヤ綔绔欏彉鍖栨椂鍒锋柊鍒楄〃
+watch(
+  () => props.workstationId,
+  () => gridApi.query(),
+);
+</script>
+
+<template>
+  <div class="issue-tab">
+    <!-- 椤堕儴锛氳〃鍗� / 鏂板缓鎸夐挳 -->
+    <div class="issue-tab__header">
+      <div class="issue-tab__header-left">
+        <IconifyIcon icon="lucide:rotate-ccw" class="issue-tab__icon" />
+        <span class="issue-tab__title">閫�鏂欏崟</span>
+        <Tag v-if="isCreating" color="orange">鏂板缓涓�</Tag>
+      </div>
+      <div class="issue-tab__header-actions">
+        <template v-if="!isCreating">
+          <Button type="primary" size="small" @click="handleCreate">
+            <template #icon><IconifyIcon icon="lucide:plus" /></template>
+            鏂板缓閫�鏂欏崟
+          </Button>
+        </template>
+        <template v-else>
+          <Button type="primary" size="small" @click="handleSave">
+            <template #icon><IconifyIcon icon="lucide:save" /></template>
+            淇濆瓨
+          </Button>
+          <Button size="small" @click="handleCancelEdit">鍙栨秷</Button>
+        </template>
+      </div>
+    </div>
+
+    <!-- 琛ㄥ崟鍖哄煙锛堜粎鏂板缓鏃跺睍绀猴級 -->
+    <div v-if="isCreating" class="issue-tab__form">
+      <Form />
+    </div>
+
+    <Divider style="margin: 12px 0" />
+
+    <!-- 搴曢儴锛氶��鏂欒褰曞垪琛� -->
+    <div ref="listRef" class="issue-tab__list">
+      <Grid table-title="閫�鏂欒褰�">
+        <template #actions="{ row }">
+          <Popconfirm
+            v-if="row.status === MesWmReturnIssueStatusEnum.PREPARE"
+            title="纭鎻愪氦锛熸彁浜ゅ悗涓嶈兘淇敼"
+            @confirm="handleSubmitRow(row)"
+          >
+            <Button type="link" size="small">鎻愪氦</Button>
+          </Popconfirm>
+          <Popconfirm
+            v-if="row.status === MesWmReturnIssueStatusEnum.PREPARE"
+            :title="`纭鍒犻櫎 ${row.code}锛焋"
+            @confirm="handleDeleteRow(row)"
+          >
+            <Button type="link" size="small" danger>鍒犻櫎</Button>
+          </Popconfirm>
+          <Popconfirm
+            v-if="
+              row.status === MesWmReturnIssueStatusEnum.CONFIRMED ||
+              row.status === MesWmReturnIssueStatusEnum.APPROVING ||
+              row.status === MesWmReturnIssueStatusEnum.APPROVED
+            "
+            title="纭鍙栨秷锛熷彇娑堝悗涓嶅彲鎭㈠"
+            @confirm="handleCancelRow(row)"
+          >
+            <Button type="link" size="small" danger>鍙栨秷</Button>
+          </Popconfirm>
+        </template>
+      </Grid>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.issue-tab {
+  padding: 20px 24px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.issue-tab__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.issue-tab__header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.issue-tab__icon {
+  font-size: 22px;
+  color: #fa8c16;
+}
+
+.issue-tab__title {
+  font-size: 17px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.issue-tab__header-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.issue-tab__form {
+  margin-top: 16px;
+  padding: 16px;
+  background: #fafbfc;
+  border-radius: 10px;
+  border: 1px solid #f0f0f0;
+}
+
+.issue-tab__list {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+</style>
diff --git a/src/views/mes/workbench/components/tabs/TaskDetailTab.vue b/src/views/mes/workbench/components/tabs/TaskDetailTab.vue
new file mode 100644
index 0000000..68e1a68
--- /dev/null
+++ b/src/views/mes/workbench/components/tabs/TaskDetailTab.vue
@@ -0,0 +1,190 @@
+<script lang="ts" setup>
+import type { PendingTask } from '../../../types';
+
+import { Descriptions, Progress, Tag } from 'ant-design-vue';
+
+defineOptions({ name: 'WorkbenchTaskDetailTab' });
+
+defineProps<{
+  task?: PendingTask | null;
+}>();
+
+function getProgressStatus(rate?: number): 'exception' | 'normal' | 'success' {
+  if (rate === undefined || rate === null) return 'normal';
+  if (rate >= 100) return 'success';
+  if (rate < 0) return 'exception';
+  return 'normal';
+}
+</script>
+
+<template>
+  <div class="task-detail-tab">
+    <div v-if="!task" class="task-detail-tab__empty">
+      璇蜂粠宸︿晶宸ュ崟鍒楄〃閫夋嫨涓�鏉′换鍔�
+    </div>
+    <template v-else>
+      <!-- 杩涘害姒傝 -->
+      <div class="detail-progress">
+        <div class="detail-progress__head">
+          <span class="detail-progress__title">鎶ュ伐杩涘害</span>
+          <span class="detail-progress__rate">{{ task.feedbackRate ?? 0 }}%</span>
+          <span class="detail-progress__qty">
+            {{ task.producedQuantity ?? 0 }} / {{ task.quantity ?? 0 }}
+            {{ task.unitMeasureName }}
+          </span>
+        </div>
+        <Progress
+          :percent="task.feedbackRate ?? 0"
+          :status="getProgressStatus(task.feedbackRate)"
+          :stroke-color="{ from: '#108ee9', to: '#87d068' }"
+          :show-info="false"
+          :stroke-width="14"
+        />
+        <div class="detail-progress__stats">
+          <div class="stat-item">
+            <span class="stat-item__label">鎺掍骇鏁伴噺</span>
+            <span class="stat-item__value">{{ task.quantity ?? 0 }}</span>
+          </div>
+          <div class="stat-item">
+            <span class="stat-item__label">宸茬敓浜�</span>
+            <span class="stat-item__value">{{ task.producedQuantity ?? 0 }}</span>
+          </div>
+          <div class="stat-item">
+            <span class="stat-item__label">鍦ㄩ�旀姤宸�</span>
+            <span class="stat-item__value">{{ task.inTransitQuantity ?? 0 }}</span>
+          </div>
+          <div class="stat-item">
+            <span class="stat-item__label">鍙姤宸�</span>
+            <span class="stat-item__value stat-item__value--primary">
+              {{ task.reportableQuantity ?? 0 }}
+            </span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 璇︾粏淇℃伅 -->
+      <Descriptions
+        bordered
+        :column="2"
+        size="middle"
+        class="detail-desc"
+      >
+        <Descriptions.Item label="浠诲姟缂栫爜">{{ task.code ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="浠诲姟鍚嶇О">{{ task.name ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="宸ュ崟缂栫爜">{{ task.workOrderCode ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="宸ュ崟鍚嶇О">{{ task.workOrderName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="宸ヤ綔绔�">{{ task.workstationName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="宸ュ簭">{{ task.processName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="浜у搧缂栫爜">{{ task.itemCode ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="浜у搧鍚嶇О">{{ task.itemName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="瑙勬牸鍨嬪彿">{{ task.itemSpecification ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="璁¢噺鍗曚綅">{{ task.unitMeasureName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="鍚堟牸鍝佹暟閲�">{{ task.qualifyQuantity ?? 0 }}</Descriptions.Item>
+        <Descriptions.Item label="涓嶈壇鍝佹暟閲�">{{ task.unqualifyQuantity ?? 0 }}</Descriptions.Item>
+        <Descriptions.Item label="瀹㈡埛鍚嶇О">{{ task.clientName ?? '鈥�' }}</Descriptions.Item>
+        <Descriptions.Item label="鏄惁璐ㄦ宸ュ簭">
+          <Tag v-if="task.checkFlag" color="orange">鏄�</Tag>
+          <Tag v-else>鍚�</Tag>
+        </Descriptions.Item>
+        <Descriptions.Item label="闇�姹傛棩鏈�" :span="2">
+          {{ task.requestDate ?? '鈥�' }}
+        </Descriptions.Item>
+        <Descriptions.Item label="澶囨敞" :span="2">{{ task.remark ?? '鈥�' }}</Descriptions.Item>
+      </Descriptions>
+    </template>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.task-detail-tab {
+  padding: 20px 24px;
+  min-height: 100%;
+  height: 100%;
+  overflow: auto;
+}
+
+.task-detail-tab__empty {
+  padding: 64px 0;
+  text-align: center;
+  color: #8c8c8c;
+  font-size: 16px;
+}
+
+.detail-progress {
+  margin-bottom: 20px;
+  padding: 16px 20px;
+  border-radius: 8px;
+  background: #f6f8fa;
+  border: 1px solid #e8e8e8;
+}
+
+.detail-progress__head {
+  display: flex;
+  align-items: baseline;
+  flex-wrap: wrap;
+  gap: 8px 16px;
+  margin-bottom: 12px;
+}
+
+.detail-progress__title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.detail-progress__rate {
+  font-size: 22px;
+  font-weight: 700;
+  color: var(--ant-primary-color, #1890ff);
+  font-variant-numeric: tabular-nums;
+}
+
+.detail-progress__qty {
+  margin-left: auto;
+  font-size: 14px;
+  font-weight: 600;
+  color: #595959;
+}
+
+.detail-progress__stats {
+  display: flex;
+  gap: 24px;
+  margin-top: 14px;
+  flex-wrap: wrap;
+}
+
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+
+  &__label {
+    font-size: 12px;
+    color: #8c8c8c;
+    font-weight: 500;
+  }
+
+  &__value {
+    font-size: 18px;
+    font-weight: 700;
+    color: #1a1a1a;
+    font-variant-numeric: tabular-nums;
+
+    &--primary {
+      color: var(--ant-primary-color, #1890ff);
+    }
+  }
+}
+
+.detail-desc {
+  :deep(.ant-descriptions-item-label) {
+    width: 140px;
+    font-weight: 600;
+    background: #fafafa;
+  }
+
+  :deep(.ant-descriptions-item-content) {
+    font-size: 14px;
+  }
+}
+</style>
diff --git a/src/views/mes/workbench/index.vue b/src/views/mes/workbench/index.vue
new file mode 100644
index 0000000..4fc3302
--- /dev/null
+++ b/src/views/mes/workbench/index.vue
@@ -0,0 +1,430 @@
+<script lang="ts" setup>
+import type { MesMdWorkstationApi } from '#/api/mes/md/workstation';
+import type { PendingTask, WorkbenchTabItem, WorkbenchTabKey } from './types';
+
+import { computed, ref, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Button } from 'ant-design-vue';
+
+import { getPendingFeedbackPage } from '#/api/mes/pro/task';
+import { router } from '#/router';
+
+import TaskListPanel from './components/TaskListPanel.vue';
+import WorkstationPanel from './components/WorkstationPanel.vue';
+import WorkstationSelectDialog from './components/WorkstationSelectDialog.vue';
+import FeedbackHistoryTab from './components/tabs/FeedbackHistoryTab.vue';
+import FeedbackTab from './components/tabs/FeedbackTab.vue';
+import ProductIssueTab from './components/tabs/ProductIssueTab.vue';
+import RecordParamTab from './components/tabs/RecordParamTab.vue';
+import ReturnIssueTab from './components/tabs/ReturnIssueTab.vue';
+import TaskDetailTab from './components/tabs/TaskDetailTab.vue';
+
+defineOptions({ name: 'MesWorkbench' });
+
+// --- Tab 閰嶇疆 ---
+const tabItems: WorkbenchTabItem[] = [
+  { key: 'detail', label: '宸ュ崟璇︽儏', icon: 'lucide:file-text' },
+  { key: 'feedback', label: '鎶ュ伐', icon: 'lucide:edit-3' },
+  { key: 'history', label: '鎶ュ伐璁板綍', icon: 'lucide:history' },
+  { key: 'record-param', label: '鐢熶骇璁板綍鍙傛暟', icon: 'lucide:file-cog' },
+  { key: 'product-issue', label: '棰嗘枡', icon: 'lucide:clipboard-list' },
+  { key: 'return-issue', label: '閫�鏂�', icon: 'lucide:rotate-ccw' },
+];
+
+const activeTab = ref<WorkbenchTabKey>('detail');
+const activeTabLabel = computed(
+  () => tabItems.find((t) => t.key === activeTab.value)?.label ?? '',
+);
+
+// --- 宸ヤ綔绔� ---
+const workstation = ref<MesMdWorkstationApi.Workstation>();
+const workstationId = computed(() => workstation.value?.id);
+const workstationName = computed(() => workstation.value?.name ?? '');
+const wsSelectDialogRef = ref<InstanceType<typeof WorkstationSelectDialog>>();
+
+function openWorkstationSelect() {
+  wsSelectDialogRef.value?.open();
+}
+
+function handleWorkstationSelect(ws: MesMdWorkstationApi.Workstation) {
+  workstation.value = ws;
+}
+
+// --- 浠诲姟鍒楄〃 ---
+const taskList = ref<PendingTask[]>([]);
+const taskLoading = ref(false);
+const selectedTask = ref<PendingTask>();
+
+async function loadTaskList() {
+  if (!workstationId.value) {
+    taskList.value = [];
+    selectedTask.value = undefined;
+    return;
+  }
+  taskLoading.value = true;
+  try {
+    const result = await getPendingFeedbackPage({
+      workstationId: workstationId.value,
+      pageNo: 1,
+      pageSize: 200,
+    });
+    taskList.value = result.list ?? [];
+    // 鑷姩閫変腑骞跺悓姝� selectedTask 鍒版渶鏂版暟鎹�
+    if (taskList.value.length) {
+      if (selectedTask.value) {
+        // 灏濊瘯鍦ㄦ柊鍒楄〃涓壘鍒板悓 ID 鐨勪换鍔★紝鏇存柊寮曠敤浠ュ埛鏂版暟鎹�
+        const updated = taskList.value.find(
+          (t) => t.id === selectedTask.value!.id,
+        );
+        selectedTask.value = updated ?? taskList.value[0];
+      } else {
+        selectedTask.value = taskList.value[0];
+      }
+    } else {
+      selectedTask.value = undefined;
+    }
+  } finally {
+    taskLoading.value = false;
+  }
+}
+
+// 宸ヤ綔绔欏彉鏇存椂閲嶆柊鍔犺浇浠诲姟鍒楄〃
+watch(workstationId, () => {
+  loadTaskList();
+});
+
+function handleSelectTask(task: PendingTask) {
+  selectedTask.value = task;
+}
+
+// --- 鎶ュ伐鍒锋柊 ---
+const reportRefreshKey = ref(0);
+
+function onReported() {
+  reportRefreshKey.value += 1;
+  // 鍒锋柊浠诲姟鍒楄〃浠ユ洿鏂拌繘搴�
+  loadTaskList();
+}
+
+// --- 杩斿洖绯荤粺 ---
+function goBack() {
+  router.push('/');
+}
+</script>
+
+<template>
+  <div class="workbench-page">
+    <!-- 椤堕儴瀵艰埅鏍� -->
+    <header class="workbench-topbar">
+      <div class="workbench-topbar__brand">
+        <IconifyIcon icon="lucide:factory" class="workbench-topbar__logo" />
+        <div class="workbench-topbar__brand-text">
+          <span class="workbench-topbar__title">鐢熶骇宸ヤ綔鍙�</span>
+          <span v-if="workstationName" class="workbench-topbar__sub">
+            {{ workstationName }}
+          </span>
+        </div>
+      </div>
+
+      <nav class="workbench-nav">
+        <button
+          v-for="tab in tabItems"
+          :key="tab.key"
+          type="button"
+          class="workbench-nav__item"
+          :class="{ 'workbench-nav__item--active': activeTab === tab.key }"
+          @click="activeTab = tab.key"
+        >
+          <IconifyIcon :icon="tab.icon" class="workbench-nav__icon" />
+          {{ tab.label }}
+        </button>
+      </nav>
+
+      <Button class="workbench-back-btn" @click="goBack">
+        <template #icon>
+          <IconifyIcon icon="lucide:arrow-left" />
+        </template>
+        杩斿洖绯荤粺
+      </Button>
+    </header>
+
+    <!-- 涓讳綋鍖哄煙 -->
+    <div class="workbench-body">
+      <!-- 宸︿晶杈规爮 -->
+      <aside class="workbench-aside">
+        <!-- 宸ヤ綔绔欓�夋嫨闈㈡澘 -->
+        <WorkstationPanel
+          :workstation="workstation"
+          @change="openWorkstationSelect"
+        />
+
+        <!-- 宸ュ崟鍒楄〃 -->
+        <TaskListPanel
+          :list="taskList"
+          :selected-id="selectedTask?.id"
+          :loading="taskLoading"
+          @select="handleSelectTask"
+        />
+      </aside>
+
+      <!-- 鍒嗗壊绾挎嫋鎷芥寜閽紙瑙嗚瑁呴グ锛� -->
+      <div class="workbench-split">
+        <div class="workbench-split__line" />
+      </div>
+
+      <!-- 鍙充晶涓诲尯鍩� -->
+      <main class="workbench-main">
+        <div class="workbench-main__head">
+          <span class="workbench-main__tab-title">{{ activeTabLabel }}</span>
+          <span v-if="selectedTask?.workOrderCode" class="workbench-main__context">
+            宸ュ崟 {{ selectedTask.workOrderCode }}
+          </span>
+        </div>
+        <div class="workbench-main__body">
+          <TaskDetailTab
+            v-if="activeTab === 'detail'"
+            :task="selectedTask"
+          />
+          <FeedbackTab
+            v-else-if="activeTab === 'feedback'"
+            :task="selectedTask"
+            :refresh-key="reportRefreshKey"
+            @reported="onReported"
+          />
+          <FeedbackHistoryTab
+            v-else-if="activeTab === 'history'"
+            :task-id="selectedTask?.id"
+            :refresh-key="reportRefreshKey"
+            @reported="onReported"
+          />
+          <RecordParamTab
+            v-else-if="activeTab === 'record-param'"
+            :work-order-id="selectedTask?.workOrderId"
+          />
+          <ProductIssueTab
+            v-else-if="activeTab === 'product-issue'"
+            :workstation-id="workstationId"
+            :workstation-name="workstationName"
+          />
+          <ReturnIssueTab
+            v-else-if="activeTab === 'return-issue'"
+            :workstation-id="workstationId"
+            :workstation-name="workstationName"
+          />
+        </div>
+      </main>
+    </div>
+
+    <!-- 宸ヤ綔绔欓�夋嫨寮圭獥 -->
+    <WorkstationSelectDialog
+      ref="wsSelectDialogRef"
+      @select="handleWorkstationSelect"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+$radius: 10px;
+$shadow: 0 2px 12px rgb(0 0 0 / 8%);
+$shadow-sm: 0 1px 4px rgb(0 0 0 / 6%);
+$aside-width: 420px;
+
+.workbench-page {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+  background: #f5f5f5;
+}
+
+// --- 椤堕儴瀵艰埅鏍� ---
+.workbench-topbar {
+  display: flex;
+  align-items: flex-end;
+  gap: 20px;
+  flex-shrink: 0;
+  min-height: 56px;
+  padding: 0 20px;
+  background: linear-gradient(
+    105deg,
+    #1a3a5c 0%,
+    #1890ff 42%,
+    #40a9ff 100%
+  );
+  color: #fff;
+  box-shadow: $shadow;
+  z-index: 10;
+}
+
+.workbench-topbar__brand {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  flex-shrink: 0;
+  padding-bottom: 10px;
+}
+
+.workbench-topbar__logo {
+  font-size: 28px;
+  filter: brightness(1.1);
+}
+
+.workbench-topbar__brand-text {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.workbench-topbar__title {
+  font-size: 18px;
+  font-weight: 600;
+  letter-spacing: 0.02em;
+}
+
+.workbench-topbar__sub {
+  font-size: 13px;
+  opacity: 0.85;
+}
+
+.workbench-nav {
+  flex: 1;
+  display: flex;
+  align-items: flex-end;
+  gap: 4px;
+  min-width: 0;
+  padding-bottom: 0;
+  overflow-x: auto;
+  scrollbar-width: none;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+
+.workbench-nav__item {
+  flex-shrink: 0;
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 10px 20px;
+  border: none;
+  border-radius: 8px 8px 0 0;
+  background: transparent;
+  color: rgb(255 255 255 / 75%);
+  font-size: 15px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+
+  &:hover:not(&--active) {
+    color: #fff;
+    background: rgb(255 255 255 / 12%);
+  }
+
+  &--active {
+    color: #1890ff;
+    background: #f5f5f5;
+    font-weight: 600;
+    box-shadow: 0 -2px 8px rgb(0 0 0 / 6%);
+  }
+}
+
+.workbench-nav__icon {
+  font-size: 18px;
+}
+
+.workbench-back-btn {
+  flex-shrink: 0;
+  margin-bottom: 8px;
+  border-color: rgb(255 255 255 / 35%) !important;
+  background: rgb(255 255 255 / 12%) !important;
+  color: #fff !important;
+
+  &:hover {
+    border-color: rgb(255 255 255 / 55%) !important;
+    background: rgb(255 255 255 / 22%) !important;
+  }
+}
+
+// --- 涓讳綋甯冨眬 ---
+.workbench-body {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  align-items: stretch;
+  padding: 12px;
+  gap: 0;
+}
+
+.workbench-aside {
+  flex-shrink: 0;
+  width: $aside-width;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  min-height: 0;
+  overflow: hidden;
+}
+
+
+.workbench-split {
+  flex-shrink: 0;
+  width: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.workbench-split__line {
+  width: 1px;
+  height: 100%;
+  background: #e8e8e8;
+}
+
+.workbench-main {
+  flex: 1;
+  margin-left: 12px;
+  min-width: 0;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  border-radius: $radius;
+  border: 1px solid #f0f0f0;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+}
+
+.workbench-main__head {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+  flex-shrink: 0;
+  padding: 14px 24px;
+  border-bottom: 1px solid #f0f0f0;
+  background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
+}
+
+.workbench-main__tab-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1890ff;
+}
+
+.workbench-main__context {
+  font-size: 14px;
+  color: #595959;
+  padding: 4px 12px;
+  background: #f0f0f0;
+  border-radius: 6px;
+  font-weight: 500;
+}
+
+.workbench-main__body {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+</style>
diff --git a/src/views/mes/workbench/types.ts b/src/views/mes/workbench/types.ts
new file mode 100644
index 0000000..8f74ad3
--- /dev/null
+++ b/src/views/mes/workbench/types.ts
@@ -0,0 +1,14 @@
+import type { MesProTaskApi } from '#/api/mes/pro/task';
+
+/** 宸ヤ綔鍙� Tab 椤甸敭 */
+export type WorkbenchTabKey = 'detail' | 'feedback' | 'history' | 'record-param' | 'product-issue' | 'return-issue';
+
+/** 宸ヤ綔鍙� Tab 椤� */
+export interface WorkbenchTabItem {
+  key: WorkbenchTabKey;
+  label: string;
+  icon: string;
+}
+
+/** 寰呮姤宸ヤ换鍔★紙浠� API 绫诲瀷閲嶅鍑猴紝渚夸簬缁勪欢寮曠敤锛� */
+export type PendingTask = MesProTaskApi.PendingFeedbackTask;

--
Gitblit v1.9.3