Przeglądaj źródła

feat(web): 管理端报告归属管理页改用 Element UI 组件
该页面从 Arco Design 迁移至 Element UI 规范,新增家庭选择过滤、归属状态标签、归属成员展示、刷新功能等。展示列表新增「调整归属」操作入口,点击后弹出归属成员选择器。

iwt 4 dni temu
rodzic
commit
f8fb51435f

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-61d304d3f5979faa75201214e75515319ecff833
+41849a766303be05fb232a81fd79fb444c98d408

+ 157 - 133
cfc-web/src/views/admin/HealthReportReassign.vue

@@ -1,151 +1,175 @@
 <template>
   <div class="reassign-container">
-    <div class="toolbar">
+    <div class="page-header">
       <span>报告归属管理</span>
-      <a-button @click="refresh">刷新</a-button>
+      <el-button type="primary" size="small" icon="el-icon-refresh" @click="loadData">刷新</el-button>
     </div>
 
-    <a-table
-      :data="reports"
-      :columns="columns"
-      :loading="loading"
-      :row-key="row => row.id"
-      @selection-change="handleSelectionChange"
-    >
-      <template #row-actions="{ row }">
-        <template v-if="row.subjectId">
-          <!-- 已绑定:显示重新分配按钮 -->
-          <a-dropdown v-dropdown="dropdown">
-            <a-trigger>
-              <a-icon type="setting" />
-            </a-trigger>
-            <a-menu>
-              <a-menu-item @click="reassignRow(row)">重新分配</a-menu-item>
-            </a-menu>
-          </a-dropdown>
+    <!-- 家庭选择 -->
+    <el-form :inline="true" class="filter-bar">
+      <el-form-item label="选择家庭">
+        <el-select v-model="familyId" placeholder="请选择家庭" filterable clearable style="width: 260px" @change="loadData">
+          <el-option v-for="f in familyList" :key="f.id" :label="f.name" :value="f.id" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="reports" v-loading="loading" border stripe>
+      <el-table-column prop="id" label="报告ID" width="100" />
+      <el-table-column prop="reportType" label="报告类型" width="120">
+        <template slot-scope="{ row }">
+          <el-tag size="small">{{ row.reportType === 'gut_flora' ? '菌群检测' : row.reportType === 'physical_exam' ? '体检报告' : row.reportType }}</el-tag>
         </template>
-        <template v-else>
-          <!-- 未绑定:显示绑定按钮 -->
-          <a-button type="primary" size="small" @click="bindRow(row)">绑定归属</a-button>
+      </el-table-column>
+      <el-table-column label="报告对象" width="120">
+        <template slot-scope="{ row }">{{ row.personName || row.subjectName || '-' }}</template>
+      </el-table-column>
+      <el-table-column label="归属状态" width="120">
+        <template slot-scope="{ row }">
+          <el-tag v-if="row.subjectId" type="success" size="small">已归属</el-tag>
+          <el-tag v-else type="warning" size="small">未归属</el-tag>
         </template>
-      </template>
-    </a-table>
-
-    <!-- 选择成员弹窗 -->
-    <a-modal :visible="visible" @ok="doReassign" @cancel="visible = false">
-      <template #title>
-        <span v="reassignMode">报告重新分配</span>
-        <span v-else>绑定归属</span>
-      </template>
-      <template #body>
-        <a-space direction="vertical" :space="8">
-          <a-select
-            :options="members"
-            placeholder="选择家庭成员"
-            @change="onMemberSelect"
-            disabled="reassignMode && !row.subjectId"
-          />
-        </a-space>
-        <!-- 如为重新分配模式,显示当前归属 -->
-        <template v-if="reassignMode && row.subjectId">
-          <p class="hint-text">当前归属:{{ getMemberName(row.subjectId) }}</p>
+      </el-table-column>
+      <el-table-column label="当前归属" width="120">
+        <template slot-scope="{ row }">{{ row.subjectId ? getMemberName(row.subjectId) : '-' }}</template>
+      </el-table-column>
+      <el-table-column prop="reportDate" label="报告日期" width="120">
+        <template slot-scope="{ row }">{{ formatTime(row.reportDate) }}</template>
+      </el-table-column>
+      <el-table-column prop="createdAt" label="上传时间" width="160">
+        <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+      </el-table-column>
+      <el-table-column label="操作" width="140" fixed="right">
+        <template slot-scope="{ row }">
+          <el-button size="mini" type="primary" @click="openReassign(row)">重新分配</el-button>
         </template>
-      </template>
-      <footer>
-        <a-button @click="visible = false">取消</a-button>
-        <a-button type="primary" @click="doConfirm">确认</a-button>
-      </footer>
-    </a-modal>
+      </el-table-column>
+    </el-table>
+
+    <!-- 重新分配弹窗 -->
+    <el-dialog title="重新分配报告归属" :visible.sync="dialogVisible" width="480px" :close-on-click-modal="false">
+      <el-form label-width="90px">
+        <el-form-item label="报告ID">
+          <span>{{ currentReport && currentReport.id }}</span>
+        </el-form-item>
+        <el-form-item v-if="currentReport && currentReport.subjectId" label="当前归属">
+          <span>{{ getMemberName(currentReport.subjectId) }}</span>
+        </el-form-item>
+        <el-form-item label="归属成员">
+          <el-select v-model="newSubjectId" placeholder="选择归属成员(留空表示解除归属)" filterable clearable style="width: 100%">
+            <el-option v-for="m in memberList" :key="m.id" :label="m.nickname || m.realName || m.userId" :value="m.id" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <span slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="saving" @click="doReassign">确认</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
-<script setup>
-import { ref, computed, watch } from 'vue'
-import { aTable, aSelect, aModal, aButton, aSpace, aMessage } from '/umi-design'
-import { request } from '/utils/api'
-
-const props = defineProps({
-  familyId: { type: Number, required: true }
-})
-
-const members = ref([])
-const reports = ref([])
-const loading = ref(false)
-const visible = ref(false)
-let currentRow = null
-let reassignMode = false // 'bind' | 'reassign'
-
-const columns = [
-  { title: '报告ID', dataIndex: 'id', width: 100 },
-  { title: '上传日期', dataIndex: 'reportDate', width: 120 },
-  { title: '报告类型', dataIndex: 'reportType', width: 100 },
-  { title: '主体', dataIndex: 'subjectDisplay', width: 100 },
-  { title: '操作', dataIndex: 'actions', width: 120 }
-]
+<script>
+import { getFamilyList, getFamilyMemberList, reassignHealthReport, getUnlinkedReports } from '@/api/admin'
 
-// 从 API 获取成员列表
-const fetchMembers = async () => {
-  const { data } = await request('/api/family/member/list', {
-    includeFamily: true
-  })
-  members.value = data.result || data.list || []
-}
-
-// 刷新列表
-const refresh = async () => {
-  loading.value = true
-  const { data } = await request('/api/health/report/unlinked', { familyId: props.familyId })
-  reports.value = data.result || data
-  loading.value = false
-  await fetchMembers()
-}
-
-// 绑定/重新分配行
-const bindRow = (row) => {
-  currentRow = row
-  reassignMode = false
-  visible.value = true
-}
-
-const reassignRow = (row) => {
-  currentRow = row
-  reassignMode = true
-  visible.value = true
-}
-
-// 成员选择变化 - 保存选中的成员 ID
-const selectedMemberId = ref('')
-const onMemberSelect = (e) => {
-  selectedMemberId.value = e.target.value
-}
-
-// 获取成员名称
-const getMemberName = (memberId) => {
-  const m = members.value.find(x => x.id === memberId)
-  return m ? (m.nickname || m.realName || m.userId.toString()) : '未知'
-}
-
-// 确认操作
-const doConfirm = async () => {
-  if (currentRow.subjectId && reassignMode) {
-    // 重新分配:发送新的 subjectId
-    await request('/api/health/report/reassign', {
-      reportId: currentRow.id,
-      subjectId: selectedMemberId.value
-    })
-  } else if (!currentRow.subjectId && !reassignMode) {
-    // 绑定:发送当前选中的 memberId
-    await request('/api/health/report/reassign', {
-      reportId: currentRow.id,
-      subjectId: selectedMemberId.value
-    })
+export default {
+  name: 'HealthReportReassign',
+  data() {
+    return {
+      familyList: [],
+      familyId: null,
+      memberList: [],
+      reports: [],
+      loading: false,
+      saving: false,
+      dialogVisible: false,
+      currentReport: null,
+      newSubjectId: null
+    }
+  },
+  mounted() {
+    this.loadFamilies()
+  },
+  methods: {
+    formatTime(t) {
+      return t ? String(t).replace('T', ' ').substring(0, 19) : '-'
+    },
+    getMemberName(memberId) {
+      if (!memberId) return '-'
+      var m = this.memberList.find(function(x) { return x.id === memberId })
+      return m ? (m.nickname || m.realName || m.userId) : String(memberId)
+    },
+    loadFamilies() {
+      getFamilyList({}).then(res => {
+        this.familyList = (res.data && res.data.list) || res.data || []
+        if (this.familyList.length > 0) {
+          this.familyId = this.familyList[0].id
+          this.loadData()
+        }
+      }).catch(() => {
+        this.$message.error('获取家庭列表失败')
+      })
+    },
+    loadData() {
+      if (!this.familyId) {
+        this.reports = []
+        return
+      }
+      this.loading = true
+      getUnlinkedReports({ familyId: this.familyId }).then(res => {
+        this.reports = (res.data && res.data.list) || res.data || []
+        this.loadMembers()
+      }).catch(() => {
+        this.$message.error('加载报告列表失败')
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    loadMembers() {
+      getFamilyMemberList({ familyId: this.familyId }).then(res => {
+        this.memberList = (res.data && res.data.list) || res.data || []
+      }).catch(() => {
+        this.memberList = []
+      })
+    },
+    openReassign(row) {
+      this.currentReport = row
+      this.newSubjectId = row.subjectId || null
+      this.dialogVisible = true
+    },
+    doReassign() {
+      if (!this.currentReport) return
+      this.saving = true
+      reassignHealthReport({
+        reportId: this.currentReport.id,
+        subjectId: this.newSubjectId
+      }).then(res => {
+        this.$message.success('归属已更新')
+        this.dialogVisible = false
+        this.loadData()
+      }).catch(() => {
+        this.$message.error('归属更新失败')
+      }).finally(() => {
+        this.saving = false
+      })
+    }
   }
-  visible.value = false
-  await refresh()
 }
 </script>
 
 <style scoped>
-.reassign-container { margin: 16px; }
-.hint-text { color: #888; font-size: 12px; margin-top: 4px; }
+.reassign-container {
+  padding: 16px;
+}
+.page-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 16px;
+  font-weight: 600;
+  margin-bottom: 16px;
+}
+.filter-bar {
+  margin-bottom: 12px;
+}
 </style>