Sfoglia il codice sorgente

feat(web-admin): add region staff config dialog and list summaries

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 1 mese fa
parent
commit
8029477e90

+ 2 - 2
web-admin/src/types/api.d.ts

@@ -535,8 +535,8 @@ declare namespace Api {
       id: string
       regionName: string
       hospitalNames: string[]
-      staffNames: string[]
-      leaderNames: string[]
+      staffNames?: string[]
+      leaderNames?: string[]
       tenantNo: string
       orderNum: number
       remark: string

+ 389 - 3
web-admin/src/views/basic-info/collection-region/index.vue

@@ -87,6 +87,42 @@
               <span v-else class="muted">—</span>
             </template>
           </el-table-column>
+          <el-table-column
+            label="工作人员"
+            min-width="160"
+            align="left"
+            header-align="center"
+            class-name="hospital-info-col"
+          >
+            <template #default="{ row }">
+              <template v-if="row.staffNames && row.staffNames.length">
+                <div v-for="(name, idx) in row.staffNames" :key="idx" class="hospital-cell-line">
+                  {{ name }}
+                </div>
+              </template>
+              <span v-else class="muted">—</span>
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="负责人"
+            min-width="140"
+            align="center"
+            header-align="center"
+          >
+            <template #default="{ row }">
+              <template v-if="row.leaderNames && row.leaderNames.length">
+                <el-tag
+                  v-for="(name, idx) in row.leaderNames"
+                  :key="idx"
+                  type="success"
+                  size="small"
+                  style="margin: 2px"
+                  >{{ name }}</el-tag
+                >
+              </template>
+              <el-tag v-else type="info" size="small">未设置</el-tag>
+            </template>
+          </el-table-column>
           <el-table-column
             prop="remark"
             label="备注"
@@ -110,16 +146,19 @@
           </el-table-column>
           <el-table-column
             label="操作"
-            min-width="160"
+            min-width="300"
             fixed="right"
             align="center"
             header-align="center"
           >
             <template #default="{ row }">
-              <div style="display: flex; justify-content: center; gap: 8px">
+              <div style="display: flex; justify-content: center; gap: 8px; flex-wrap: wrap">
                 <el-button class="btn-op-item primary" @click="openHospitalDialog(row)">
                   地区医院配置
                 </el-button>
+                <el-button class="btn-op-item primary" @click="openStaffDialog(row)">
+                  地区人员配置
+                </el-button>
                 <el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
                   <el-button class="btn-op-item">
                     <el-icon><More /></el-icon>
@@ -360,6 +399,187 @@
         </template>
       </el-dialog>
 
+      <el-dialog
+        v-model="staffDialogVisible"
+        :title="staffDialogTitle"
+        width="860px"
+        class="dialog-form-layout"
+        align-center
+        append-to-body
+        modal-class="protocol-dialog-mask"
+        destroy-on-close
+        :close-on-click-modal="false"
+        @closed="onStaffDialogClosed"
+      >
+        <div class="hospital-dialog-head">
+          <span class="muted">当前地区:{{ currentRegionName }}</span>
+        </div>
+        <div class="list-toolbar hospital-toolbar">
+          <div class="list-toolbar-row">
+            <div class="list-toolbar-selected">
+              已选 <span class="sel-num">{{ staffSelected.length }}</span> 项
+            </div>
+            <el-button class="btn-main" type="primary" @click="handleAddStaff">
+              <el-icon><Plus /></el-icon>
+              添加人员
+            </el-button>
+            <el-button
+              class="btn-ghost"
+              type="danger"
+              :disabled="staffSelected.length === 0"
+              @click="handleBatchDeleteStaff"
+            >
+              <el-icon><Delete /></el-icon>
+              批量移除
+            </el-button>
+          </div>
+        </div>
+        <div class="list-body list-wrap hospital-table-wrap">
+          <el-table
+            class="list-table"
+            :data="staffTable"
+            v-loading="staffLoading"
+            stripe
+            row-key="id"
+            max-height="360"
+            @selection-change="onStaffSelectionChange"
+          >
+            <el-table-column type="selection" min-width="55" align="center" header-align="center" />
+            <el-table-column
+              type="index"
+              label="序号"
+              min-width="68"
+              align="center"
+              header-align="center"
+            />
+            <el-table-column
+              prop="userName"
+              label="姓名"
+              min-width="120"
+              align="center"
+              header-align="center"
+            />
+            <el-table-column
+              prop="userNo"
+              label="工号"
+              min-width="120"
+              align="center"
+              header-align="center"
+            />
+            <el-table-column
+              prop="deptName"
+              label="部门"
+              min-width="140"
+              align="center"
+              header-align="center"
+            >
+              <template #default="{ row }">
+                <span v-if="row.deptName">{{ row.deptName }}</span>
+                <span v-else class="muted">—</span>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="是否负责人"
+              min-width="120"
+              align="center"
+              header-align="center"
+            >
+              <template #default="{ row }">
+                <el-tag v-if="row.isLeader" type="success" size="small">负责人</el-tag>
+                <el-tag v-else type="info" size="small">否</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="操作"
+              min-width="260"
+              fixed="right"
+              align="center"
+              header-align="center"
+            >
+              <template #default="{ row }">
+                <el-button class="btn-op-item primary" @click="toggleStaffLeader(row)">
+                  {{ row.isLeader ? '取消负责人' : '设为负责人' }}
+                </el-button>
+                <el-button class="btn-op-item" @click="handleDeleteStaff(row)">移除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="pagination list-pager-bar">
+            <el-pagination
+              :current-page="staffPagination.page"
+              :page-size="staffPagination.pageSize"
+              :total="staffPagination.total"
+              :page-sizes="[10, 20, 50]"
+              layout="total, sizes, prev, pager, next"
+              @size-change="
+                (val) => (
+                  (staffPagination.page = 1), (staffPagination.pageSize = val), loadStaff()
+                )
+              "
+              @current-change="(val) => ((staffPagination.page = val), loadStaff())"
+            />
+          </div>
+        </div>
+
+        <el-dialog
+          v-model="staffAddVisible"
+          title="添加人员"
+          width="520px"
+          class="dialog-form-layout"
+          align-center
+          append-to-body
+          modal-class="protocol-dialog-mask"
+          destroy-on-close
+          :close-on-click-modal="false"
+        >
+          <el-form label-position="top">
+            <el-form-item label="系统人员" required>
+              <el-select
+                v-model="staffAddForm.userId"
+                filterable
+                remote
+                clearable
+                :remote-method="remoteSearchUsers"
+                :loading="staffUserLoading"
+                placeholder="搜索姓名/账号"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="u in staffUserOptions"
+                  :key="u.id"
+                  :label="`${u.nickName}(${u.userNo})`"
+                  :value="u.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="设为负责人">
+              <el-switch v-model="staffAddForm.isLeader" />
+            </el-form-item>
+          </el-form>
+          <template #footer>
+            <el-button class="btn-ghost" @click="staffAddVisible = false">
+              <el-icon><Close /></el-icon>
+              取消
+            </el-button>
+            <el-button
+              class="btn-main"
+              type="primary"
+              :loading="staffAddLoading"
+              @click="submitAddStaff"
+            >
+              <el-icon><Check /></el-icon>
+              确定
+            </el-button>
+          </template>
+        </el-dialog>
+        <template #footer>
+          <el-button class="btn-ghost" @click="staffDialogVisible = false">
+            <el-icon><Close /></el-icon>
+            关闭
+          </el-button>
+        </template>
+      </el-dialog>
+
       <SystemViewDialog
         v-model:visible="viewVisible"
         :title="viewRow?.regionName || '地区详情'"
@@ -388,7 +608,12 @@
     fetchHospitalList,
     fetchCreateHospital,
     fetchUpdateHospital,
-    fetchDeleteHospital
+    fetchDeleteHospital,
+    fetchRegionStaffList,
+    fetchCreateRegionStaff,
+    fetchUpdateRegionStaff,
+    fetchDeleteRegionStaff,
+    fetchUserList
   } from '@/api/admin'
   defineOptions({ name: 'BizCollectionRegion' })
 
@@ -434,6 +659,10 @@
     if (!row) return []
     const hospitals =
       row.hospitalNames && row.hospitalNames.length > 0 ? row.hospitalNames.join('、') : '无'
+    const staffs =
+      row.staffNames && row.staffNames.length > 0 ? row.staffNames.join('、') : '无'
+    const leaders =
+      row.leaderNames && row.leaderNames.length > 0 ? row.leaderNames.join('、') : '未设置'
     return [
       {
         title: '基本信息',
@@ -441,6 +670,13 @@
           { label: '地区名称', value: row.regionName },
           { label: '创建时间', value: row.createTime },
           { label: '关联医院', value: hospitals, span: true },
+          { label: '工作人员', value: staffs, span: true },
+          {
+            label: '负责人',
+            value: row.leaderNames?.length ? leaders : '未设置',
+            tagType: row.leaderNames?.length ? 'success' : 'info',
+            span: true
+          },
           { label: '备注', value: row.remark, span: true }
         ]
       }
@@ -674,6 +910,156 @@
       await loadHospitals()
     }, fullscreenLoadingText.batchDelete)
   }
+
+  const staffDialogVisible = ref(false)
+  const staffDialogTitle = ref('地区人员配置')
+  const staffTable = ref<Api.BizCollectionRegionStaff.StaffItem[]>([])
+  const staffSelected = ref<Api.BizCollectionRegionStaff.StaffItem[]>([])
+  const staffLoading = ref(false)
+  const staffPagination = reactive({ page: 1, pageSize: 10, total: 0 })
+  const staffAddVisible = ref(false)
+  const staffAddLoading = ref(false)
+  const staffAddForm = reactive<{ userId: string; isLeader: boolean }>({
+    userId: '',
+    isLeader: false
+  })
+  const staffUserOptions = ref<Api.User.UserItem[]>([])
+  const staffUserLoading = ref(false)
+  const boundStaffUserIds = ref<Set<string>>(new Set())
+
+  function onStaffSelectionChange(rows: Api.BizCollectionRegionStaff.StaffItem[]) {
+    staffSelected.value = rows
+  }
+
+  function openStaffDialog(row: Api.BizCollectionRegion.RegionItem) {
+    currentRegionId.value = row.id
+    currentRegionName.value = row.regionName || ''
+    staffDialogTitle.value = '地区人员配置'
+    staffPagination.page = 1
+    staffSelected.value = []
+    staffDialogVisible.value = true
+    loadStaff()
+  }
+
+  async function refreshBoundStaffUserIds() {
+    if (!currentRegionId.value) {
+      boundStaffUserIds.value = new Set()
+      return
+    }
+    try {
+      const res = await fetchRegionStaffList(currentRegionId.value, {
+        page: 1,
+        pageSize: 500
+      })
+      boundStaffUserIds.value = new Set((res.list || []).map((s) => s.userId))
+    } catch {
+      boundStaffUserIds.value = new Set(staffTable.value.map((s) => s.userId))
+    }
+  }
+
+  async function loadStaff() {
+    if (!currentRegionId.value) return
+    staffLoading.value = true
+    try {
+      const res = await fetchRegionStaffList(currentRegionId.value, {
+        page: staffPagination.page,
+        pageSize: staffPagination.pageSize
+      })
+      staffTable.value = res.list || []
+      staffPagination.total = res.total ?? 0
+      await refreshBoundStaffUserIds()
+    } finally {
+      staffLoading.value = false
+    }
+  }
+
+  function onStaffDialogClosed() {
+    staffTable.value = []
+    staffSelected.value = []
+    boundStaffUserIds.value = new Set()
+    loadData()
+  }
+
+  function handleAddStaff() {
+    staffAddForm.userId = ''
+    staffAddForm.isLeader = false
+    staffUserOptions.value = []
+    staffAddVisible.value = true
+    remoteSearchUsers('')
+  }
+
+  async function remoteSearchUsers(query: string) {
+    staffUserLoading.value = true
+    try {
+      const res = await fetchUserList({
+        page: 1,
+        pageSize: 20,
+        keyword: query || undefined,
+        status: '1'
+      })
+      const bound = boundStaffUserIds.value
+      staffUserOptions.value = (res.list || []).filter((u) => !bound.has(u.id))
+    } finally {
+      staffUserLoading.value = false
+    }
+  }
+
+  async function submitAddStaff() {
+    if (!staffAddForm.userId) {
+      ElMessage.warning('请选择系统人员')
+      return
+    }
+    staffAddLoading.value = true
+    try {
+      await fetchCreateRegionStaff(currentRegionId.value, {
+        userId: staffAddForm.userId,
+        isLeader: staffAddForm.isLeader
+      })
+      ElMessage.success('添加成功')
+      staffAddVisible.value = false
+      staffAddForm.userId = ''
+      staffAddForm.isLeader = false
+      await loadStaff()
+      await loadData()
+    } catch {
+      /* */
+    } finally {
+      staffAddLoading.value = false
+    }
+  }
+
+  async function toggleStaffLeader(row: Api.BizCollectionRegionStaff.StaffItem) {
+    await fetchUpdateRegionStaff(currentRegionId.value, row.id, { isLeader: !row.isLeader })
+    ElMessage.success('已更新')
+    await loadStaff()
+    await loadData()
+  }
+
+  async function handleDeleteStaff(row: Api.BizCollectionRegionStaff.StaffItem) {
+    await ElMessageBox.confirm(`确定移除「${row.userName}」吗?`, '提示', { type: 'warning' })
+    await fetchDeleteRegionStaff(currentRegionId.value, row.id)
+    ElMessage.success('已移除')
+    await loadStaff()
+    await loadData()
+  }
+
+  async function handleBatchDeleteStaff() {
+    if (!staffSelected.value.length) return
+    await ElMessageBox.confirm(`确定移除选中的 ${staffSelected.value.length} 人吗?`, '提示', {
+      type: 'warning'
+    })
+    const rid = currentRegionId.value
+    await withFullscreenLoading(async () => {
+      for (const row of staffSelected.value) {
+        await fetchDeleteRegionStaff(rid, row.id)
+      }
+      ElMessage.success('已移除')
+      staffSelected.value = []
+      await loadStaff()
+      await loadData()
+    }, fullscreenLoadingText.batchDelete)
+  }
+
   // 输入检索条件时自动搜索(防抖 300ms)
   watchDebounced(
     searchForm,