فهرست منبع

添加居家服务人员

unknown 1 ماه پیش
والد
کامیت
dd7140d247

+ 189 - 0
src/api/living-home/elderly/index.ts

@@ -118,5 +118,194 @@ export const updateElderPassword = (data: { elderId: string | number; password:
   })
 }
 
+// ==================== 服务区域接口 ====================
+
+// 服务区域 VO
+export interface ServiceAreaVO {
+  id?: number
+  areaCode: string | number[]
+  areaName: string
+  remark?: string
+}
+
+// 查询服务区域列表
+export const getServiceAreaList = () => {
+  return request.get({
+    url: 'living-home/service-area/list'
+  })
+}
+
+// 查询服务区域详情
+export const getServiceArea = (id: number) => {
+  return request.get({
+    url: `living-home/service-area/get?id=${id}`
+  })
+}
+
+// 新增服务区域
+export const createServiceArea = (data: ServiceAreaVO) => {
+  return request.post({
+    url: 'living-home/service-area/create',
+    data
+  })
+}
+
+// 修改服务区域
+export const updateServiceArea = (data: ServiceAreaVO) => {
+  return request.put({
+    url: 'living-home/service-area/update',
+    data
+  })
+}
+
+// 删除服务区域
+export const deleteServiceArea = (ids: number[]) => {
+  return request.delete({
+    url: `living-home/service-area/delete?ids=${ids.join(',')}`
+  })
+}
+
+// ==================== 服务人员接口 ====================
+
+// 服务人员 VO
+export interface ServicePersonVO {
+  id?: number
+  name: string
+  idCard: string
+  gender: string
+  age: string
+  phone: string
+  personType: string
+  serviceAreaId?: number
+  serviceArea?: string
+  areaCode?: string
+  detailAddress?: string
+  avatar?: string
+  organization?: string
+  elderCount?: number
+  status?: string
+}
+
+
+
+// 查询服务人员详情
+export const getServicePerson = (id: number) => {
+  return request.get({
+    url: `living-home/service-person/get?id=${id}`
+  })
+}
+
+// 新增服务人员
+export const createServicePerson = (data: ServicePersonVO) => {
+  return request.post({
+    url: 'living-home/service-person/create',
+    data
+  })
+}
+
+// 修改服务人员
+export const updateServicePerson = (data: ServicePersonVO) => {
+  return request.put({
+    url: 'living-home/service-person/update',
+    data
+  })
+}
+
+// 删除服务人员
+export const deleteServicePerson = (ids: number[]) => {
+  return request.delete({
+    url: `living-home/service-person/delete?ids=${ids.join(',')}`
+  })
+}
+
+// 服务人员离职
+export const resignServicePerson = (id: number) => {
+  return request.post({
+    url: `living-home/service-person/resign?id=${id}`
+  })
+}
+
+// ==================== 长者关联接口 ====================
+
+// 查询长者列表
+export const getElderList = (params?: any) => {
+  return request.get({
+    url: 'living-home/elderlyInfo/list',
+    params
+  })
+}
+
+// 关联长者
+export const relateElder = (data: { servicePersonId?: number; elderId: number }) => {
+  return request.post({
+    url: 'living-home/service-person/relate-elder',
+    data
+  })
+}
+
+// 批量关联长者
+export const batchRelateElder = (data: { servicePersonId?: number; elderIds: number[] }) => {
+  return request.post({
+    url: 'living-home/service-person/batch-relate-elder',
+    data
+  })
+}
+
+// ==================== 服务类别接口 ====================
+
+// 服务类别 VO
+export interface ServiceTypeVO {
+  id?: number
+  name: string
+  selfCareSituation: string[]
+  remark?: string
+  sort?: number
+}
+
+// 查询服务类别列表
+export const getServiceTypeList = (params?: { name?: string }) => {
+  return request.get({
+    url: 'living-home/service-type/list',
+    params
+  })
+}
+
+// 查询服务类别详情
+export const getServiceType = (id: number) => {
+  return request.get({
+    url: `living-home/service-type/get?id=${id}`
+  })
+}
+
+// 新增服务类别
+export const createServiceType = (data: ServiceTypeVO) => {
+  return request.post({
+    url: 'living-home/service-type/create',
+    data
+  })
+}
+
+// 修改服务类别
+export const updateServiceType = (data: ServiceTypeVO) => {
+  return request.put({
+    url: 'living-home/service-type/update',
+    data
+  })
+}
+
+// 删除服务类别
+export const deleteServiceType = (ids: number[]) => {
+  return request.delete({
+    url: `living-home/service-type/delete?ids=${ids.join(',')}`
+  })
+}
+
+// 服务类别排序(上移/下移)
+export const sortServiceType = (data: { id: number; direction: 'up' | 'down' }) => {
+  return request.post({
+    url: 'living-home/service-type/sort',
+    data
+  })
+}
 
 

+ 19 - 0
src/api/system/area/index.ts

@@ -1,10 +1,29 @@
 import request from '@/config/axios'
 
+// 地区 VO
+export interface AreaVO {
+  id: number
+  name: string
+  parentId: number
+  level: number
+  children?: AreaVO[]
+}
+
 // 获得地区树
 export const getAreaTree = async () => {
   return await request.get({ url: '/system/area/tree' })
 }
 
+// 获得地区列表(根据父级ID)
+export const getAreaList = async (parentId: number) => {
+  return await request.get({ url: '/system/area/list?parentId=' + parentId })
+}
+
+// 获得地区详情
+export const getArea = async (id: number) => {
+  return await request.get({ url: '/system/area/get?id=' + id })
+}
+
 // 获得 IP 对应的地区名
 export const getAreaByIp = async (ip: string) => {
   return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })

+ 108 - 0
src/views/living-home/serviceItemManage/serviceType/ServiceTypeForm.vue

@@ -0,0 +1,108 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="40vw">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <el-form-item label="服务类别" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入" size="large"/>
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark" size="large">
+        <el-input
+          v-model="formData.remark"
+          type="textarea"
+          :rows="3"
+          placeholder="请输入备注"
+          maxlength="200"
+          show-word-limit
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { FormRules } from 'element-plus'
+import * as ServiceTypeApi from '@/api/living-home/elderly'
+
+defineOptions({ name: 'ServiceTypeForm' })
+
+const { t } = useI18n()
+const message = useMessage()
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formLoading = ref(false)
+const formType = ref('')
+const formData = ref<ServiceTypeApi.ServiceTypeVO>({
+  id: undefined,
+  name: '',
+  selfCareSituation: [],
+  remark: ''
+})
+const formRules = reactive<FormRules>({
+  name: [{ required: true, message: '服务类别不能为空', trigger: 'blur' }],
+  selfCareSituation: [{ required: true, message: '请选择关联自理情况', trigger: 'change', type: 'array' }]
+})
+const formRef = ref()
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = type === 'create' ? '新增服务类别' : '编辑服务类别'
+  formType.value = type
+  resetForm()
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ServiceTypeApi.getServiceType(id)
+      formData.value = data
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open })
+
+/** 提交表单 */
+const emit = defineEmits(['success'])
+const submitForm = async () => {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  formLoading.value = true
+  try {
+    if (formType.value === 'create') {
+      await ServiceTypeApi.createServiceType(formData.value)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServiceTypeApi.updateServiceType(formData.value)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    selfCareSituation: [],
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 160 - 4
src/views/living-home/serviceItemManage/serviceType/index.vue

@@ -1,11 +1,167 @@
-<script setup lang="ts">
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="85px"
+    >
+      <el-form-item label="服务类别:" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-200px"
+        />
+      </el-form-item>
+      <el-form-item class="!ml-auto">
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" /> 查询
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" /> 重置
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
 
-</script>
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="flex items-center justify-between mb-15px">
+      <span class="font-bold text-base">服务类别列表</span>
+      <div>
+        <el-button type="primary" @click="openForm('create')">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="danger" plain @click="handleBatchDelete" :disabled="!selectedIds.length">
+          <Icon icon="ep:delete" class="mr-5px" /> 删除
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="序号" type="index" width="80" align="center" />
+      <el-table-column prop="name" label="服务类别" align="center" min-width="150" />
 
-<template>
-a
+      <el-table-column label="操作" align="center" width="200" fixed="right">
+        <template #default="scope">
+          <el-button link type="primary" @click="openForm('update', scope.row.id)">
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ServiceTypeForm ref="formRef" @success="getList" />
 </template>
 
+<script lang="ts" setup>
+import ServiceTypeForm from './ServiceTypeForm.vue'
+import * as ServiceTypeApi from '@/api/living-home/elderly'
+
+defineOptions({ name: 'ServiceType' })
+
+const message = useMessage()
+const { t } = useI18n()
+
+const loading = ref(false)
+const list = ref<ServiceTypeApi.ServiceTypeVO[]>([])
+const selectedIds = ref<number[]>([])
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 100,
+  name: '',
+})
+const queryFormRef = ref()
+
+
+
+/** 格式化自理情况显示 */
+const formatSelfCareSituation = (values: string[]) => {
+  if (!values || !values.length) return ''
+  return values.map(v => selfCareOptions.find(opt => opt.value === v)?.label).filter(Boolean).join(',')
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ServiceTypeApi.getServiceTypeList(queryParams)
+    list.value = data
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServiceTypeApi.ServiceTypeVO[]) => {
+  selectedIds.value = selection.map(item => item.id as number)
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 批量删除 */
+const handleBatchDelete = async () => {
+  if (!selectedIds.value.length) {
+    message.warning('请选择要删除的数据')
+    return
+  }
+  try {
+    await message.delConfirm()
+    await ServiceTypeApi.deleteServiceType(selectedIds.value)
+    message.success(t('common.delSuccess'))
+    getList()
+  } catch {}
+}
+
+/** 上移 */
+const handleMoveUp = async (index: number) => {
+  if (index === 0) return
+  const item = list.value[index]
+  await ServiceTypeApi.sortServiceType({ id: item.id as number, direction: 'up' })
+  getList()
+}
+
+/** 下移 */
+const handleMoveDown = async (index: number) => {
+  if (index === list.value.length - 1) return
+  const item = list.value[index]
+  await ServiceTypeApi.sortServiceType({ id: item.id as number, direction: 'down' })
+  getList()
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>
+
 <style scoped lang="scss">
 
 </style>

+ 150 - 0
src/views/living-home/serviceTeam/districtManagement/ServiceAreaForm.vue

@@ -0,0 +1,150 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="46vw">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <el-form-item label="服务区域:" prop="areaCode" size="large">
+        <el-cascader
+          v-model="formData.areaCode"
+          :options="areaTree"
+          :props="defaultProps"
+          placeholder="请选择省/市/街道"
+          clearable
+          class="!w-full"
+        />
+      </el-form-item>
+      <el-form-item label="备注地址:" prop="remark" size="large">
+        <el-input
+          v-model="formData.remark"
+          type="textarea"
+          :rows="3"
+          placeholder="请输入"
+        />
+        <div class="text-red-500 text-xs mt-1">
+          提示:服务人员、应急人员、志愿者,可选择自己的服务区域,并对服务区域内的长者提供救助和生活照料服务。
+        </div>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as ServiceAreaApi from '@/api/living-home/elderly'
+import * as AreaApi from '@/api/system/area'
+import { FormRules } from 'element-plus'
+import {defaultProps} from "@/utils/tree";
+
+defineOptions({ name: 'ServiceAreaForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref<ServiceAreaApi.ServiceAreaVO>({
+  id: undefined,
+  areaCode: [],
+  areaName: '',
+  remark: ''
+})
+const formRules = reactive<FormRules>({
+  areaCode: [{ required: true, message: '服务区域不能为空', trigger: 'change' }]
+})
+const formRef = ref() // 表单 Ref
+
+// 级联选择器配置
+const areaTree = ref([])
+
+
+
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = type === 'create' ? '新增服务区域' : '编辑服务区域'
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ServiceAreaApi.getServiceArea(id)
+      formData.value = {
+        ...data,
+        areaCode: data.areaCode ? data.areaCode.split(',').map(Number) : []
+      }
+    } finally {
+      formLoading.value = false
+    }
+  }
+
+  // 加载区域数据
+  if (areaTree.value.length === 0) {
+    areaTree.value = await AreaApi.getAreaTree()
+  }
+
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = { ...formData.value } as ServiceAreaApi.ServiceAreaVO
+
+    // 获取区域名称
+
+    if (formType.value === 'create') {
+      await ServiceAreaApi.createServiceArea(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServiceAreaApi.updateServiceArea(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 根据区域代码获取区域名称 */
+const getAreaNameByCode = async (areaCode: string): Promise<string> => {
+  const codes = areaCode.split(',').map(Number)
+  const names: string[] = []
+  for (const code of codes) {
+    const area = await AreaApi.getArea(code)
+    if (area) {
+      names.push(area.name)
+    }
+  }
+  return names.join('/')
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    areaCode: [],
+    areaName: '',
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 133 - 7
src/views/living-home/serviceTeam/districtManagement/index.vue

@@ -1,11 +1,137 @@
-<script setup lang="ts">
-
-</script>
-
 <template>
-a
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="flex justify-between items-center mb-4">
+      <div class="text-base font-medium">服务区域列表</div>
+      <div>
+        <el-button type="primary" @click="openForm('create')">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedIds.length">
+          <Icon icon="ep:delete" class="mr-5px" /> 删除
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column type="index" label="序号" width="80" />
+      <el-table-column prop="areaName" label="服务区域" />
+      <el-table-column prop="areaName" label="备注" />
+      <el-table-column label="操作" align="center" width="120">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+          >
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ServiceAreaForm ref="formRef" @success="getList" />
 </template>
+<script lang="ts" setup>
+import * as ServiceAreaApi from '@/api/living-home/elderly'
+import ServiceAreaForm from './ServiceAreaForm.vue'
+
+defineOptions({ name: 'ServiceArea' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
 
-<style scoped lang="scss">
+const loading = ref(true) // 列表的加载中
+const list = ref<ServiceAreaApi.ServiceAreaVO[]>([]) // 列表的数据
+const selectedIds = ref<number[]>([]) // 选中的ID列表
 
-</style>
+// 默认测试数据
+const defaultData: ServiceAreaApi.ServiceAreaVO[] = [
+  {
+    id: 1,
+    areaCode: '441900',
+    areaName: '东莞市/茶山镇',
+    remark: '服务人员、应急人员、志愿者可选择此区域提供服务'
+  },
+  {
+    id: 2,
+    areaCode: '341600,341621,341621100',
+    areaName: '亳州市/涡阳县/陈大镇',
+    remark: '主要服务区域,覆盖多个社区'
+  },
+  {
+    id: 3,
+    areaCode: '440300,440305',
+    areaName: '深圳市/南山区',
+    remark: '南山区全部街道'
+  },
+  {
+    id: 4,
+    areaCode: '440100,440106',
+    areaName: '广州市/天河区',
+    remark: '天河区核心服务区域'
+  },
+  {
+    id: 5,
+    areaCode: '440600,440604',
+    areaName: '佛山市/禅城区',
+    remark: '禅城区志愿者服务范围'
+  }
+]
+
+/** 查询服务区域列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // 先尝试从接口获取数据
+    const data = await ServiceAreaApi.getServiceAreaList()
+    // 如果接口返回数据为空,则使用默认数据
+    list.value = data && data.length > 0 ? data : defaultData
+  } catch (error) {
+    // 接口调用失败时,使用默认数据
+    console.log('使用默认数据')
+    list.value = defaultData
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServiceAreaApi.ServiceAreaVO[]) => {
+  selectedIds.value = selection.map((item) => item.id)
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 批量删除按钮操作 */
+const handleBatchDelete = async () => {
+  if (selectedIds.value.length === 0) {
+    message.warning('请选择要删除的数据')
+    return
+  }
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ServiceAreaApi.deleteServiceArea(selectedIds.value)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
+</script>

+ 174 - 0
src/views/living-home/serviceTeam/serviceDepartment/DeptForm.vue

@@ -0,0 +1,174 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="上级部门" prop="parentId">
+        <el-tree-select
+          v-model="formData.parentId"
+          :data="deptTree"
+          :props="defaultProps"
+          check-strictly
+          default-expand-all
+          placeholder="请选择上级部门"
+          value-key="deptId"
+        />
+      </el-form-item>
+      <el-form-item label="部门名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入部门名称" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
+      </el-form-item>
+      <el-form-item label="负责人" prop="leaderUserId">
+        <el-select v-model="formData.leaderUserId" clearable placeholder="请输入负责人">
+          <el-option
+            v-for="item in userList"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="联系电话" prop="phone">
+        <el-input v-model="formData.phone" maxlength="11" placeholder="请输入联系电话" />
+      </el-form-item>
+      <el-form-item label="邮箱" prop="email">
+        <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="formData.status" clearable placeholder="请选择状态">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { CommonStatusEnum } from '@/utils/constants'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemDeptForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  title: '',
+  parentId: undefined,
+  name: undefined,
+  sort: undefined,
+  leaderUserId: undefined,
+  phone: undefined,
+  email: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive<FormRules>({
+  parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
+  email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
+  phone: [
+    { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+  ],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const deptTree = ref() // 树形结构
+const userList = ref<UserApi.UserVO[]>([]) // 用户列表
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DeptApi.getDept(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得用户列表
+  userList.value = await UserApi.getSimpleUserList()
+  // 获得部门树
+  await getTree()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as DeptApi.DeptVO
+    if (formType.value === 'create') {
+      await DeptApi.createDept(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DeptApi.updateDept(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    title: '',
+    parentId: undefined,
+    name: undefined,
+    sort: undefined,
+    leaderUserId: undefined,
+    phone: undefined,
+    email: undefined,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+
+/** 获得部门树 */
+const getTree = async () => {
+  deptTree.value = []
+  const data = await DeptApi.getSimpleDeptList()
+  let dept: Tree = { id: 0, name: '顶级部门', children: [] }
+  dept.children = handleTree(data)
+  deptTree.value.push(dept)
+}
+</script>

+ 190 - 0
src/views/living-home/serviceTeam/serviceDepartment/index.vue

@@ -0,0 +1,190 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="部门名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入部门名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="部门状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择部门状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:dept:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="danger" plain @click="toggleExpandAll">
+          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      v-if="refreshTable"
+    >
+      <el-table-column prop="name" label="部门名称" />
+      <el-table-column prop="leader" label="负责人">
+        <template #default="scope">
+          {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="sort" label="排序" />
+      <el-table-column prop="status" label="状态">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:dept:update']"
+          >
+            修改
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:dept:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <DeptForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import DeptForm from './DeptForm.vue'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'SystemDept' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref() // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 100,
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const isExpandAll = ref(true) // 是否展开,默认全部展开
+const refreshTable = ref(true) // 重新渲染表格状态
+const userList = ref<UserApi.UserVO[]>([]) // 用户列表
+
+/** 查询部门列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await DeptApi.getDeptPage(queryParams)
+    list.value = handleTree(data)
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  nextTick(() => {
+    refreshTable.value = true
+  })
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.pageNo = 1
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await DeptApi.deleteDept(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 获取用户列表
+  userList.value = await UserApi.getSimpleUserList()
+})
+</script>

+ 198 - 0
src/views/living-home/serviceTeam/servicePersonnelManage/RelateElderDialog.vue

@@ -0,0 +1,198 @@
+<template>
+  <Dialog v-model="dialogVisible" title="关联长者设置" width="56vw">
+    <!-- 搜索栏 -->
+    <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="mb-4">
+      <el-form-item label="长者姓名:">
+        <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 20vw"/>
+      </el-form-item>
+      <el-form-item label="关联状态:">
+        <el-select v-model="queryParams.relationStatus" placeholder="请选择" clearable style="width: 20vw">
+          <el-option label="未关联" value="未关联" />
+          <el-option label="已关联" value="已关联" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="居住地址:">
+        <el-cascader
+          v-model="queryParams.areaCode"
+          :options="areaTree"
+          :props="defaultProps"
+          placeholder="请选择省/市/街道"
+          clearable
+          style="width: 20vw;"
+        />
+
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">查询</el-button>
+        <el-button @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 批量关联按钮 -->
+    <div class="mb-4">
+      <el-button type="primary" @click="handleBatchRelate" :disabled="!selectedIds.length">
+        批量关联长者
+      </el-button>
+    </div>
+
+    <!-- 长者列表 -->
+    <el-table
+      v-loading="loading"
+      :data="list"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column type="index" label="序号" width="80" />
+      <el-table-column prop="name" label="长者姓名" />
+      <el-table-column prop="gender" label="性别" width="80" />
+      <el-table-column prop="age" label="年龄" width="80" />
+      <el-table-column prop="phone" label="联系方式" />
+      <el-table-column prop="address" label="居住地址" />
+      <el-table-column label="操作" align="center" width="120">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleRelate(scope.row)">
+            关联长者
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <div class="flex justify-end mt-4">
+      <el-pagination
+        v-model:current-page="queryParams.pageNo"
+        v-model:page-size="queryParams.pageSize"
+        :total="total"
+        :page-sizes="[10, 20, 30, 50]"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="getList"
+        @current-change="getList"
+      />
+    </div>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import * as ElderApi from '@/api/living-home/elderly'
+import * as AreaApi from '@/api/system/area'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+defineOptions({ name: 'RelateElderDialog' })
+
+const message = useMessage() // 消息弹窗
+
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+  value: 'id',
+  isLeaf: 'leaf',
+  emitPath: false,
+  checkStrictly: true,
+}
+
+
+const dialogVisible = ref(false) // 弹窗显示状态
+const loading = ref(false) // 加载状态
+const list = ref<any[]>([]) // 长者列表
+const total = ref(0) // 总数
+const selectedIds = ref<number[]>([]) // 选中的长者ID
+const servicePersonId = ref<number>() // 当前服务人员ID
+
+// 查询参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 20,
+  name: '',
+  relationStatus: '',
+  areaCode: [],
+  address: ''
+})
+
+const queryFormRef = ref()
+const areaTree = ref<any[]>([]) // 地区树数据
+
+/** 加载地区树 */
+const loadAreaTree = async () => {
+  try {
+    areaTree.value = await AreaApi.getAreaTree()
+    //areaTree.value = handleTree(data)
+  } catch (error) {
+    console.error('加载地区树失败:', error)
+  }
+}
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  servicePersonId.value = id
+  dialogVisible.value = true
+  await loadAreaTree()
+  await getList()
+}
+defineExpose({ open })
+
+/** 查询长者列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const params = {
+      ...queryParams,
+      servicePersonId: servicePersonId.value
+    }
+    const data = await ElderApi.getElderList(params)
+    list.value = data.list || []
+    total.value = data.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 多选 */
+const handleSelectionChange = (selection: any[]) => {
+  selectedIds.value = selection.map((item) => item.id)
+}
+
+/** 单个关联 */
+const handleRelate = async (row: any) => {
+  try {
+    await ElderApi.relateElder({
+      servicePersonId: servicePersonId.value,
+      elderId: row.id
+    })
+    message.success('关联成功')
+    getList()
+  } catch (error) {
+    console.error('关联失败:', error)
+  }
+}
+
+/** 批量关联 */
+const handleBatchRelate = async () => {
+  if (selectedIds.value.length === 0) {
+    message.warning('请选择要关联的长者')
+    return
+  }
+  try {
+    await ElderApi.batchRelateElder({
+      servicePersonId: servicePersonId.value,
+      elderIds: selectedIds.value
+    })
+    message.success('批量关联成功')
+    getList()
+  } catch (error) {
+    console.error('批量关联失败:', error)
+  }
+}
+</script>

+ 300 - 0
src/views/living-home/serviceTeam/servicePersonnelManage/ServicePersonForm.vue

@@ -0,0 +1,300 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="60vw">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <!-- 第一行:姓名、身份证号 -->
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="姓名:" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入服务人员姓名" :disabled="isReadonly" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="身份证号:" prop="idCard">
+            <el-input v-model="formData.idCard" placeholder="请输入身份证号码" :disabled="isReadonly" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 第二行:性别、年龄 -->
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="性别:" prop="gender">
+            <el-select v-model="formData.gender" placeholder="请选择" class="!w-full" :disabled="isReadonly">
+              <el-option label="男" value="男" />
+              <el-option label="女" value="女" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="年龄:" prop="age">
+            <el-input v-model="formData.age" placeholder="请输入" :disabled="isReadonly" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 第三行:手机号码、人员类型 -->
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="手机号码:" prop="phone">
+            <el-input v-model="formData.phone" placeholder="请输入" :disabled="isReadonly" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="人员类型:" prop="personType">
+            <el-select v-model="formData.personType" placeholder="请选择" class="!w-full" :disabled="isReadonly">
+              <el-option label="服务人员" value="服务人员" />
+              <el-option label="应急人员" value="应急人员" />
+              <el-option label="志愿者" value="志愿者" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 第四行:服务区域、所属部门 -->
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="服务区域:" prop="serviceAreaId">
+            <el-select v-model="formData.serviceAreaId" placeholder="请选择" class="!w-full" :disabled="isReadonly">
+              <el-option
+                v-for="item in serviceAreaList"
+                :key="item.id"
+                :label="item.areaName"
+                :value="item.id"
+              />
+            </el-select>
+            <div v-if="!isReadonly" class="text-red-500 text-xs mt-1">
+              提示:请先在"服务团队管理-服务区域设置"中添加服务区域。
+            </div>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="所属部门:" prop="deptId">
+            <el-tree-select
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              placeholder="请选择所属部门"
+              class="!w-full"
+              :disabled="isReadonly"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 第五行:居住地址 -->
+      <el-form-item label="居住地址:" prop="address" style="width: 100%;">
+        <el-row :gutter="10" style="width: 100%;">
+          <el-col :span="12">
+            <el-cascader
+              v-model="formData.areaCode"
+              :options="areaTree"
+              :props="defaultProps"
+              placeholder="1.请选择省/市/街道"
+              clearable
+              filterable
+              class="!w-full"
+              :disabled="isReadonly"
+            />
+          </el-col>
+          <el-col :span="12">
+            <el-input v-model="formData.detailAddress" placeholder="2.请输入详细地址:如街道、小区、楼栋、单元、房号" :disabled="isReadonly" />
+          </el-col>
+        </el-row>
+      </el-form-item>
+
+      <!-- 第六行:头像 -->
+      <el-form-item label="头像:" prop="avatar">
+        <UploadImg
+          v-model="formData.avatar"
+          :disabled="isReadonly"
+          :drag="false"
+          width="120px"
+          height="120px"
+          borderradius="50%"
+          :show-delete="!isReadonly"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button v-if="!isReadonly" type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">{{ isReadonly ? '关 闭' : '取 消' }}</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import * as ServicePersonApi from '@/api/living-home/elderly'
+import * as AreaApi from '@/api/system/area'
+import * as DeptApi from '@/api/system/dept'
+import { defaultProps, handleTree } from '@/utils/tree'
+import { UploadImg } from '@/components/UploadFile'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'ServicePersonForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中
+const formType = ref('') // 表单的类型:create - 新增;update - 修改;detail - 详情
+const isReadonly = ref(false) // 是否只读模式(详情查看)
+
+// 表单数据
+const formData = ref({
+  id: undefined as number | undefined,
+  name: '',
+  idCard: '',
+  gender: '',
+  age: '',
+  phone: '',
+  personType: '服务人员', // 默认服务人员
+  serviceAreaId: undefined as number | undefined,
+  deptId: undefined as number | undefined, // 所属部门
+  areaCode: [] as number[],
+  detailAddress: '',
+  avatar: ''
+})
+
+// 表单校验规则
+const formRules = reactive<FormRules>({
+  name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
+  idCard: [{ required: true, message: '身份证号不能为空', trigger: 'blur' }],
+  gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
+  age: [{ required: true, message: '年龄不能为空', trigger: 'blur' }],
+  phone: [{ required: true, message: '手机号码不能为空', trigger: 'blur' }],
+  personType: [{ required: true, message: '人员类型不能为空', trigger: 'change' }],
+  serviceAreaId: [{ required: true, message: '服务区域不能为空', trigger: 'change' }]
+})
+
+const formRef = ref() // 表单 Ref
+const serviceAreaList = ref<any[]>([]) // 服务区域列表
+const areaTree = ref<any[]>([]) // 地区树数据
+const deptList = ref<any[]>([]) // 部门列表
+
+/** 加载地区树 */
+const loadAreaTree = async () => {
+  try {
+    areaTree.value = await AreaApi.getAreaTree()
+
+  } catch (error) {
+    console.error('加载地区树失败:', error)
+  }
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  formType.value = type
+  isReadonly.value = type === 'detail' // 详情模式设为只读
+  
+  // 设置弹窗标题
+  if (type === 'create') {
+    dialogTitle.value = '新增服务人员'
+  } else if (type === 'update') {
+    dialogTitle.value = '编辑服务人员'
+  } else if (type === 'detail') {
+    dialogTitle.value = '服务人员详情'
+  }
+  
+  resetForm()
+  
+  // 加载服务区域列表、部门列表和地区树
+  await loadServiceAreaList()
+  await loadDeptList()
+  await loadAreaTree()
+
+  // 修改或查看详情时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ServicePersonApi.getServicePerson(id)
+      formData.value = {
+        ...data,
+        areaCode: data.areaCode ? data.areaCode.split(',').map(Number) : []
+      }
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 加载服务区域列表 */
+const loadServiceAreaList = async () => {
+  try {
+    const data = await ServicePersonApi.getServiceAreaList()
+    serviceAreaList.value = data || []
+  } catch (error) {
+    console.error('加载服务区域失败:', error)
+  }
+}
+
+/** 加载部门列表 */
+const loadDeptList = async () => {
+  try {
+    const data = await DeptApi.getSimpleDeptList()
+    deptList.value = handleTree(data)
+  } catch (error) {
+    console.error('加载部门列表失败:', error)
+  }
+}
+
+
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件
+const submitForm = async () => {
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  
+  formLoading.value = true
+  try {
+    const data = { ...formData.value } as any
+    // 将级联选择的数组转换为字符串
+    if (Array.isArray(data.areaCode)) {
+      data.areaCode = data.areaCode.join(',')
+    }
+    
+    if (formType.value === 'create') {
+      await ServicePersonApi.createServicePerson(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServicePersonApi.updateServicePerson(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    idCard: '',
+    gender: '',
+    age: '',
+    phone: '',
+    personType: '服务人员',
+    serviceAreaId: undefined,
+    deptId: undefined,
+    areaCode: [],
+    detailAddress: '',
+    avatar: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 336 - 6
src/views/living-home/serviceTeam/servicePersonnelManage/index.vue

@@ -1,11 +1,341 @@
-<script setup lang="ts">
+<template>
+  <!-- 搜索筛选区域 -->
+  <ContentWrap>
+    <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="-mb-15px">
+      <el-form-item label="姓名:" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-180px"
+        />
+      </el-form-item>
+      <el-form-item label="手机号码:" prop="phone">
+        <el-input
+          v-model="queryParams.phone"
+          placeholder="请输入"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-180px"
+        />
+      </el-form-item>
+      <el-form-item label="服务区域:" prop="serviceArea">
+        <el-select
+          v-model="queryParams.serviceArea"
+          placeholder="全部"
+          clearable
+          class="!w-180px"
+        >
+          <el-option label="全部" value="" />
+          <el-option
+            v-for="item in serviceAreaOptions"
+            :key="item"
+            :label="item"
+            :value="item"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="在职状态:" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="全部"
+          clearable
+          class="!w-180px"
+        >
+          <el-option label="全部" value="" />
+          <el-option label="在职" value="在职" />
+          <el-option label="离职" value="离职" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" /> 查询
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" /> 重置
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
 
-</script>
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="flex justify-between items-center mb-4">
+      <div class="text-base font-medium">服务人员列表</div>
+      <div>
+        <el-button type="primary" @click="openForm('create')">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedIds.length">
+          <Icon icon="ep:delete" class="mr-5px" /> 删除
+        </el-button>
+        <el-button type="primary" plain @click="handleExport">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+        <el-button type="primary" plain @click="handleImport">
+          <Icon icon="ep:upload" class="mr-5px" /> 导入服务人员
+        </el-button>
+        <el-button type="primary" plain @click="handleDownloadTemplate">
+          <Icon icon="ep:document" class="mr-5px" /> 下载导入模板
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column type="index" label="序号" width="80" />
+      <el-table-column prop="name" label="姓名" />
+      <el-table-column prop="idCard" label="身份证号" />
+      <el-table-column prop="gender" label="性别" width="80" />
+      <el-table-column prop="age" label="年龄" width="80" />
+      <el-table-column prop="phone" label="手机号码" />
+      <el-table-column prop="elderCount" label="关联长者" width="100" />
+      <el-table-column prop="serviceArea" label="服务区域" show-overflow-tooltip/>
+      <el-table-column prop="organization" label="所属组织" show-overflow-tooltip/>
+      <el-table-column label="操作" align="center" width="350">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleDetail(scope.row)">
+            <Icon icon="ep:view" class="mr-5px" /> 详情
+          </el-button>
+          <el-button link type="primary" @click="openForm('update', scope.row.id)">
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+          <el-button link type="primary" @click="handleRelateElder(scope.row)">
+            <Icon icon="ep:connection" class="mr-5px" /> 关联长者设置
+          </el-button>
+          <el-button link type="danger" @click="handleResign(scope.row)">
+            <Icon icon="ep:close" class="mr-5px" /> 离职
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
 
-<template>
-a
+  <!-- 表单弹窗:添加/修改 -->
+  <ServicePersonForm ref="formRef" @success="getList" />
+
+  <!-- 关联长者设置弹窗 -->
+  <RelateElderDialog ref="relateElderRef" />
 </template>
+<script lang="ts" setup>
+import * as ServicePersonApi from '@/api/living-home/elderly'
+import ServicePersonForm from './ServicePersonForm.vue'
+import RelateElderDialog from './RelateElderDialog.vue'
+
+defineOptions({ name: 'ServicePersonnelManage' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<ServicePersonApi.ServicePersonVO[]>([]) // 列表的数据
+const selectedIds = ref<number[]>([]) // 选中的ID列表
+const queryFormRef = ref() // 搜索表单Ref
+
+// 查询参数
+const queryParams = reactive({
+  name: '',
+  phone: '',
+  serviceArea: '',
+  status: ''
+})
+
+// 服务区域选项(从默认数据中提取)
+const serviceAreaOptions = computed(() => {
+  const areas = new Set(defaultData.map(item => item.serviceArea))
+  return Array.from(areas)
+})
+
+// 默认数据
+const defaultData: ServicePersonApi.ServicePersonVO[] = [
+  {
+    id: 1,
+    name: '张磊',
+    idCard: '43********30',
+    gender: '男',
+    age: '36',
+    phone: '198****9999',
+    personType: '服务人员',
+    elderCount: 0,
+    serviceArea: '东莞市/茶山镇',
+    organization: '客服部',
+    status: '在职'
+  },
+  {
+    id: 2,
+    name: '李华',
+    idCard: '44********21',
+    gender: '女',
+    age: '32',
+    phone: '139****8888',
+    personType: '服务人员',
+    elderCount: 3,
+    serviceArea: '深圳市/南山区',
+    organization: '护理部',
+    status: '在职'
+  },
+  {
+    id: 3,
+    name: '王强',
+    idCard: '11********55',
+    gender: '男',
+    age: '45',
+    phone: '136****6666',
+    personType: '应急人员',
+    elderCount: 5,
+    serviceArea: '广州市/天河区',
+    organization: '应急部',
+    status: '在职'
+  },
+  {
+    id: 4,
+    name: '赵敏',
+    idCard: '31********88',
+    gender: '女',
+    age: '28',
+    phone: '158****7777',
+    personType: '志愿者',
+    elderCount: 2,
+    serviceArea: '佛山市/禅城区',
+    organization: '志愿者协会',
+    status: '在职'
+  },
+  {
+    id: 5,
+    name: '刘洋',
+    idCard: '22********99',
+    gender: '男',
+    age: '38',
+    phone: '137****5555',
+    personType: '服务人员',
+    elderCount: 1,
+    serviceArea: '中山市/石岐区',
+    organization: '服务部',
+    status: '在职'
+  }
+]
+
+/** 查询服务人员列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // 先尝试从接口获取数据
+    const data = await ServicePersonApi.getServicePersonList(queryParams)
+    // 如果接口返回数据为空,则使用默认数据
+    let result = data && data.length > 0 ? data : defaultData
+    
+    // 前端筛选(如果使用默认数据)
+    if (result === defaultData) {
+      result = result.filter(item => {
+        const matchName = !queryParams.name || item.name.includes(queryParams.name)
+        const matchPhone = !queryParams.phone || item.phone.includes(queryParams.phone)
+        const matchArea = !queryParams.serviceArea || item.serviceArea === queryParams.serviceArea
+        const matchStatus = !queryParams.status || item.status === queryParams.status
+        return matchName && matchPhone && matchArea && matchStatus
+      })
+    }
+    
+    list.value = result
+  } catch (error) {
+    // 接口调用失败时,使用默认数据并筛选
+    console.log('使用默认数据')
+    list.value = defaultData.filter(item => {
+      const matchName = !queryParams.name || item.name.includes(queryParams.name)
+      const matchPhone = !queryParams.phone || item.phone.includes(queryParams.phone)
+      const matchArea = !queryParams.serviceArea || item.serviceArea === queryParams.serviceArea
+      const matchStatus = !queryParams.status || item.status === queryParams.status
+      return matchName && matchPhone && matchArea && matchStatus
+    })
+  } finally {
+    loading.value = false
+  }
+}
 
-<style scoped lang="scss">
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
 
-</style>
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  queryParams.name = ''
+  queryParams.phone = ''
+  queryParams.serviceArea = ''
+  queryParams.status = ''
+  getList()
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServicePersonApi.ServicePersonVO[]) => {
+  selectedIds.value = selection.map((item) => item.id)
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 批量删除按钮操作 */
+const handleBatchDelete = async () => {
+  if (selectedIds.value.length === 0) {
+    message.warning('请选择要删除的数据')
+    return
+  }
+  try {
+    await message.delConfirm()
+    await ServicePersonApi.deleteServicePerson(selectedIds.value)
+    message.success(t('common.delSuccess'))
+    await getList()
+  } catch {}
+}
+
+/** 导出 */
+const handleExport = () => {
+  // TODO: 实现导出功能
+  message.info('导出功能开发中')
+}
+
+/** 导入 */
+const handleImport = () => {
+  // TODO: 实现导入功能
+  message.info('导入功能开发中')
+}
+
+/** 下载导入模板 */
+const handleDownloadTemplate = () => {
+  // TODO: 实现下载模板功能
+  message.info('下载模板功能开发中')
+}
+
+/** 详情 */
+const handleDetail = (row: ServicePersonApi.ServicePersonVO) => {
+  openForm('detail', row.id)
+}
+
+/** 关联长者设置 */
+const relateElderRef = ref()
+const handleRelateElder = (row: ServicePersonApi.ServicePersonVO) => {
+  relateElderRef.value.open(row.id)
+}
+
+/** 离职 */
+const handleResign = async (row: ServicePersonApi.ServicePersonVO) => {
+  try {
+    await message.confirm('确认将该服务人员设为离职状态?')
+    await ServicePersonApi.resignServicePerson(row.id)
+    message.success('操作成功')
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
+</script>