Selaa lähdekoodia

Merge branch 'master' of http://47.107.245.0:3000/xiongxing/kyj-yanglao-web-new

xiongxing 2 viikkoa sitten
vanhempi
commit
6f5efd2eda

+ 896 - 0
src/views/living-home/visiting-service/appointment-list/AddForm.vue

@@ -0,0 +1,896 @@
+<template>
+  <Dialog v-model="dialogVisible" scroll :title="dialogTitle" width="66vw">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="110px"
+    >
+      <!-- 长者信息选择 -->
+      <div class="section-title">长者信息</div>
+      
+      <el-form-item label="长者姓名" prop="elderId">
+        <el-select
+          v-model="formData.elderId"
+          placeholder="请输入长者姓名搜索"
+          class="w-full"
+          filterable
+          remote
+          clearable
+          :disabled="isDetailMode"
+          :remote-method="searchElderRemote"
+          :loading="elderSearchLoading"
+          @change="handleElderChange"
+        >
+          <el-option
+            v-for="item in elderSearchOptions"
+            :key="item.id"
+            :label="`${item.elderName} ${item.phone || ''} ${item.currentLiveAddress || ''}`"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <!-- 长者详情展示 -->
+      <el-row :gutter="20" v-if="selectedElder">
+        <el-col :span="8">
+          <el-form-item label="姓名">
+            <el-input v-model="selectedElder.elderName" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="性别">
+            <el-input :value="selectedElder.sex === '1' ? '男' : '女'" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="年龄">
+            <el-input :value="selectedElder.age || '-'" disabled />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20" v-if="selectedElder">
+        <el-col :span="12">
+          <el-form-item label="手机号码">
+            <el-input v-model="selectedElder.phone" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="居住地址">
+            <el-input v-model="selectedElder.currentLiveAddress" disabled />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 工单信息 -->
+      <div class="section-title">工单信息</div>
+
+      <!-- 服务项目选择 -->
+      <el-form-item label="服务项目" prop="serviceItems">
+        <!-- 搜索框 -->
+        <el-input
+          v-model="serviceItemSearchKeyword"
+          placeholder="请输入项目名称搜索"
+          clearable
+          :disabled="isDetailMode"
+          @input="handleServiceItemSearch"
+        >
+          <template #prefix>
+            <Icon icon="ep:search" />
+          </template>
+        </el-input>
+      </el-form-item>
+      
+      <!-- 已选择展示 -->
+      <el-form-item v-if="selectedServiceItems.length > 0">
+        <div class="selected-info">
+          <span class="selected-label">已选:</span>
+          <span class="selected-names">{{ selectedServiceItemNames }}</span>
+          <span class="selected-count">({{ selectedServiceItems.length }}项,合计¥{{ totalServiceAmount }})</span>
+        </div>
+      </el-form-item>
+      
+      <!-- 服务项目卡片列表 -->
+      <el-form-item>
+        <div class="service-item-list" :class="{ 'detail-mode': isDetailMode }">
+          <div
+            v-for="item in displayedServiceItemList"
+            :key="item.id"
+            class="service-item-card"
+            :class="{ selected: selectedServiceItems.some(s => s.id === item.id) }"
+            @click="!isDetailMode && toggleServiceItem(item)"
+          >
+            <div class="card-header">
+              <el-checkbox 
+                :model-value="selectedServiceItems.some(s => s.id === item.id)"
+                @click.stop
+                @change="!isDetailMode && toggleServiceItem(item)"
+              />
+            </div>
+            <div class="card-body">
+              <div class="item-name" :title="item.itemName">{{ item.itemName }}</div>
+              <div class="item-amount">¥{{ item.amount }}</div>
+            </div>
+          </div>
+          <div v-if="displayedServiceItemList.length === 0" class="empty-text">
+            暂无匹配的服务项目
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="下单方式" prop="orderType">
+        <el-select
+          v-model="formData.orderType"
+          placeholder="请选择下单方式"
+          class="w-full"
+          :disabled="isDetailMode"
+        >
+          <el-option label="报警" value="报警" />
+          <el-option label="电话咨询" value="电话咨询" />
+          <el-option label="自主下单" value="自主下单" />
+          <el-option label="上门关怀" value="上门关怀" />
+          <el-option label="工单详情下单" value="工单详情下单" />
+          <el-option label="长者小程序下单" value="长者小程序下单" />
+          <el-option label="守护中心下单" value="守护中心下单" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="服务人员" prop="servicePersonId">
+        <el-select
+          v-model="formData.servicePersonId"
+          placeholder="请选择服务人员"
+          clearable
+          class="w-full"
+          filterable
+          :disabled="isDetailMode"
+        >
+          <el-option
+            v-for="item in servicePersonList"
+            :key="item.id"
+            :label="`${item.name} ${item.phone ? '(' + item.phone + ')' : ''}`"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="下单时间" prop="orderTime">
+        <el-date-picker
+          v-model="formData.orderTime"
+          type="datetime"
+          placeholder="选择下单时间"
+          class="w-full"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="上门开始时间" prop="visitStartTime">
+            <el-date-picker
+              v-model="formData.visitStartTime"
+              type="datetime"
+              placeholder="选择上门开始时间"
+              class="w-full"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              :disabled="isDetailMode"
+              @change="handleVisitStartTimeChange"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="上门结束时间" prop="visitEndTime">
+            <el-date-picker
+              v-model="formData.visitEndTime"
+              type="datetime"
+              placeholder="选择上门结束时间"
+              class="w-full"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              :disabled="isDetailMode"
+              @change="handleVisitEndTimeChange"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="服务时长" prop="serviceDuration">
+        <el-row :gutter="10" class="w-full">
+          <el-col :span="12">
+            <el-input-number
+              v-model="formData.serviceDuration"
+              :min="0"
+              :step="15"
+              placeholder="请输入服务时长(分钟)"
+              class="w-full"
+              :disabled="isDetailMode"
+              @change="handleDurationChange"
+            />
+          </el-col>
+          <el-col :span="12" class="flex items-center">
+            <span class="duration-display">{{ formattedDuration }}</span>
+          </el-col>
+        </el-row>
+      </el-form-item>
+
+      <el-form-item label="工单备注" prop="remark">
+        <el-input
+          v-model="formData.remark"
+          type="textarea"
+          :rows="3"
+          placeholder="请输入工单备注"
+          maxlength="200"
+          show-word-limit
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <!-- 金额信息 -->
+      <div class="section-title">金额信息</div>
+
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-form-item label="项目金额" prop="projectAmount">
+            <el-input-number
+              v-model="formData.projectAmount"
+              :min="0"
+              :precision="2"
+              placeholder="请输入项目金额"
+              class="w-full"
+              :disabled="isDetailMode"
+              @change="handleProjectAmountChange"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="补贴金额" prop="subsidyAmount">
+            <el-input-number
+              v-model="formData.subsidyAmount"
+              :min="0"
+              :precision="2"
+              placeholder="补贴金额"
+              class="w-full"
+              disabled
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="自费金额" prop="selfPayAmount">
+            <el-input-number
+              v-model="formData.selfPayAmount"
+              :min="0"
+              :precision="2"
+              placeholder="自费金额"
+              class="w-full"
+              disabled
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="补贴到账日期" prop="subsidyArrivalDate">
+        <el-date-picker
+          v-model="formData.subsidyArrivalDate"
+          type="date"
+          placeholder="选择补贴到账日期"
+          class="w-full"
+          value-format="YYYY-MM-DD"
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <!-- 详情模式下显示额外信息 -->
+      <template v-if="isDetailMode && detailData">
+        <div class="section-title">其他信息</div>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="下单时间">
+              <el-input v-model="detailData.orderTime" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="是否过期">
+              <el-input :value="detailData.isExpired === '是' ? '是' : '否'" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="工单状态">
+              <el-input v-model="detailData.status" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </template>
+    </el-form>
+
+    <!-- 后端提交参数说明 -->
+    <el-divider content-position="left">后端接口参数说明</el-divider>
+    <el-descriptions :column="2" border size="small" class="field-description">
+      <el-descriptions-item label="elderId" :span="1">长者ID(number,必填)</el-descriptions-item>
+      <el-descriptions-item label="elderName" :span="1">长者姓名(string,必填)</el-descriptions-item>
+      
+      <el-descriptions-item label="gender" :span="1">性别(string,男/女)</el-descriptions-item>
+      <el-descriptions-item label="age" :span="1">年龄(number)</el-descriptions-item>
+      
+      <el-descriptions-item label="phone" :span="1">手机号码(string)</el-descriptions-item>
+      <el-descriptions-item label="address" :span="1">居住地址(string)</el-descriptions-item>
+      
+      <el-descriptions-item label="orderType" :span="1">下单方式(string,必填)</el-descriptions-item>
+      <el-descriptions-item label="servicePersonId" :span="1">服务人员ID(number,必填)</el-descriptions-item>
+      
+      <el-descriptions-item label="servicePersonName" :span="1">服务人员姓名(string,必填)</el-descriptions-item>
+      <el-descriptions-item label="orderTime" :span="1">下单时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      
+      <el-descriptions-item label="visitStartTime" :span="1">上门开始时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      <el-descriptions-item label="visitEndTime" :span="1">上门结束时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      
+      <el-descriptions-item label="serviceDuration" :span="1">服务时长(number,分钟)</el-descriptions-item>
+      <el-descriptions-item label="projectAmount" :span="1">项目金额(number,元)</el-descriptions-item>
+      
+      <el-descriptions-item label="subsidyAmount" :span="1">补贴金额(number,元,默认等于项目金额)</el-descriptions-item>
+      <el-descriptions-item label="selfPayAmount" :span="1">自费金额(number,元,默认为0)</el-descriptions-item>
+      
+      <el-descriptions-item label="serviceItems" :span="2">
+        <div>服务项目列表(array,必填)</div>
+        <div style="margin-top: 5px; color: #606266; font-size: 12px;">
+          每项包含:id(项目ID)、itemName(项目名称)、amount(金额)
+        </div>
+      </el-descriptions-item>
+      
+      <el-descriptions-item label="remark" :span="2">工单备注(string)</el-descriptions-item>
+    </el-descriptions>
+
+    <template #footer>
+      <el-button @click="dialogVisible = false">{{ isDetailMode ? '关闭' : '取消' }}</el-button>
+      <el-button v-if="!isDetailMode" type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import * as ElderApi from '@/api/living-home/elderly'
+
+const emit = defineEmits(['success'])
+
+const dialogVisible = ref(false)
+const dialogMode = ref<'create' | 'edit' | 'detail'>('create')
+const submitLoading = ref(false)
+const formRef = ref()
+const servicePersonList = ref<any[]>([])
+const serviceItemList = ref<any[]>([])
+const selectedServiceItems = ref<any[]>([])
+const serviceItemSearchKeyword = ref('')
+const detailData = ref<any>({})
+
+// 长者搜索相关
+const elderSearchLoading = ref(false)
+const elderSearchOptions = ref<any[]>([])
+const selectedElder = ref<any>(null)
+
+const formData = reactive({
+  id: undefined as number | undefined,
+  elderId: undefined as number | undefined,
+  elderName: '',
+  gender: '',
+  age: undefined as number | undefined,
+  phone: '',
+  address: '',
+  orderType: '电话咨询', // 默认使用电话咨询
+  servicePersonId: undefined as number | undefined,
+  servicePersonName: '',
+  serviceItemIds: [] as number[], // 服务项目ID
+  orderTime: '', // 下单时间
+  visitStartTime: '', // 上门开始时间
+  visitEndTime: '', // 上门结束时间
+  serviceDuration: undefined as number | undefined, // 服务时长(分钟)
+  serviceItems: [] as number[], // 服务项目ID列表
+  projectAmount: 0, // 项目金额,默认为0
+  subsidyAmount: 0, // 补贴金额,默认为0
+  selfPayAmount: 0, // 自费金额,默认为0
+  subsidyArrivalDate: '', // 补贴到账日期
+  remark: ''
+})
+
+const formRules = {
+  elderId: [{ required: true, message: '请选择长者', trigger: 'change' }],
+  serviceItems: [{ required: true, message: '请选择服务项目', trigger: 'change', type: 'array' }],
+  orderType: [{ required: true, message: '请选择下单方式', trigger: 'change' }],
+  servicePersonId: [{ required: true, message: '请选择服务人员', trigger: 'change' }],
+  orderTime: [{ required: true, message: '请选择下单时间', trigger: 'change' }],
+  visitStartTime: [{ required: true, message: '请选择上门开始时间', trigger: 'change' }],
+  visitEndTime: [{ required: true, message: '请选择上门结束时间', trigger: 'change' }],
+  serviceDuration: [{ required: true, message: '请输入服务时长', trigger: 'change' }],
+  projectAmount: [{ required: true, message: '请输入项目金额', trigger: 'change' }]
+}
+
+/** 是否为详情模式(只读) */
+const isDetailMode = computed(() => dialogMode.value === 'detail')
+
+/** 弹窗标题 */
+const dialogTitle = computed(() => {
+  const titles = { create: '新增预约', edit: '编辑预约', detail: '预约详情' }
+  return titles[dialogMode.value]
+})
+
+// 远程搜索长者
+const searchElderRemote = async (query: string) => {
+  if (query) {
+    elderSearchLoading.value = true
+    try {
+      const params = {
+        pageNo: 1,
+        pageSize: 50,
+        elderName: query
+      }
+      const data = await ElderApi.getHomeElderlyList(params)
+      elderSearchOptions.value = data.list || []
+    } catch (error) {
+      console.log('搜索长者失败', error)
+    } finally {
+      elderSearchLoading.value = false
+    }
+  } else {
+    elderSearchOptions.value = []
+  }
+}
+
+// 长者选择变化
+const handleElderChange = (val: number) => {
+  if (val) {
+    const elder = elderSearchOptions.value.find(item => item.id === val)
+    if (elder) {
+      selectedElder.value = elder
+      formData.elderName = elder.elderName
+      formData.gender = elder.sex === '1' ? '男' : '女'
+      formData.age = elder.age
+      formData.phone = elder.phone
+      formData.address = elder.currentLiveAddress
+    }
+  } else {
+    selectedElder.value = null
+    formData.elderName = ''
+    formData.gender = ''
+    formData.age = undefined
+    formData.phone = ''
+    formData.address = ''
+  }
+}
+
+// 格式化服务时长显示
+const formattedDuration = computed(() => {
+  const minutes = formData.serviceDuration
+  if (!minutes || minutes <= 0) return ''
+  const hours = Math.floor(minutes / 60)
+  const mins = minutes % 60
+  if (hours > 0 && mins > 0) {
+    return `${hours}小时${mins}分钟`
+  } else if (hours > 0) {
+    return `${hours}小时`
+  } else {
+    return `${mins}分钟`
+  }
+})
+
+// 计算时间差(分钟)
+const calculateDuration = (startTime: string, endTime: string): number => {
+  if (!startTime || !endTime) return 0
+  const start = new Date(startTime).getTime()
+  const end = new Date(endTime).getTime()
+  if (isNaN(start) || isNaN(end) || end <= start) return 0
+  return Math.round((end - start) / (1000 * 60))
+}
+
+// 计算结束时间
+const calculateEndTime = (startTime: string, durationMinutes: number): string => {
+  if (!startTime || !durationMinutes || durationMinutes <= 0) return ''
+  const start = new Date(startTime)
+  if (isNaN(start.getTime())) return ''
+  const end = new Date(start.getTime() + durationMinutes * 60 * 1000)
+  const year = end.getFullYear()
+  const month = String(end.getMonth() + 1).padStart(2, '0')
+  const day = String(end.getDate()).padStart(2, '0')
+  const hours = String(end.getHours()).padStart(2, '0')
+  const minutes = String(end.getMinutes()).padStart(2, '0')
+  const seconds = String(end.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+// 上门开始时间变化
+const handleVisitStartTimeChange = () => {
+  if (formData.visitStartTime && formData.serviceDuration) {
+    formData.visitEndTime = calculateEndTime(formData.visitStartTime, formData.serviceDuration)
+  }
+}
+
+// 上门结束时间变化
+const handleVisitEndTimeChange = () => {
+  if (formData.visitStartTime && formData.visitEndTime) {
+    const duration = calculateDuration(formData.visitStartTime, formData.visitEndTime)
+    if (duration > 0) {
+      formData.serviceDuration = duration
+    }
+  }
+}
+
+// 服务时长变化
+const handleDurationChange = () => {
+  if (formData.visitStartTime && formData.serviceDuration) {
+    formData.visitEndTime = calculateEndTime(formData.visitStartTime, formData.serviceDuration)
+  }
+}
+
+// 项目金额变化
+const handleProjectAmountChange = () => {
+  const amount = formData.projectAmount || 0
+  formData.subsidyAmount = amount
+  formData.selfPayAmount = 0
+}
+
+// 获取服务人员列表
+const getServicePersonList = async () => {
+  try {
+    const params = { pageNo: 1, pageSize: 1000 }
+    const data = await ElderApi.getServicePersonList(params)
+    servicePersonList.value = data.list || []
+  } catch (error) {
+    console.log('获取服务人员列表失败', error)
+  }
+}
+
+// 获取服务项目列表
+const getServiceItemList = async () => {
+  try {
+    const params = { pageNo: 1, pageSize: 1000 }
+    const data = await ElderApi.getServiceItemList(params)
+    serviceItemList.value = data.list || []
+  } catch (error) {
+    console.log('获取服务项目列表失败', error)
+  }
+}
+
+// 过滤后的服务项目列表(搜索用)
+const filteredServiceItemList = computed(() => {
+  if (!serviceItemSearchKeyword.value) {
+    return serviceItemList.value
+  }
+  const keyword = serviceItemSearchKeyword.value.toLowerCase()
+  return serviceItemList.value.filter(item => 
+    item.itemName?.toLowerCase().includes(keyword)
+  )
+})
+
+// 展示的服务项目列表(最多10条)
+const displayedServiceItemList = computed(() => {
+  return filteredServiceItemList.value.slice(0, 10)
+})
+
+// 已选择项目名称(用于显示)
+const selectedServiceItemNames = computed(() => {
+  return selectedServiceItems.value.map(item => item.itemName).join('、')
+})
+
+// 服务项目搜索
+const handleServiceItemSearch = () => {
+  // 搜索时只过滤列表,不影响已选择的项目
+}
+
+// 服务项目总金额计算
+const totalServiceAmount = computed(() => {
+  return selectedServiceItems.value.reduce((sum, item) => sum + (item.amount || 0), 0)
+})
+
+// 切换服务项目选择
+const toggleServiceItem = (item: any) => {
+  const index = selectedServiceItems.value.findIndex(s => s.id === item.id)
+  if (index > -1) {
+    selectedServiceItems.value.splice(index, 1)
+  } else {
+    selectedServiceItems.value.push(item)
+  }
+  // 更新表单数据
+  formData.serviceItems = selectedServiceItems.value.map(s => s.id)
+  formData.serviceItemIds = formData.serviceItems
+  // 自动更新项目金额
+  formData.projectAmount = totalServiceAmount.value
+  // 更新补贴金额(等于项目金额)
+  formData.subsidyAmount = formData.projectAmount
+  formData.selfPayAmount = 0
+}
+
+// 打开弹窗
+const open = async (row?: any, mode: 'create' | 'edit' | 'detail' = 'create') => {
+  dialogMode.value = mode
+  dialogVisible.value = true
+  resetForm()
+  
+  // 加载服务人员列表和服务项目列表
+  await getServicePersonList()
+  await getServiceItemList()
+
+  if (row && (mode === 'edit' || mode === 'detail')) {
+    // 回填数据
+    await fillFormData(row)
+    detailData.value = row
+  }
+}
+
+// 回填表单数据
+const fillFormData = async (row: any) => {
+  formData.id = row.id
+  formData.elderId = row.elderId
+  formData.elderName = row.elderName
+  formData.gender = row.gender
+  formData.age = row.age
+  formData.phone = row.phone
+  formData.address = row.address
+  formData.orderType = row.orderType
+  formData.servicePersonId = row.servicePersonId
+  formData.servicePersonName = row.servicePersonName
+  formData.orderTime = row.orderTime
+  formData.visitStartTime = row.visitStartTime || row.visitTime
+  formData.visitEndTime = row.visitEndTime
+  formData.serviceDuration = row.serviceDuration
+  formData.projectAmount = row.projectAmount || 0
+  formData.subsidyAmount = row.subsidyAmount || row.projectAmount || 0
+  formData.selfPayAmount = row.selfPayAmount || 0
+  formData.subsidyArrivalDate = row.subsidyArrivalDate || ''
+  formData.remark = row.remark || ''
+
+  // 回填服务项目
+  if (row.serviceItems && row.serviceItems.length > 0) {
+    const itemIds = row.serviceItems.map((item: any) => typeof item === 'object' ? item.id : item)
+    formData.serviceItems = itemIds
+    formData.serviceItemIds = itemIds
+    // 从列表中找到对应的服务项目
+    selectedServiceItems.value = serviceItemList.value.filter(item => itemIds.includes(item.id))
+  } else {
+    formData.serviceItems = []
+    formData.serviceItemIds = []
+    selectedServiceItems.value = []
+  }
+
+  // 设置选中的长者信息
+  selectedElder.value = {
+    id: row.elderId,
+    elderName: row.elderName,
+    sex: row.gender === '男' ? '1' : '2',
+    age: row.age,
+    phone: row.phone,
+    currentLiveAddress: row.address
+  }
+  elderSearchOptions.value = [selectedElder.value]
+}
+
+// 获取当前时间格式化
+const getCurrentDateTime = (): string => {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = String(now.getMonth() + 1).padStart(2, '0')
+  const day = String(now.getDate()).padStart(2, '0')
+  const hours = String(now.getHours()).padStart(2, '0')
+  const minutes = String(now.getMinutes()).padStart(2, '0')
+  const seconds = String(now.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.id = undefined
+  formData.elderId = undefined
+  formData.elderName = ''
+  formData.gender = ''
+  formData.age = undefined
+  formData.phone = ''
+  formData.address = ''
+  formData.orderType = '电话咨询'
+  formData.servicePersonId = undefined
+  formData.servicePersonName = ''
+  formData.orderTime = getCurrentDateTime() // 默认当前时间
+  formData.visitStartTime = ''
+  formData.visitEndTime = ''
+  formData.serviceDuration = undefined
+  formData.serviceItems = []
+  formData.serviceItemIds = []
+  selectedServiceItems.value = []
+  serviceItemSearchKeyword.value = ''
+  formData.projectAmount = 0
+  formData.subsidyAmount = 0
+  formData.selfPayAmount = 0
+  formData.remark = ''
+  
+  selectedElder.value = null
+  elderSearchOptions.value = []
+  
+  nextTick(() => {
+    formRef.value?.resetFields()
+  })
+}
+
+// 提交表单
+const handleSubmit = async () => {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  submitLoading.value = true
+  try {
+    // 获取选中的服务人员名称
+    const servicePerson = servicePersonList.value.find(item => item.id === formData.servicePersonId)
+    const servicePersonName = servicePerson?.name || ''
+
+    const data = {
+      ...formData,
+      servicePersonName,
+      serviceItems: selectedServiceItems.value.map(item => ({
+        id: item.id,
+        itemName: item.itemName,
+        amount: item.amount
+      }))
+    }
+
+    if (dialogMode.value === 'edit' && formData.id) {
+      await ElderApi.updateAppointment(data)
+      ElMessage.success('编辑成功')
+    } else {
+      await ElderApi.createAppointment(data)
+      ElMessage.success('新增成功')
+    }
+    
+    dialogVisible.value = false
+    emit('success')
+  } catch (error) {
+    console.log('提交失败', error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+defineExpose({ open })
+</script>
+
+<style scoped lang="scss">
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin: 20px 0 15px 0;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+  
+  &:first-child {
+    margin-top: 0;
+  }
+}
+
+.w-full {
+  width: 100%;
+}
+
+.duration-display {
+  color: #606266;
+  font-size: 14px;
+  margin-left: 10px;
+}
+
+.mt-10px {
+  margin-top: 10px;
+}
+
+.mt-15px {
+  margin-top: 15px;
+}
+
+.mr-5px {
+  margin-right: 5px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin: 20px 0 15px 0;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+  
+  &:first-child {
+    margin-top: 0;
+  }
+}
+
+.selected-info {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 5px;
+  padding: 8px 12px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  font-size: 13px;
+  
+  .selected-label {
+    color: #606266;
+    font-weight: 500;
+  }
+  
+  .selected-names {
+    color: #303133;
+    font-weight: bold;
+  }
+  
+  .selected-count {
+    color: #409eff;
+    font-weight: bold;
+  }
+}
+
+.service-item-list {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 10px;
+  padding: 10px 0;
+  overflow-x: auto;
+  overflow-y: hidden;
+  
+  &.detail-mode {
+    .service-item-card {
+      cursor: default;
+    }
+  }
+  
+  .empty-text {
+    width: 100%;
+    text-align: center;
+    color: #909399;
+    padding: 20px 0;
+    font-size: 14px;
+  }
+}
+
+.service-item-card {
+  width: calc(20% - 8px);
+  min-width: 120px;
+  max-width: 150px;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  padding: 8px 10px;
+  cursor: pointer;
+  transition: all 0.3s;
+  background-color: #fff;
+  
+  &:hover {
+    border-color: #409eff;
+    box-shadow: 0 2px 8px 0 rgba(64, 158, 255, 0.15);
+  }
+  
+  &.selected {
+    border-color: #409eff;
+    background-color: #ecf5ff;
+  }
+  
+  .card-header {
+    display: flex;
+    justify-content: flex-end;
+    margin-bottom: 4px;
+  }
+  
+  .card-body {
+    text-align: center;
+    
+    .item-name {
+      font-size: 13px;
+      font-weight: 500;
+      color: #303133;
+      margin-bottom: 4px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    
+    .item-amount {
+      font-size: 14px;
+      color: #f56c6c;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 64 - 30
src/views/living-home/visiting-service/appointment-list/index.vue

@@ -80,12 +80,17 @@
   <!-- 列表 -->
   <ContentWrap>
     <div class="flex items-center justify-between mb-15px">
-      <span class="font-bold text-base">预约服务工单</span>
+
       <div class="flex gap-10px">
         <el-button type="primary" @click="handleOrderByElder">
-          <Icon icon="ep:user" class="mr-5px" /> 按长者预约
+          <Icon icon="ep:user" class="mr-5px" /> 新增补贴登记
+        </el-button>
+        <el-button type="success" @click="handleOrderUpload">
+          <Icon icon="ep:upload" class="mr-5px" /> 导入
+        </el-button>
+        <el-button type="warning" @click="handleOrderDownload">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
-
       </div>
     </div>
     <el-table
@@ -94,17 +99,46 @@
       row-key="id"
     >
       <el-table-column label="序号" type="index" width="80" align="center" />
-      <el-table-column prop="elderName" label="姓名" align="center" min-width="100" />
+      <!-- 长者信息 -->
+      <el-table-column prop="elderName" label="长者姓名" align="center" min-width="100" />
       <el-table-column prop="gender" label="性别" align="center" width="80" />
       <el-table-column prop="age" label="年龄" align="center" width="80" />
       <el-table-column prop="phone" label="手机号码" align="center" min-width="120" />
       <el-table-column prop="address" label="居住地址" align="center" min-width="200" show-overflow-tooltip />
+      <!-- 工单信息 -->
       <el-table-column prop="orderType" label="下单方式" align="center" min-width="120" />
-      <el-table-column prop="servicePersonName" label="服务人员姓名" align="center" min-width="120" />
+      <el-table-column prop="servicePersonName" label="服务人员" align="center" min-width="120" />
       <el-table-column prop="orderTime" label="下单时间" align="center" min-width="160" />
-      <el-table-column prop="visitTime" label="上门时间" align="center" min-width="120" />
-      <el-table-column prop="isExpired" label="是否过期" align="center" width="100" />
-      <el-table-column label="操作" align="center" width="350" fixed="right">
+      <el-table-column prop="visitStartTime" label="上门开始时间" align="center" min-width="160" />
+      <el-table-column prop="visitEndTime" label="上门结束时间" align="center" min-width="160" />
+      <el-table-column prop="serviceDuration" label="服务时长(分钟)" align="center" width="120" />
+      <!-- 服务项目 -->
+      <el-table-column label="服务项目" align="center" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          {{ scope.row.serviceItems ? scope.row.serviceItems.map(item => item.itemName || item).join('、') : '-' }}
+        </template>
+      </el-table-column>
+      <!-- 金额信息 -->
+      <el-table-column prop="projectAmount" label="项目金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.projectAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="subsidyAmount" label="补贴金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.subsidyAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="selfPayAmount" label="自费金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.selfPayAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="subsidyArrivalDate" label="补贴到账日期" align="center" width="120" />
+      <!-- 其他 -->
+      <el-table-column prop="remark" label="工单备注" align="center" min-width="150" show-overflow-tooltip />
+
+      <el-table-column label="操作" align="center" width="280" fixed="right">
         <template #default="scope">
           <el-button link type="primary" @click="handleDetail(scope.row)">
             <Icon icon="ep:view" class="mr-5px" /> 详情
@@ -116,26 +150,26 @@
             <Icon icon="ep:delete" class="mr-5px" /> 删除
           </el-button>
 
-          <el-dropdown @command="(command) => handleSignInCommand(command, scope.row)" trigger="hover">
-            <el-button style="margin-left: 10px">
-              其他操作<Icon icon="ep:arrow-down" class="ml-5px" />
-            </el-button>
-            <template #dropdown>
-              <el-dropdown-menu>
-                <el-dropdown-item command="record">
-                  <Icon icon="ep:list" class="mr-5px" /> 签到记录
-                </el-dropdown-item>
-                <el-dropdown-item command="quick">
-                  <Icon icon="ep:check" class="mr-5px" /> 一键签到
-                </el-dropdown-item>
-                <el-dropdown-item>
-                  <el-button link type="success" @click="handleChangeServicePerson(scope.row)">
-                    <Icon icon="ep:edit" class="mr-5px" /> 修改服务人员
-                  </el-button>
-                </el-dropdown-item>
-              </el-dropdown-menu>
-            </template>
-          </el-dropdown>
+<!--          <el-dropdown @command="(command) => handleSignInCommand(command, scope.row)" trigger="hover">-->
+<!--            <el-button style="margin-left: 10px">-->
+<!--              其他操作<Icon icon="ep:arrow-down" class="ml-5px" />-->
+<!--            </el-button>-->
+<!--            <template #dropdown>-->
+<!--              <el-dropdown-menu>-->
+<!--                <el-dropdown-item command="record">-->
+<!--                  <Icon icon="ep:list" class="mr-5px" /> 签到记录-->
+<!--                </el-dropdown-item>-->
+<!--                <el-dropdown-item command="quick">-->
+<!--                  <Icon icon="ep:check" class="mr-5px" /> 一键签到-->
+<!--                </el-dropdown-item>-->
+<!--                <el-dropdown-item>-->
+<!--                  <el-button link type="success" @click="handleChangeServicePerson(scope.row)">-->
+<!--                    <Icon icon="ep:edit" class="mr-5px" /> 修改服务人员-->
+<!--                  </el-button>-->
+<!--                </el-dropdown-item>-->
+<!--              </el-dropdown-menu>-->
+<!--            </template>-->
+<!--          </el-dropdown>-->
         </template>
       </el-table-column>
     </el-table>
@@ -151,7 +185,7 @@
   </ContentWrap>
 
   <!-- 按长者预约弹窗(同时用于详情/编辑) -->
-  <OrderByElderDialog ref="orderByElderRef" @success="getList" />
+  <AddForm ref="orderByElderRef" @success="getList" />
 
   <!-- 修改服务人员弹窗 -->
   <ChangeServicePersonDialog ref="changeServicePersonRef" @success="getList" />
@@ -165,10 +199,10 @@
 
 <script lang="ts" setup>
 import * as AppointmentApi from '@/api/living-home/elderly'
-import OrderByElderDialog from './OrderByElderDialog.vue'
 import ChangeServicePersonDialog from './ChangeServicePersonDialog.vue'
 import SignInRecordDialog from './SignInRecordDialog.vue'
 import QuickSignInDialog from './QuickSignInDialog.vue'
+import AddForm from './AddForm.vue'
 
 defineOptions({ name: 'AppointmentList' })
 

+ 906 - 0
src/views/living-home/visiting-service/self-funded-registration/AddForm.vue

@@ -0,0 +1,906 @@
+<template>
+  <Dialog v-model="dialogVisible" scroll :title="dialogTitle" width="66vw">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="110px"
+    >
+      <!-- 长者信息选择 -->
+      <div class="section-title">长者信息</div>
+      
+      <el-form-item label="长者姓名" prop="elderId">
+        <el-select
+          v-model="formData.elderId"
+          placeholder="请输入长者姓名搜索"
+          class="w-full"
+          filterable
+          remote
+          clearable
+          :disabled="isDetailMode"
+          :remote-method="searchElderRemote"
+          :loading="elderSearchLoading"
+          @change="handleElderChange"
+        >
+          <el-option
+            v-for="item in elderSearchOptions"
+            :key="item.id"
+            :label="`${item.elderName} ${item.phone || ''} ${item.currentLiveAddress || ''}`"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <!-- 长者详情展示 -->
+      <el-row :gutter="20" v-if="selectedElder">
+        <el-col :span="8">
+          <el-form-item label="姓名">
+            <el-input v-model="selectedElder.elderName" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="性别">
+            <el-input :value="selectedElder.sex === '1' ? '男' : '女'" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="年龄">
+            <el-input :value="selectedElder.age || '-'" disabled />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20" v-if="selectedElder">
+        <el-col :span="12">
+          <el-form-item label="手机号码">
+            <el-input v-model="selectedElder.phone" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="居住地址">
+            <el-input v-model="selectedElder.currentLiveAddress" disabled />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 工单信息 -->
+      <div class="section-title">工单信息</div>
+
+      <!-- 服务项目选择 -->
+      <el-form-item label="服务项目" prop="serviceItems">
+        <!-- 搜索框 -->
+        <el-input
+          v-model="serviceItemSearchKeyword"
+          placeholder="请输入项目名称搜索"
+          clearable
+          :disabled="isDetailMode"
+          @input="handleServiceItemSearch"
+        >
+          <template #prefix>
+            <Icon icon="ep:search" />
+          </template>
+        </el-input>
+      </el-form-item>
+      
+      <!-- 已选择展示 -->
+      <el-form-item v-if="selectedServiceItems.length > 0">
+        <div class="selected-info">
+          <span class="selected-label">已选:</span>
+          <span class="selected-names">{{ selectedServiceItemNames }}</span>
+          <span class="selected-count">({{ selectedServiceItems.length }}项,合计¥{{ totalServiceAmount }})</span>
+        </div>
+      </el-form-item>
+      
+      <!-- 服务项目卡片列表 -->
+      <el-form-item>
+        <div class="service-item-list" :class="{ 'detail-mode': isDetailMode }">
+          <div
+            v-for="item in displayedServiceItemList"
+            :key="item.id"
+            class="service-item-card"
+            :class="{ selected: selectedServiceItems.some(s => s.id === item.id) }"
+            @click="!isDetailMode && toggleServiceItem(item)"
+          >
+            <div class="card-header">
+              <el-checkbox 
+                :model-value="selectedServiceItems.some(s => s.id === item.id)"
+                @click.stop
+                @change="!isDetailMode && toggleServiceItem(item)"
+              />
+            </div>
+            <div class="card-body">
+              <div class="item-name" :title="item.itemName">{{ item.itemName }}</div>
+              <div class="item-amount">¥{{ item.amount }}</div>
+            </div>
+          </div>
+          <div v-if="displayedServiceItemList.length === 0" class="empty-text">
+            暂无匹配的服务项目
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="下单方式" prop="orderType">
+        <el-select
+          v-model="formData.orderType"
+          placeholder="请选择下单方式"
+          class="w-full"
+          :disabled="isDetailMode"
+        >
+          <el-option label="报警" value="报警" />
+          <el-option label="电话咨询" value="电话咨询" />
+          <el-option label="自主下单" value="自主下单" />
+          <el-option label="上门关怀" value="上门关怀" />
+          <el-option label="工单详情下单" value="工单详情下单" />
+          <el-option label="长者小程序下单" value="长者小程序下单" />
+          <el-option label="守护中心下单" value="守护中心下单" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="服务人员" prop="servicePersonId">
+        <el-select
+          v-model="formData.servicePersonId"
+          placeholder="请选择服务人员"
+          clearable
+          class="w-full"
+          filterable
+          :disabled="isDetailMode"
+        >
+          <el-option
+            v-for="item in servicePersonList"
+            :key="item.id"
+            :label="`${item.name} ${item.phone ? '(' + item.phone + ')' : ''}`"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="下单时间" prop="orderTime">
+        <el-date-picker
+          v-model="formData.orderTime"
+          type="datetime"
+          placeholder="选择下单时间"
+          class="w-full"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="上门开始时间" prop="visitStartTime">
+            <el-date-picker
+              v-model="formData.visitStartTime"
+              type="datetime"
+              placeholder="选择上门开始时间"
+              class="w-full"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              :disabled="isDetailMode"
+              @change="handleVisitStartTimeChange"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="上门结束时间" prop="visitEndTime">
+            <el-date-picker
+              v-model="formData.visitEndTime"
+              type="datetime"
+              placeholder="选择上门结束时间"
+              class="w-full"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              :disabled="isDetailMode"
+              @change="handleVisitEndTimeChange"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="服务时长" prop="serviceDuration">
+        <el-row :gutter="10" class="w-full">
+          <el-col :span="12">
+            <el-input-number
+              v-model="formData.serviceDuration"
+              :min="0"
+              :step="15"
+              placeholder="请输入服务时长(分钟)"
+              class="w-full"
+              :disabled="isDetailMode"
+              @change="handleDurationChange"
+            />
+          </el-col>
+          <el-col :span="12" class="flex items-center">
+            <span class="duration-display">{{ formattedDuration }}</span>
+          </el-col>
+        </el-row>
+      </el-form-item>
+
+      <el-form-item label="工单备注" prop="remark">
+        <el-input
+          v-model="formData.remark"
+          type="textarea"
+          :rows="3"
+          placeholder="请输入工单备注"
+          maxlength="200"
+          show-word-limit
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <!-- 金额信息 -->
+      <div class="section-title">金额信息</div>
+
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-form-item label="项目金额" prop="projectAmount">
+            <el-input-number
+              v-model="formData.projectAmount"
+              :min="0"
+              :precision="2"
+              placeholder="请输入项目金额"
+              class="w-full"
+              :disabled="isDetailMode"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="补贴金额" prop="subsidyAmount">
+            <el-input-number
+              v-model="formData.subsidyAmount"
+              :min="0"
+              :precision="2"
+              placeholder="补贴金额"
+              class="w-full"
+              disabled
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="自费金额" prop="selfPayAmount">
+            <el-input-number
+              v-model="formData.selfPayAmount"
+              :min="0"
+              :precision="2"
+              placeholder="自费金额"
+              class="w-full"
+              disabled
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="补贴到账日期" prop="subsidyArrivalDate">
+        <el-date-picker
+          v-model="formData.subsidyArrivalDate"
+          type="date"
+          placeholder="选择补贴到账日期"
+          class="w-full"
+          value-format="YYYY-MM-DD"
+          :disabled="isDetailMode"
+        />
+      </el-form-item>
+
+      <!-- 详情模式下显示额外信息 -->
+      <template v-if="isDetailMode && detailData">
+        <div class="section-title">其他信息</div>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="下单时间">
+              <el-input v-model="detailData.orderTime" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="是否过期">
+              <el-input :value="detailData.isExpired === '是' ? '是' : '否'" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="工单状态">
+              <el-input v-model="detailData.status" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </template>
+    </el-form>
+
+    <!-- 后端提交参数说明 -->
+    <el-divider content-position="left">后端接口参数说明</el-divider>
+    <el-descriptions :column="2" border size="small" class="field-description">
+      <el-descriptions-item label="elderId" :span="1">长者ID(number,必填)</el-descriptions-item>
+      <el-descriptions-item label="elderName" :span="1">长者姓名(string,必填)</el-descriptions-item>
+      
+      <el-descriptions-item label="gender" :span="1">性别(string,男/女)</el-descriptions-item>
+      <el-descriptions-item label="age" :span="1">年龄(number)</el-descriptions-item>
+      
+      <el-descriptions-item label="phone" :span="1">手机号码(string)</el-descriptions-item>
+      <el-descriptions-item label="address" :span="1">居住地址(string)</el-descriptions-item>
+      
+      <el-descriptions-item label="orderType" :span="1">下单方式(string,必填)</el-descriptions-item>
+      <el-descriptions-item label="servicePersonId" :span="1">服务人员ID(number,必填)</el-descriptions-item>
+      
+      <el-descriptions-item label="servicePersonName" :span="1">服务人员姓名(string,必填)</el-descriptions-item>
+      <el-descriptions-item label="orderTime" :span="1">下单时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      
+      <el-descriptions-item label="visitStartTime" :span="1">上门开始时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      <el-descriptions-item label="visitEndTime" :span="1">上门结束时间(string,YYYY-MM-DD HH:mm:ss)</el-descriptions-item>
+      
+      <el-descriptions-item label="serviceDuration" :span="1">服务时长(number,分钟)</el-descriptions-item>
+      <el-descriptions-item label="projectAmount" :span="1">项目金额(number,元)</el-descriptions-item>
+      
+      <el-descriptions-item label="subsidyAmount" :span="1">补贴金额(number,元,默认为项目金额的50%,上限800元)</el-descriptions-item>
+      <el-descriptions-item label="selfPayAmount" :span="1">自费金额(number,元,项目金额-补贴金额)</el-descriptions-item>
+      
+      <el-descriptions-item label="subsidyArrivalDate" :span="2">补贴到账日期(string,YYYY-MM-DD,非必填)</el-descriptions-item>
+      
+      <el-descriptions-item label="serviceItems" :span="2">
+        <div>服务项目列表(array,必填)</div>
+        <div style="margin-top: 5px; color: #606266; font-size: 12px;">
+          每项包含:id(项目ID)、itemName(项目名称)、amount(金额)
+        </div>
+      </el-descriptions-item>
+      
+      <el-descriptions-item label="remark" :span="2">工单备注(string)</el-descriptions-item>
+    </el-descriptions>
+
+    <template #footer>
+      <el-button @click="dialogVisible = false">{{ isDetailMode ? '关闭' : '取消' }}</el-button>
+      <el-button v-if="!isDetailMode" type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import * as ElderApi from '@/api/living-home/elderly'
+
+const emit = defineEmits(['success'])
+
+const dialogVisible = ref(false)
+const dialogMode = ref<'create' | 'edit' | 'detail'>('create')
+const submitLoading = ref(false)
+const formRef = ref()
+const servicePersonList = ref<any[]>([])
+const serviceItemList = ref<any[]>([])
+const selectedServiceItems = ref<any[]>([])
+const serviceItemSearchKeyword = ref('')
+const detailData = ref<any>({})
+
+// 长者搜索相关
+const elderSearchLoading = ref(false)
+const elderSearchOptions = ref<any[]>([])
+const selectedElder = ref<any>(null)
+
+const formData = reactive({
+  id: undefined as number | undefined,
+  elderId: undefined as number | undefined,
+  elderName: '',
+  gender: '',
+  age: undefined as number | undefined,
+  phone: '',
+  address: '',
+  orderType: '电话咨询', // 默认使用电话咨询
+  servicePersonId: undefined as number | undefined,
+  servicePersonName: '',
+  serviceItemIds: [] as number[], // 服务项目ID
+  orderTime: '', // 下单时间
+  visitStartTime: '', // 上门开始时间
+  visitEndTime: '', // 上门结束时间
+  serviceDuration: undefined as number | undefined, // 服务时长(分钟)
+  serviceItems: [] as number[], // 服务项目ID列表
+  projectAmount: 0, // 项目金额,默认为0
+  subsidyAmount: 0, // 补贴金额,默认为0
+  selfPayAmount: 0, // 自费金额,默认为0
+  subsidyArrivalDate: '', // 补贴到账日期
+  remark: ''
+})
+
+const formRules = {
+  elderId: [{ required: true, message: '请选择长者', trigger: 'change' }],
+  serviceItems: [{ required: true, message: '请选择服务项目', trigger: 'change', type: 'array' }],
+  orderType: [{ required: true, message: '请选择下单方式', trigger: 'change' }],
+  servicePersonId: [{ required: true, message: '请选择服务人员', trigger: 'change' }],
+  orderTime: [{ required: true, message: '请选择下单时间', trigger: 'change' }],
+  visitStartTime: [{ required: true, message: '请选择上门开始时间', trigger: 'change' }],
+  visitEndTime: [{ required: true, message: '请选择上门结束时间', trigger: 'change' }],
+  serviceDuration: [{ required: true, message: '请输入服务时长', trigger: 'change' }],
+  projectAmount: [{ required: true, message: '请输入项目金额', trigger: 'change' }]
+}
+
+/** 是否为详情模式(只读) */
+const isDetailMode = computed(() => dialogMode.value === 'detail')
+
+/** 弹窗标题 */
+const dialogTitle = computed(() => {
+  const titles = { create: '新增预约', edit: '编辑预约', detail: '预约详情' }
+  return titles[dialogMode.value]
+})
+
+// 远程搜索长者
+const searchElderRemote = async (query: string) => {
+  if (query) {
+    elderSearchLoading.value = true
+    try {
+      const params = {
+        pageNo: 1,
+        pageSize: 50,
+        elderName: query
+      }
+      const data = await ElderApi.getHomeElderlyList(params)
+      elderSearchOptions.value = data.list || []
+    } catch (error) {
+      console.log('搜索长者失败', error)
+    } finally {
+      elderSearchLoading.value = false
+    }
+  } else {
+    elderSearchOptions.value = []
+  }
+}
+
+// 长者选择变化
+const handleElderChange = (val: number) => {
+  if (val) {
+    const elder = elderSearchOptions.value.find(item => item.id === val)
+    if (elder) {
+      selectedElder.value = elder
+      formData.elderName = elder.elderName
+      formData.gender = elder.sex === '1' ? '男' : '女'
+      formData.age = elder.age
+      formData.phone = elder.phone
+      formData.address = elder.currentLiveAddress
+    }
+  } else {
+    selectedElder.value = null
+    formData.elderName = ''
+    formData.gender = ''
+    formData.age = undefined
+    formData.phone = ''
+    formData.address = ''
+  }
+}
+
+// 格式化服务时长显示
+const formattedDuration = computed(() => {
+  const minutes = formData.serviceDuration
+  if (!minutes || minutes <= 0) return ''
+  const hours = Math.floor(minutes / 60)
+  const mins = minutes % 60
+  if (hours > 0 && mins > 0) {
+    return `${hours}小时${mins}分钟`
+  } else if (hours > 0) {
+    return `${hours}小时`
+  } else {
+    return `${mins}分钟`
+  }
+})
+
+// 计算时间差(分钟)
+const calculateDuration = (startTime: string, endTime: string): number => {
+  if (!startTime || !endTime) return 0
+  const start = new Date(startTime).getTime()
+  const end = new Date(endTime).getTime()
+  if (isNaN(start) || isNaN(end) || end <= start) return 0
+  return Math.round((end - start) / (1000 * 60))
+}
+
+// 计算结束时间
+const calculateEndTime = (startTime: string, durationMinutes: number): string => {
+  if (!startTime || !durationMinutes || durationMinutes <= 0) return ''
+  const start = new Date(startTime)
+  if (isNaN(start.getTime())) return ''
+  const end = new Date(start.getTime() + durationMinutes * 60 * 1000)
+  const year = end.getFullYear()
+  const month = String(end.getMonth() + 1).padStart(2, '0')
+  const day = String(end.getDate()).padStart(2, '0')
+  const hours = String(end.getHours()).padStart(2, '0')
+  const minutes = String(end.getMinutes()).padStart(2, '0')
+  const seconds = String(end.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+// 上门开始时间变化
+const handleVisitStartTimeChange = () => {
+  if (formData.visitStartTime && formData.serviceDuration) {
+    formData.visitEndTime = calculateEndTime(formData.visitStartTime, formData.serviceDuration)
+  }
+}
+
+// 上门结束时间变化
+const handleVisitEndTimeChange = () => {
+  if (formData.visitStartTime && formData.visitEndTime) {
+    const duration = calculateDuration(formData.visitStartTime, formData.visitEndTime)
+    if (duration > 0) {
+      formData.serviceDuration = duration
+    }
+  }
+}
+
+// 服务时长变化
+const handleDurationChange = () => {
+  if (formData.visitStartTime && formData.serviceDuration) {
+    formData.visitEndTime = calculateEndTime(formData.visitStartTime, formData.serviceDuration)
+  }
+}
+
+// 补贴金额上限
+const SUBSIDY_MAX_AMOUNT = 800
+
+// 监听项目金额变化,自动计算补贴金额和自费金额
+watch(() => formData.projectAmount, (newVal) => {
+  const projectAmount = newVal || 0
+  // 补贴金额默认为项目金额的50%,但不超过上限
+  let subsidyAmount = projectAmount * 0.5
+  if (subsidyAmount > SUBSIDY_MAX_AMOUNT) {
+    subsidyAmount = SUBSIDY_MAX_AMOUNT
+  }
+  formData.subsidyAmount = Math.round(subsidyAmount * 100) / 100
+  // 自费金额 = 项目金额 - 补贴金额
+  formData.selfPayAmount = Math.round((projectAmount - formData.subsidyAmount) * 100) / 100
+}, { immediate: true })
+
+// 获取服务人员列表
+const getServicePersonList = async () => {
+  try {
+    const params = { pageNo: 1, pageSize: 1000 }
+    const data = await ElderApi.getServicePersonList(params)
+    servicePersonList.value = data.list || []
+  } catch (error) {
+    console.log('获取服务人员列表失败', error)
+  }
+}
+
+// 获取服务项目列表
+const getServiceItemList = async () => {
+  try {
+    const params = { pageNo: 1, pageSize: 1000 }
+    const data = await ElderApi.getServiceItemList(params)
+    serviceItemList.value = data.list || []
+  } catch (error) {
+    console.log('获取服务项目列表失败', error)
+  }
+}
+
+// 过滤后的服务项目列表(搜索用)
+const filteredServiceItemList = computed(() => {
+  if (!serviceItemSearchKeyword.value) {
+    return serviceItemList.value
+  }
+  const keyword = serviceItemSearchKeyword.value.toLowerCase()
+  return serviceItemList.value.filter(item => 
+    item.itemName?.toLowerCase().includes(keyword)
+  )
+})
+
+// 展示的服务项目列表(最多10条)
+const displayedServiceItemList = computed(() => {
+  return filteredServiceItemList.value.slice(0, 10)
+})
+
+// 已选择项目名称(用于显示)
+const selectedServiceItemNames = computed(() => {
+  return selectedServiceItems.value.map(item => item.itemName).join('、')
+})
+
+// 服务项目搜索
+const handleServiceItemSearch = () => {
+  // 搜索时只过滤列表,不影响已选择的项目
+}
+
+// 服务项目总金额计算
+const totalServiceAmount = computed(() => {
+  return selectedServiceItems.value.reduce((sum, item) => sum + (item.amount || 0), 0)
+})
+
+// 切换服务项目选择
+const toggleServiceItem = (item: any) => {
+  const index = selectedServiceItems.value.findIndex(s => s.id === item.id)
+  if (index > -1) {
+    selectedServiceItems.value.splice(index, 1)
+  } else {
+    selectedServiceItems.value.push(item)
+  }
+  // 更新表单数据
+  formData.serviceItems = selectedServiceItems.value.map(s => s.id)
+  formData.serviceItemIds = formData.serviceItems
+  // 自动更新项目金额
+  formData.projectAmount = totalServiceAmount.value
+  // 更新补贴金额(等于项目金额)
+  formData.subsidyAmount = formData.projectAmount
+  formData.selfPayAmount = 0
+}
+
+// 打开弹窗
+const open = async (row?: any, mode: 'create' | 'edit' | 'detail' = 'create') => {
+  dialogMode.value = mode
+  dialogVisible.value = true
+  resetForm()
+  
+  // 加载服务人员列表和服务项目列表
+  await getServicePersonList()
+  await getServiceItemList()
+
+  if (row && (mode === 'edit' || mode === 'detail')) {
+    // 回填数据
+    await fillFormData(row)
+    detailData.value = row
+  }
+}
+
+// 回填表单数据
+const fillFormData = async (row: any) => {
+  formData.id = row.id
+  formData.elderId = row.elderId
+  formData.elderName = row.elderName
+  formData.gender = row.gender
+  formData.age = row.age
+  formData.phone = row.phone
+  formData.address = row.address
+  formData.orderType = row.orderType
+  formData.servicePersonId = row.servicePersonId
+  formData.servicePersonName = row.servicePersonName
+  formData.orderTime = row.orderTime
+  formData.visitStartTime = row.visitStartTime || row.visitTime
+  formData.visitEndTime = row.visitEndTime
+  formData.serviceDuration = row.serviceDuration
+  formData.projectAmount = row.projectAmount || 0
+  formData.subsidyAmount = row.subsidyAmount || row.projectAmount || 0
+  formData.selfPayAmount = row.selfPayAmount || 0
+  formData.subsidyArrivalDate = row.subsidyArrivalDate || ''
+  formData.remark = row.remark || ''
+
+  // 回填服务项目
+  if (row.serviceItems && row.serviceItems.length > 0) {
+    const itemIds = row.serviceItems.map((item: any) => typeof item === 'object' ? item.id : item)
+    formData.serviceItems = itemIds
+    formData.serviceItemIds = itemIds
+    // 从列表中找到对应的服务项目
+    selectedServiceItems.value = serviceItemList.value.filter(item => itemIds.includes(item.id))
+  } else {
+    formData.serviceItems = []
+    formData.serviceItemIds = []
+    selectedServiceItems.value = []
+  }
+
+  // 设置选中的长者信息
+  selectedElder.value = {
+    id: row.elderId,
+    elderName: row.elderName,
+    sex: row.gender === '男' ? '1' : '2',
+    age: row.age,
+    phone: row.phone,
+    currentLiveAddress: row.address
+  }
+  elderSearchOptions.value = [selectedElder.value]
+}
+
+// 获取当前时间格式化
+const getCurrentDateTime = (): string => {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = String(now.getMonth() + 1).padStart(2, '0')
+  const day = String(now.getDate()).padStart(2, '0')
+  const hours = String(now.getHours()).padStart(2, '0')
+  const minutes = String(now.getMinutes()).padStart(2, '0')
+  const seconds = String(now.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.id = undefined
+  formData.elderId = undefined
+  formData.elderName = ''
+  formData.gender = ''
+  formData.age = undefined
+  formData.phone = ''
+  formData.address = ''
+  formData.orderType = '电话咨询'
+  formData.servicePersonId = undefined
+  formData.servicePersonName = ''
+  formData.orderTime = getCurrentDateTime() // 默认当前时间
+  formData.visitStartTime = ''
+  formData.visitEndTime = ''
+  formData.serviceDuration = undefined
+  formData.serviceItems = []
+  formData.serviceItemIds = []
+  selectedServiceItems.value = []
+  serviceItemSearchKeyword.value = ''
+  formData.projectAmount = 0
+  formData.subsidyAmount = 0
+  formData.selfPayAmount = 0
+  formData.remark = ''
+  
+  selectedElder.value = null
+  elderSearchOptions.value = []
+  
+  nextTick(() => {
+    formRef.value?.resetFields()
+  })
+}
+
+// 提交表单
+const handleSubmit = async () => {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  submitLoading.value = true
+  try {
+    // 获取选中的服务人员名称
+    const servicePerson = servicePersonList.value.find(item => item.id === formData.servicePersonId)
+    const servicePersonName = servicePerson?.name || ''
+
+    const data = {
+      ...formData,
+      servicePersonName,
+      serviceItems: selectedServiceItems.value.map(item => ({
+        id: item.id,
+        itemName: item.itemName,
+        amount: item.amount
+      }))
+    }
+
+    if (dialogMode.value === 'edit' && formData.id) {
+      await ElderApi.updateAppointment(data)
+      ElMessage.success('编辑成功')
+    } else {
+      await ElderApi.createAppointment(data)
+      ElMessage.success('新增成功')
+    }
+    
+    dialogVisible.value = false
+    emit('success')
+  } catch (error) {
+    console.log('提交失败', error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+defineExpose({ open })
+</script>
+
+<style scoped lang="scss">
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin: 20px 0 15px 0;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+  
+  &:first-child {
+    margin-top: 0;
+  }
+}
+
+.w-full {
+  width: 100%;
+}
+
+.duration-display {
+  color: #606266;
+  font-size: 14px;
+  margin-left: 10px;
+}
+
+.mt-10px {
+  margin-top: 10px;
+}
+
+.mt-15px {
+  margin-top: 15px;
+}
+
+.mr-5px {
+  margin-right: 5px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin: 20px 0 15px 0;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+  
+  &:first-child {
+    margin-top: 0;
+  }
+}
+
+.selected-info {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 5px;
+  padding: 8px 12px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  font-size: 13px;
+  
+  .selected-label {
+    color: #606266;
+    font-weight: 500;
+  }
+  
+  .selected-names {
+    color: #303133;
+    font-weight: bold;
+  }
+  
+  .selected-count {
+    color: #409eff;
+    font-weight: bold;
+  }
+}
+
+.service-item-list {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 10px;
+  padding: 10px 0;
+  overflow-x: auto;
+  overflow-y: hidden;
+  
+  &.detail-mode {
+    .service-item-card {
+      cursor: default;
+    }
+  }
+  
+  .empty-text {
+    width: 100%;
+    text-align: center;
+    color: #909399;
+    padding: 20px 0;
+    font-size: 14px;
+  }
+}
+
+.service-item-card {
+  width: calc(20% - 8px);
+  min-width: 120px;
+  max-width: 150px;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  padding: 8px 10px;
+  cursor: pointer;
+  transition: all 0.3s;
+  background-color: #fff;
+  
+  &:hover {
+    border-color: #409eff;
+    box-shadow: 0 2px 8px 0 rgba(64, 158, 255, 0.15);
+  }
+  
+  &.selected {
+    border-color: #409eff;
+    background-color: #ecf5ff;
+  }
+  
+  .card-header {
+    display: flex;
+    justify-content: flex-end;
+    margin-bottom: 4px;
+  }
+  
+  .card-body {
+    text-align: center;
+    
+    .item-name {
+      font-size: 13px;
+      font-weight: 500;
+      color: #303133;
+      margin-bottom: 4px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    
+    .item-amount {
+      font-size: 14px;
+      color: #f56c6c;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 357 - 0
src/views/living-home/visiting-service/self-funded-registration/index.vue

@@ -0,0 +1,357 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="120px"
+    >
+      <el-form-item label="长者姓名:" prop="elderName">
+        <el-input
+          v-model="queryParams.elderName"
+          placeholder="请输入"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-180px"
+        />
+      </el-form-item>
+      <el-form-item label="服务人员姓名:" prop="servicePersonName">
+        <el-input
+          v-model="queryParams.servicePersonName"
+          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="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-option label="已接单" value="已接单" />
+          <el-option label="已签到" value="已签到" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="下单方式:" prop="orderType">
+        <el-select
+          v-model="queryParams.orderType"
+          placeholder="全部"
+          clearable
+          class="!w-180px"
+        >
+          <el-option label="全部" value="" />
+          <el-option label="报警" value="报警" />
+          <el-option label="电话咨询" value="电话咨询" />
+          <el-option label="自主下单" value="自主下单" />
+          <el-option label="上门关怀" value="上门关怀" />
+          <el-option label="工单详情下单" value="工单详情下单" />
+          <el-option label="长者小程序下单" value="长者小程序下单" />
+          <el-option label="守护中心下单" value="守护中心下单" />
+        </el-select>
+      </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>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="flex items-center justify-between mb-15px">
+
+      <div class="flex gap-10px">
+        <el-button type="primary" @click="handleOrderByElder">
+          <Icon icon="ep:user" class="mr-5px" /> 新增自费登记
+        </el-button>
+        <el-button type="success" @click="handleOrderUpload">
+          <Icon icon="ep:upload" class="mr-5px" /> 导入
+        </el-button>
+        <el-button type="warning" @click="handleOrderDownload">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+    >
+      <el-table-column label="序号" type="index" width="80" align="center" />
+      <!-- 长者信息 -->
+      <el-table-column prop="elderName" label="长者姓名" align="center" min-width="100" />
+      <el-table-column prop="gender" label="性别" align="center" width="80" />
+      <el-table-column prop="age" label="年龄" align="center" width="80" />
+      <el-table-column prop="phone" label="手机号码" align="center" min-width="120" />
+      <el-table-column prop="address" label="居住地址" align="center" min-width="200" show-overflow-tooltip />
+      <!-- 工单信息 -->
+      <el-table-column prop="orderType" label="下单方式" align="center" min-width="120" />
+      <el-table-column prop="servicePersonName" label="服务人员" align="center" min-width="120" />
+      <el-table-column prop="orderTime" label="下单时间" align="center" min-width="160" />
+      <el-table-column prop="visitStartTime" label="上门开始时间" align="center" min-width="160" />
+      <el-table-column prop="visitEndTime" label="上门结束时间" align="center" min-width="160" />
+      <el-table-column prop="serviceDuration" label="服务时长(分钟)" align="center" width="120" />
+      <!-- 服务项目 -->
+      <el-table-column label="服务项目" align="center" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          {{ scope.row.serviceItems ? scope.row.serviceItems.map(item => item.itemName || item).join('、') : '-' }}
+        </template>
+      </el-table-column>
+      <!-- 金额信息 -->
+      <el-table-column prop="projectAmount" label="项目金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.projectAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="subsidyAmount" label="补贴金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.subsidyAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="selfPayAmount" label="自费金额" align="center" width="100">
+        <template #default="scope">
+          ¥{{ scope.row.selfPayAmount || 0 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="subsidyArrivalDate" label="补贴到账日期" align="center" width="120" />
+      <!-- 其他 -->
+      <el-table-column prop="remark" label="工单备注" align="center" min-width="150" show-overflow-tooltip />
+
+      <el-table-column label="操作" align="center" width="280" fixed="right">
+        <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="warning" @click="handleEdit(scope.row)">
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row)">
+            <Icon icon="ep:delete" class="mr-5px" /> 删除
+          </el-button>
+
+          <!--          <el-dropdown @command="(command) => handleSignInCommand(command, scope.row)" trigger="hover">-->
+          <!--            <el-button style="margin-left: 10px">-->
+          <!--              其他操作<Icon icon="ep:arrow-down" class="ml-5px" />-->
+          <!--            </el-button>-->
+          <!--            <template #dropdown>-->
+          <!--              <el-dropdown-menu>-->
+          <!--                <el-dropdown-item command="record">-->
+          <!--                  <Icon icon="ep:list" class="mr-5px" /> 签到记录-->
+          <!--                </el-dropdown-item>-->
+          <!--                <el-dropdown-item command="quick">-->
+          <!--                  <Icon icon="ep:check" class="mr-5px" /> 一键签到-->
+          <!--                </el-dropdown-item>-->
+          <!--                <el-dropdown-item>-->
+          <!--                  <el-button link type="success" @click="handleChangeServicePerson(scope.row)">-->
+          <!--                    <Icon icon="ep:edit" class="mr-5px" /> 修改服务人员-->
+          <!--                  </el-button>-->
+          <!--                </el-dropdown-item>-->
+          <!--              </el-dropdown-menu>-->
+          <!--            </template>-->
+          <!--          </el-dropdown>-->
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <Pagination
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      :total="total"
+      @pagination="getList"
+      class="mt-15px"
+    />
+  </ContentWrap>
+
+  <!-- 按长者预约弹窗(同时用于详情/编辑) -->
+  <AddForm ref="orderByElderRef" @success="getList" />
+
+</template>
+
+<script lang="ts" setup>
+import * as AppointmentApi from '@/api/living-home/elderly'
+
+import AddForm from './AddForm.vue'
+
+defineOptions({ name: 'AppointmentList' })
+
+const message = useMessage()
+const { t } = useI18n()
+
+const loading = ref(false)
+const list = ref<any[]>([])
+const total = ref(0)
+const queryFormRef = ref()
+
+// 查询参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  servicePersonName: '',
+  phone: '',
+  status: '',
+  orderType: ''
+})
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await AppointmentApi.getAppointmentPage(queryParams)
+    list.value = data.list || []
+    total.value = data.total || 0
+  } catch (error) {
+    console.log('获取预约列表失败', error)
+    // 使用模拟数据
+    list.value = [
+      {
+        id: 1,
+        elderName: '张三',
+        gender: '女',
+        age: 43,
+        phone: '156****5555',
+        address: '河源市/源城区/城东街道aaa',
+        orderType: '坐席下单',
+        servicePersonName: '张磊',
+        orderTime: '2026-04-27 15:00:04',
+        visitTime: '2026-04-27 上午',
+        isExpired: '否'
+      },
+      {
+        id: 2,
+        elderName: '张三',
+        gender: '女',
+        age: 43,
+        phone: '156****5555',
+        address: '河源市/源城区/城东街道aaa',
+        orderType: '坐席下单',
+        servicePersonName: '张磊',
+        orderTime: '2026-04-27 15:00:04',
+        visitTime: '2026-04-28 上午',
+        isExpired: '否'
+      },
+      {
+        id: 3,
+        elderName: '张三',
+        gender: '女',
+        age: 43,
+        phone: '156****5555',
+        address: '河源市/源城区/城东街道aaa',
+        orderType: '坐席下单',
+        servicePersonName: '张磊',
+        orderTime: '2026-04-27 15:00:04',
+        visitTime: '2026-04-29 上午',
+        isExpired: '否'
+      }
+    ]
+    total.value = list.value.length
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  queryParams.elderName = ''
+  queryParams.servicePersonName = ''
+  queryParams.phone = ''
+  queryParams.status = ''
+  queryParams.orderType = ''
+  handleQuery()
+}
+
+/** 判断是否可编辑 */
+const isEditable = (status: string) => {
+  return status === '待派单' || status === '待接单'
+}
+
+/** 详情(只读) */
+const handleDetail = (row: any) => {
+  orderByElderRef.value.open(row, 'detail')
+}
+
+/** 编辑 */
+const handleEdit = (row: any) => {
+  orderByElderRef.value.open(row, 'edit')
+}
+
+/** 删除 */
+const handleDelete = async (row: any) => {
+  try {
+    await message.delConfirm()
+    await AppointmentApi.deleteAppointment(row.id)
+    message.success(t('common.delSuccess'))
+    getList()
+  } catch {}
+}
+
+/** 签到下拉菜单命令处理 */
+const handleSignInCommand = (command: string, row: any) => {
+  if (command === 'record') {
+    signInRecordRef.value.open(row)
+  } else if (command === 'quick') {
+    quickSignInRef.value.open(row)
+  }
+}
+
+/** 修改服务人员 */
+const changeServicePersonRef = ref()
+const handleChangeServicePerson = (row: any) => {
+  changeServicePersonRef.value.open(row)
+}
+
+/** 签到记录 */
+const signInRecordRef = ref()
+
+/** 一键签到 */
+const quickSignInRef = ref()
+
+/** 按长者预约 */
+const orderByElderRef = ref()
+const handleOrderByElder = () => {
+  orderByElderRef.value.open()
+}
+
+/** 按服务人员预约 */
+const orderByServicePersonRef = ref()
+const handleOrderByServicePerson = () => {
+  orderByServicePersonRef.value.open()
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped lang="scss">
+</style>

+ 11 - 0
src/views/living-home/visiting-service/trusteeship/index.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+a
+</template>
+
+<style scoped lang="scss">
+
+</style>