Explorar o código

添加居家服务项目

unknown hai 1 mes
pai
achega
bd98149ad4

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

@@ -308,4 +308,168 @@ export const sortServiceType = (data: { id: number; direction: 'up' | 'down' })
   })
 }
 
+// ==================== 服务项目管理接口 ====================
+
+// 服务项目 VO
+export interface ServiceItemVO {
+  id?: number
+  serviceImage?: string           // 服务图片
+  serviceTypeId?: number          // 服务类别ID
+  serviceTypeName?: string        // 服务类别名称
+  serviceName?: string            // 服务名称
+  servicePrice?: number           // 服务价格
+  serviceDescription?: string     // 服务概述
+  serviceDetail?: string          // 服务详情(多图URL,逗号分隔)
+  isShelf?: string                // 是否上架:是/否
+  billingMethod?: string          // 计费方式:按次/按时
+  deductElderFee?: string         // 是否扣取长者费用:是/否
+  staffCommissionType?: string    // 服务人员提成方式:按单/按比例
+  staffCommissionValue?: number   // 提成数值(元/单 或 百分比)
+  volunteerDeductFee?: string     // 志愿者服务是否扣费:是/否
+  servicePoints?: number          // 服务积分
+  needVisit?: string              // 是否需要回访:是/否
+  checkMedicationTaboo?: string   // 是否需要查看长者用药禁忌:是/否
+  serviceContentStandard?: string // 服务内容及标准
+  sort?: number                   // 排序
+}
+
+// 查询服务项目列表(分页)
+export const getServiceItemPage = (params: any) => {
+  return request.get({
+    url: 'living-home/service-item/page',
+    params
+  })
+}
+
+// 查询服务项目列表(全部)
+export const getServiceItemList = (params?: any) => {
+  return request.get({
+    url: 'living-home/service-item/list',
+    params
+  })
+}
+
+// 查询服务项目详情
+export const getServiceItem = (id: number) => {
+  return request.get({
+    url: `living-home/service-item/get?id=${id}`
+  })
+}
+
+// 新增服务项目
+export const createServiceItem = (data: ServiceItemVO) => {
+  return request.post({
+    url: 'living-home/service-item/create',
+    data
+  })
+}
+
+// 修改服务项目
+export const updateServiceItem = (data: ServiceItemVO) => {
+  return request.put({
+    url: 'living-home/service-item/update',
+    data
+  })
+}
+
+// 删除服务项目
+export const deleteServiceItem = (ids: number[]) => {
+  return request.delete({
+    url: `living-home/service-item/delete?ids=${ids.join(',')}`
+  })
+}
+
+// 批量上架服务项目
+export const batchShelfServiceItem = (ids: number[]) => {
+  return request.post({
+    url: 'living-home/service-item/batch-shelf',
+    data: ids
+  })
+}
+
+// 批量下架服务项目
+export const batchUnshelfServiceItem = (ids: number[]) => {
+  return request.post({
+    url: 'living-home/service-item/batch-unshelf',
+    data: ids
+  })
+}
+
+// 服务项目排序(上移/下移)
+export const sortServiceItem = (data: { id: number; direction: 'up' | 'down' }) => {
+  return request.post({
+    url: 'living-home/service-item/sort',
+    data
+  })
+}
+
+// ==================== 服务套餐管理接口 ====================
+
+// 服务套餐 VO
+export interface ServiceComboVO {
+  id?: number
+  comboImage?: string               // 套餐图片
+  serviceTypeId?: number            // 服务类别ID
+  serviceTypeName?: string          // 服务类别名称
+  comboName?: string                // 套餐名称
+  comboDetailIds?: number[]         // 套餐明细ID列表
+  comboItems?: any[]                // 套餐明细项目列表
+  comboPrice?: number               // 套餐金额(元)
+  discountedPrice?: number          // 折后金额
+  billingMethod?: string            // 计费方式:按次/按时
+  comboDescription?: string         // 套餐概述
+  comboDetailImages?: string        // 套餐详情图片(多图URL,逗号分隔)
+  isShelf?: string                  // 是否上架:是/否
+  volunteerDeductFee?: string       // 志愿者服务是否扣费:是/否
+  servicePoints?: number            // 服务积分
+  staffCommissionType?: string      // 服务人员提成方式:按单/按比例
+  staffCommissionValue?: number     // 提成数值
+}
+
+// 查询服务套餐列表(分页)
+export const getServiceComboPage = (params: any) => {
+  return request.get({
+    url: 'living-home/service-combo/page',
+    params
+  })
+}
+
+// 查询服务套餐列表(全部)
+export const getServiceComboList = (params?: any) => {
+  return request.get({
+    url: 'living-home/service-combo/list',
+    params
+  })
+}
+
+// 查询服务套餐详情
+export const getServiceCombo = (id: number) => {
+  return request.get({
+    url: `living-home/service-combo/get?id=${id}`
+  })
+}
+
+// 新增服务套餐
+export const createServiceCombo = (data: ServiceComboVO) => {
+  return request.post({
+    url: 'living-home/service-combo/create',
+    data
+  })
+}
+
+// 修改服务套餐
+export const updateServiceCombo = (data: ServiceComboVO) => {
+  return request.put({
+    url: 'living-home/service-combo/update',
+    data
+  })
+}
+
+// 删除服务套餐
+export const deleteServiceCombo = (ids: number[]) => {
+  return request.delete({
+    url: `living-home/service-combo/delete?ids=${ids.join(',')}`
+  })
+}
+
 

+ 536 - 0
src/views/living-home/serviceItemManage/serviceCombo/addComboForm.vue

@@ -0,0 +1,536 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="70vw">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      :disabled="formType === 'detail'"
+      label-width="130px"
+    >
+      <!-- 套餐信息 -->
+      <div class="section-title">套餐信息</div>
+      
+      <el-form-item label="套餐图片:" prop="comboImage">
+        <div class="flex items-start gap-20px">
+          <UploadImg
+            v-model="formData.comboImage"
+            height="120px"
+            width="120px"
+            :disabled="formType === 'detail'"
+          />
+          <div class="text-gray-500 text-13px">
+            <div>尺寸:正方形,推荐 800px * 800px,</div>
+            <div>图片将用于平台和小程序中,作为商品封面和头部展示。</div>
+            <el-button type="primary" link>使用默认图片 >></el-button>
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="服务类别:" prop="serviceTypeId">
+        <el-select
+          v-model="formData.serviceTypeId"
+          placeholder="请选择"
+          class="!w-300px"
+        >
+          <el-option
+            v-for="item in serviceTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="套餐名称:" prop="comboName">
+        <el-input
+          v-model="formData.comboName"
+          placeholder="请输入"
+          class="!w-300px"
+        />
+      </el-form-item>
+
+      <el-form-item label="套餐明细:" prop="comboDetailIds">
+        <div class="w-full">
+          <el-button v-if="formType !== 'detail'" type="primary" link @click="openSelectItemDialog">
+            选择服务项目 >>
+          </el-button>
+          <div v-if="selectedItems.length" class="mt-10px p-10px bg-gray-50 rounded">
+            <div v-for="(item, index) in selectedItems" :key="item.id" class="flex items-center justify-between mb-5px last:mb-0">
+              <span>{{ item.serviceName }}({{ item.servicePrice }}元)</span>
+              <el-button v-if="formType !== 'detail'" type="danger" link size="small" @click="removeSelectedItem(index)">删除</el-button>
+            </div>
+          </div>
+          <div class="text-red-500 text-12px mt-5px" v-if="formType !== 'detail'">
+            提示:请选择已有的服务项目组合成套餐(套餐内的项目不能超过10条)。
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="套餐价格:" prop="comboPrice">
+        <el-input-number
+          v-model="formData.comboPrice"
+          :min="0"
+          :precision="2"
+          placeholder="请输入"
+          class="!w-300px"
+          @change="handlePriceChange"
+        />
+        <span class="ml-10px">元</span>
+        <div class="text-orange-500 text-12px mt-5px" v-if="formType !== 'detail'">
+          提示:套餐价格默认等于所选的服务项目的总费用,也可手动修改。
+        </div>
+      </el-form-item>
+
+      <el-form-item label="套餐概述:" prop="comboDescription">
+        <el-input
+          v-model="formData.comboDescription"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入服务概述"
+          maxlength="2000"
+          show-word-limit
+          class="!w-full"
+        />
+        <div class="text-red-500 text-12px mt-5px" v-if="formType !== 'detail'">
+          提示:套餐概述默认由所选的服务项目的"名称:概述"拼接而成,支持手动编辑。新增的服务项目会自动添加至末尾,删除服务项目时,其对应的概述文字将保留,需手动删除。
+        </div>
+      </el-form-item>
+
+      <el-form-item label="套餐详情:" prop="comboDetailImages">
+        <div class="flex flex-col gap-10px">
+          <UploadImgs
+            v-model="comboDetailImages"
+            :limit="9"
+            height="100px"
+            width="140px"
+            :disabled="formType === 'detail'"
+          />
+          <div class="text-gray-500 text-13px">
+            <div>尺寸:推荐 750px * 1000px(宽750px,高1000px)</div>
+            <div>最多可上传9张</div>
+          </div>
+        </div>
+      </el-form-item>
+
+      <!-- 更多信息 -->
+      <div class="section-title mt-30px">更多信息</div>
+      
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="是否上架:" prop="isShelf" label-width="166px">
+            <el-select
+              v-model="formData.isShelf"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="服务积分:" prop="servicePoints" label-width="166px">
+            <el-input-number
+              v-model="formData.servicePoints"
+              :min="0"
+              :precision="0"
+              placeholder="请输入"
+              class="!w-full"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="志愿者服务是否扣费:" prop="volunteerDeductFee" label-width="166px">
+            <el-select
+              v-model="formData.volunteerDeductFee"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="服务人员提成方式:" prop="staffCommissionType" label-width="166px">
+            <div class="flex items-center gap-10px w-full">
+              <el-select
+                v-model="formData.staffCommissionType"
+                placeholder="请选择"
+                class="flex-1"
+                @change="handleCommissionTypeChange"
+              >
+                <el-option label="按单" value="按单" />
+                <el-option label="按比例" value="按比例" />
+              </el-select>
+              <el-input-number
+                v-model="formData.staffCommissionValue"
+                :min="0"
+                :precision="formData.staffCommissionType === '按比例' ? 1 : 2"
+                placeholder="请输入"
+                class="!w-150px"
+              />
+              <span>{{ formData.staffCommissionType === '按比例' ? '%' : '元/单' }}</span>
+            </div>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    
+    <!-- 选择服务项目弹窗 -->
+    <Dialog v-model="selectItemDialogVisible" title="选择服务项目" width="80vw" append-to-body>
+      <div class="mb-15px">
+        <el-form :inline="true" class="-mb-15px">
+          <el-form-item label="服务类别:">
+            <el-select
+              v-model="itemQueryParams.serviceTypeId"
+              placeholder="请选择"
+              clearable
+              class="!w-200px"
+              @change="getAvailableItems"
+            >
+              <el-option
+                v-for="item in serviceTypeList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+      
+      <div class="mb-15px">
+        <div class="font-bold mb-10px">服务项目列表:</div>
+        <el-table
+          ref="itemTableRef"
+          :data="availableItems"
+          row-key="id"
+          @selection-change="handleItemSelectionChange"
+          max-height="400px"
+        >
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="序号" type="index" width="80" align="center" />
+          <el-table-column prop="serviceName" label="服务名称" align="center" min-width="150" />
+          <el-table-column prop="serviceTypeName" label="服务类别" align="center" min-width="120" />
+          <el-table-column prop="deductElderFee" label="是否收费" align="center" width="100" />
+          <el-table-column prop="servicePrice" label="金额(元)" align="center" width="100" />
+          <el-table-column prop="billingMethod" label="收费方式" align="center" width="100" />
+        </el-table>
+      </div>
+      
+      <div class="bg-gray-50 p-15px rounded">
+        <div class="font-bold mb-10px">已勾选项目:</div>
+        <div v-if="tempSelectedItems.length" class="flex flex-wrap gap-10px">
+          <el-tag
+            v-for="item in tempSelectedItems"
+            :key="item.id"
+            closable
+            @close="removeTempItem(item.id)"
+          >
+            {{ item.serviceName }}({{ item.servicePrice }}元)
+          </el-tag>
+        </div>
+        <div v-else class="text-gray-400">暂无已选项目</div>
+      </div>
+      
+      <template #footer>
+        <el-button type="primary" @click="confirmSelectItems">保 存</el-button>
+        <el-button @click="selectItemDialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog>
+    
+    <template #footer>
+      <el-button v-if="formType !== 'detail'" type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">{{ formType === 'detail' ? '关 闭' : '取 消' }}</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { FormRules } from 'element-plus'
+import UploadImg from '@/components/UploadFile/src/UploadImg.vue'
+import UploadImgs from '@/components/UploadFile/src/UploadImgs.vue'
+import * as ServiceComboApi from '@/api/living-home/elderly'
+
+defineOptions({ name: 'ServiceComboForm' })
+
+const { t } = useI18n()
+const message = useMessage()
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formLoading = ref(false)
+const formType = ref('')
+const comboDetailImages = ref<string[]>([])
+const serviceTypeList = ref<any[]>([])
+const selectItemDialogVisible = ref(false)
+const availableItems = ref<any[]>([])
+const tempSelectedItems = ref<any[]>([])
+const selectedItems = ref<any[]>([])
+const itemTableRef = ref()
+const itemQueryParams = reactive({
+  serviceTypeId: undefined as number | undefined
+})
+
+const formData = ref<ServiceComboApi.ServiceComboVO>({
+  id: undefined,
+  comboImage: '',
+  serviceTypeId: undefined,
+  comboName: '',
+  comboDetailIds: [],
+  comboItems: [],
+  comboPrice: undefined,
+  discountedPrice: undefined,
+  billingMethod: '按次',
+  comboDescription: '',
+  comboDetailImages: '',
+  isShelf: '是',
+  volunteerDeductFee: '否',
+  servicePoints: undefined,
+  staffCommissionType: '按单',
+  staffCommissionValue: undefined
+})
+
+const formRules = reactive<FormRules>({
+  serviceTypeId: [{ required: true, message: '请选择服务类别', trigger: 'change' }],
+  comboName: [{ required: true, message: '请输入套餐名称', trigger: 'blur' }],
+  comboDetailIds: [{ required: true, message: '请选择套餐明细', trigger: 'change', type: 'array' }],
+  comboPrice: [{ required: true, message: '请输入套餐价格', trigger: 'blur' }],
+  isShelf: [{ required: true, message: '请选择是否上架', trigger: 'change' }],
+  volunteerDeductFee: [{ required: true, message: '请选择志愿者服务是否扣费', trigger: 'change' }],
+  staffCommissionType: [{ required: true, message: '请选择服务人员提成方式', trigger: 'change' }]
+})
+
+const formRef = ref()
+
+/** 获取服务类别列表 */
+const getServiceTypeList = async () => {
+  try {
+    const data = await ServiceComboApi.getServiceTypeList()
+    serviceTypeList.value = data || []
+  } catch (error) {
+    serviceTypeList.value = [
+      { id: 1, name: '生活照料' },
+      { id: 2, name: '医疗护理' },
+      { id: 3, name: '康复保健' },
+      { id: 4, name: '精神慰藉' },
+      { id: 5, name: '文化娱乐' }
+    ]
+  }
+}
+
+/** 获取可用的服务项目 */
+const getAvailableItems = async () => {
+  try {
+    const params: any = {}
+    if (itemQueryParams.serviceTypeId) {
+      params.serviceTypeId = itemQueryParams.serviceTypeId
+    }
+    const data = await ServiceComboApi.getServiceItemList(params)
+    availableItems.value = data || []
+  } catch (error) {
+    availableItems.value = [
+      { id: 1, serviceName: '居家清洁服务', serviceTypeName: '生活照料', servicePrice: 80, deductElderFee: '是', billingMethod: '按次' },
+      { id: 2, serviceName: '助餐送餐服务', serviceTypeName: '生活照料', servicePrice: 25, deductElderFee: '是', billingMethod: '按次' },
+      { id: 3, serviceName: '陪同就医服务', serviceTypeName: '医疗护理', servicePrice: 120, deductElderFee: '是', billingMethod: '按时' },
+      { id: 4, serviceName: '健康体检服务', serviceTypeName: '医疗护理', servicePrice: 200, deductElderFee: '是', billingMethod: '按次' }
+    ]
+  }
+}
+
+/** 打开选择项目弹窗 */
+const openSelectItemDialog = () => {
+  tempSelectedItems.value = [...selectedItems.value]
+  itemQueryParams.serviceTypeId = undefined
+  getAvailableItems()
+  selectItemDialogVisible.value = true
+}
+
+/** 处理项目选择变化 */
+const handleItemSelectionChange = (selection: any[]) => {
+  const existingIds = new Set(tempSelectedItems.value.map(item => item.id))
+  selection.forEach(item => {
+    if (!existingIds.has(item.id)) {
+      if (tempSelectedItems.value.length >= 10) {
+        message.warning('套餐内的项目不能超过10条')
+        return
+      }
+      tempSelectedItems.value.push(item)
+      existingIds.add(item.id)
+    }
+  })
+}
+
+/** 移除临时选中的项目 */
+const removeTempItem = (id: number) => {
+  const index = tempSelectedItems.value.findIndex(item => item.id === id)
+  if (index !== -1) {
+    tempSelectedItems.value.splice(index, 1)
+  }
+}
+
+/** 确认选择项目 */
+const confirmSelectItems = () => {
+  selectedItems.value = [...tempSelectedItems.value]
+  formData.value.comboDetailIds = selectedItems.value.map(item => item.id)
+  formData.value.comboItems = selectedItems.value
+  
+  // 自动计算总价
+  const totalPrice = selectedItems.value.reduce((sum, item) => sum + (item.servicePrice || 0), 0)
+  formData.value.comboPrice = totalPrice
+  
+  // 自动生成概述
+  const descriptions = selectedItems.value.map(item => `${item.serviceName}:${item.serviceDescription || ''}`)
+  formData.value.comboDescription = descriptions.join('\n')
+  
+  selectItemDialogVisible.value = false
+}
+
+/** 移除已选项目 */
+const removeSelectedItem = (index: number) => {
+  selectedItems.value.splice(index, 1)
+  formData.value.comboDetailIds = selectedItems.value.map(item => item.id)
+  formData.value.comboItems = [...selectedItems.value]
+  
+  // 重新计算价格
+  const totalPrice = selectedItems.value.reduce((sum, item) => sum + (item.servicePrice || 0), 0)
+  formData.value.comboPrice = totalPrice
+}
+
+/** 价格变化处理 */
+const handlePriceChange = (value: number | undefined) => {
+  if (value && formData.value.discountedPrice === undefined) {
+    formData.value.discountedPrice = value * 0.9
+  }
+}
+
+/** 提成方式变化 */
+const handleCommissionTypeChange = (value: string) => {
+  formData.value.staffCommissionValue = undefined
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  resetForm()
+  
+  if (type === 'create') {
+    dialogTitle.value = '新增服务套餐'
+    formType.value = 'create'
+  } else if (type === 'update') {
+    dialogTitle.value = '编辑服务套餐'
+    formType.value = 'update'
+  } else if (type === 'detail') {
+    dialogTitle.value = '服务套餐详情'
+    formType.value = 'detail'
+  }
+  
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ServiceComboApi.getServiceCombo(id)
+      formData.value = data
+      if (data.comboItems) {
+        selectedItems.value = data.comboItems
+      }
+      if (data.comboDetailImages) {
+        comboDetailImages.value = data.comboDetailImages.split(',')
+      }
+    } 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 {
+    const submitData = {
+      ...formData.value,
+      comboDetailImages: comboDetailImages.value.join(',')
+    }
+    
+    if (formType.value === 'create') {
+      await ServiceComboApi.createServiceCombo(submitData)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServiceComboApi.updateServiceCombo(submitData)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    comboImage: '',
+    serviceTypeId: undefined,
+    comboName: '',
+    comboDetailIds: [],
+    comboItems: [],
+    comboPrice: undefined,
+    discountedPrice: undefined,
+    billingMethod: '按次',
+    comboDescription: '',
+    comboDetailImages: '',
+    isShelf: '是',
+    volunteerDeductFee: '否',
+    servicePoints: undefined,
+    staffCommissionType: '按单',
+    staffCommissionValue: undefined
+  }
+  comboDetailImages.value = []
+  selectedItems.value = []
+  formRef.value?.resetFields()
+}
+
+onMounted(() => {
+  getServiceTypeList()
+})
+</script>
+
+<style scoped lang="scss">
+.section-title {
+  position: relative;
+  padding-left: 15px;
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 20px;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 4px;
+    height: 18px;
+    background-color: var(--el-color-primary);
+    border-radius: 2px;
+  }
+}
+
+:deep(.el-textarea__inner) {
+  resize: none;
+}
+</style>

+ 334 - 3
src/views/living-home/serviceItemManage/serviceCombo/index.vue

@@ -1,11 +1,342 @@
-<script setup lang="ts">
+<script lang="ts" setup>
+import addComboForm from './addComboForm.vue'
+import * as ServiceComboApi from '@/api/living-home/elderly'
 
+defineOptions({ name: 'ServiceCombo' })
+
+const message = useMessage()
+const { t } = useI18n()
+
+const loading = ref(false)
+const list = ref<ServiceComboApi.ServiceComboVO[]>([])
+const selectedIds = ref<number[]>([])
+const serviceTypeList = ref<any[]>([])
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  serviceTypeId: undefined as number | undefined,
+  comboName: '',
+})
+const queryFormRef = ref()
+
+/** 测试数据 - 服务类别 */
+const mockServiceTypes = [
+  { id: 1, name: '生活照料' },
+  { id: 2, name: '医疗护理' },
+  { id: 3, name: '康复保健' },
+  { id: 4, name: '精神慰藉' },
+  { id: 5, name: '文化娱乐' }
+]
+
+/** 测试数据 - 服务套餐列表 */
+const mockCombos = [
+  {
+    id: 1,
+    comboName: '居家照护套餐',
+    serviceTypeName: '生活照料',
+    serviceTypeId: 1,
+    comboImage: '',
+    comboItems: [
+      { id: 1, serviceName: '居家清洁服务', servicePrice: 80, serviceDescription: '提供家庭日常清洁服务' },
+      { id: 2, serviceName: '助餐送餐服务', servicePrice: 25, serviceDescription: '提供营养配餐及送餐上门' }
+    ],
+    comboPrice: 105.00,
+    discountedPrice: 94.50,
+    billingMethod: '按次',
+    servicePoints: 13,
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: 20
+  },
+  {
+    id: 2,
+    comboName: '医疗护理套餐',
+    serviceTypeName: '医疗护理',
+    serviceTypeId: 2,
+    comboImage: '',
+    comboItems: [
+      { id: 3, serviceName: '陪同就医服务', servicePrice: 120, serviceDescription: '陪同长者前往医院就诊' },
+      { id: 4, serviceName: '健康体检服务', servicePrice: 200, serviceDescription: '提供全面健康检查' }
+    ],
+    comboPrice: 320.00,
+    discountedPrice: 288.00,
+    billingMethod: '按次',
+    servicePoints: 35,
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按比例',
+    staffCommissionValue: 8
+  },
+  {
+    id: 3,
+    comboName: '康复保健套餐',
+    serviceTypeName: '康复保健',
+    serviceTypeId: 3,
+    comboImage: '',
+    comboItems: [
+      { id: 5, serviceName: '康复训练指导', servicePrice: 150, serviceDescription: '专业康复师一对一指导' }
+    ],
+    comboPrice: 150.00,
+    discountedPrice: 135.00,
+    billingMethod: '按时',
+    servicePoints: 18,
+    volunteerDeductFee: '否',
+    isShelf: '否',
+    staffCommissionType: '按单',
+    staffCommissionValue: 30
+  },
+  {
+    id: 4,
+    comboName: '精神关怀套餐',
+    serviceTypeName: '精神慰藉',
+    serviceTypeId: 4,
+    comboImage: '',
+    comboItems: [
+      { id: 6, serviceName: '心理疏导服务', servicePrice: 100, serviceDescription: '专业心理咨询师服务' }
+    ],
+    comboPrice: 100.00,
+    discountedPrice: 90.00,
+    billingMethod: '按时',
+    servicePoints: 12,
+    volunteerDeductFee: '是',
+    isShelf: '是',
+    staffCommissionType: '按比例',
+    staffCommissionValue: 12
+  }
+]
+
+/** 获取服务类别列表 */
+const getServiceTypeList = async () => {
+  try {
+    const data = await ServiceComboApi.getServiceTypeList()
+    serviceTypeList.value = data || []
+  } catch (error) {
+    console.log('获取服务类别失败,使用测试数据')
+    serviceTypeList.value = mockServiceTypes
+  }
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ServiceComboApi.getServiceComboPage(queryParams)
+    list.value = data.list || data || []
+  } catch (error) {
+    console.log('获取服务套餐列表失败,使用测试数据')
+    let filteredData = [...mockCombos]
+    if (queryParams.serviceTypeId) {
+      filteredData = filteredData.filter(item => item.serviceTypeId === queryParams.serviceTypeId)
+    }
+    if (queryParams.comboName) {
+      filteredData = filteredData.filter(item => 
+        item.comboName.includes(queryParams.comboName)
+      )
+    }
+    list.value = filteredData
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServiceComboApi.ServiceComboVO[]) => {
+  selectedIds.value = selection.map(item => item.id as number)
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 详情 */
+const handleDetail = (row: ServiceComboApi.ServiceComboVO) => {
+  formRef.value.open('detail', row.id)
+}
+
+/** 批量删除 */
+const handleBatchDelete = async () => {
+  if (!selectedIds.value.length) {
+    message.warning('请选择要删除的数据')
+    return
+  }
+  try {
+    await message.delConfirm()
+    await ServiceComboApi.deleteServiceCombo(selectedIds.value)
+    message.success(t('common.delSuccess'))
+    getList()
+  } catch {}
+}
+
+/** 单个上架/下架 */
+const handleShelf = async (row: ServiceComboApi.ServiceComboVO, action: 'up' | 'down') => {
+  const actionText = action === 'up' ? '上架' : '下架'
+  try {
+    await message.confirm(`确定要${actionText}该服务套餐吗?`)
+    row.isShelf = action === 'up' ? '是' : '否'
+    message.success(`${actionText}成功`)
+  } catch {}
+}
+
+/** 上移 */
+const handleMoveUp = async (index: number) => {
+  if (index === 0) return
+  const temp = list.value[index]
+  list.value[index] = list.value[index - 1]
+  list.value[index - 1] = temp
+}
+
+/** 下移 */
+const handleMoveDown = async (index: number) => {
+  if (index === list.value.length - 1) return
+  const temp = list.value[index]
+  list.value[index] = list.value[index + 1]
+  list.value[index + 1] = temp
+}
+
+/** 初始化 */
+onMounted(() => {
+  getServiceTypeList()
+  getList()
+})
 </script>
 
 <template>
-a
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="85px"
+    >
+      <el-form-item label="服务类别:" prop="serviceTypeId">
+        <el-select
+          v-model="queryParams.serviceTypeId"
+          placeholder="全部"
+          clearable
+          class="!w-200px"
+        >
+          <el-option
+            v-for="item in serviceTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="套餐名称:" prop="comboName">
+        <el-input
+          v-model="queryParams.comboName"
+          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>
+
+  <!-- 列表 -->
+  <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="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="comboName" label="套餐名称" align="center" min-width="120" />
+      <el-table-column prop="serviceTypeName" label="服务类别" align="center" min-width="100" />
+      <el-table-column label="服务图标" align="center" width="100">
+        <template #default="scope">
+          <el-image
+            v-if="scope.row.comboImage"
+            :src="scope.row.comboImage"
+            :preview-src-list="[scope.row.comboImage]"
+            style="width: 50px; height: 50px"
+            fit="cover"
+            preview-teleported
+          />
+          <span v-else>/</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="套餐明细" align="center" min-width="150">
+        <template #default="scope">
+          <div v-if="scope.row.comboItems && scope.row.comboItems.length" class="text-left">
+            <span v-for="(item, index) in scope.row.comboItems.slice(0, 2)" :key="index" class="block">
+              {{ item.serviceName }}
+            </span>
+            <span v-if="scope.row.comboItems.length > 2" class="text-primary cursor-pointer">
+              等{{ scope.row.comboItems.length }}项
+            </span>
+          </div>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="comboPrice" label="套餐金额(元)" align="center" width="120" />
+      <el-table-column prop="discountedPrice" label="折后金额" align="center" width="100" />
+      <el-table-column prop="billingMethod" label="计费方式" align="center" width="90" />
+      <el-table-column prop="servicePoints" label="服务积分" align="center" width="90" />
+      <el-table-column prop="volunteerDeductFee" label="志愿者服务是否扣费" align="center" width="160" />
+      <el-table-column label="操作" align="center" width="240" 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="primary" @click="openForm('update', scope.row.id)">
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+          <el-button link type="success" @click="handleShelf(scope.row, 'up')" v-if="scope.row.isShelf === '否'">
+            <Icon icon="ep:top" class="mr-5px" /> 上架
+          </el-button>
+          <el-button link type="warning" @click="handleShelf(scope.row, 'down')" v-if="scope.row.isShelf === '是'">
+            <Icon icon="ep:bottom" class="mr-5px" /> 下架
+          </el-button>
+
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <addComboForm ref="formRef" @success="getList" />
 </template>
 
 <style scoped lang="scss">
-
 </style>

+ 412 - 0
src/views/living-home/serviceItemManage/serviceItem/addItemForm.vue

@@ -0,0 +1,412 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="70vw">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      :disabled="formType === 'detail'"
+      label-width="130px"
+    >
+      <!-- 服务基本信息 -->
+      <div class="section-title">服务基本信息</div>
+      
+      <el-form-item label="服务图片:" prop="serviceImage">
+        <div class="flex items-start gap-20px">
+          <UploadImg
+            v-model="formData.serviceImage"
+            height="120px"
+            width="120px"
+          />
+          <div class="text-gray-500 text-13px">
+            <div>尺寸:正方形,推荐 800px * 800px,</div>
+            <div>图片将用于平台和小程序中,作为商品封面和头部展示。</div>
+            <el-button type="primary" link>使用默认图片 >></el-button>
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="服务类别:" prop="serviceTypeId">
+        <el-select
+          v-model="formData.serviceTypeId"
+          placeholder="请选择"
+          class="!w-300px"
+        >
+          <el-option
+            v-for="item in serviceTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="服务名称:" prop="serviceName">
+        <el-input
+          v-model="formData.serviceName"
+          placeholder="请输入"
+          class="!w-300px"
+        />
+      </el-form-item>
+
+      <el-form-item label="服务价格:" prop="servicePrice">
+        <el-input-number
+          v-model="formData.servicePrice"
+          :min="0"
+          :precision="2"
+          placeholder="请输入"
+          class="!w-300px"
+        />
+        <span class="ml-10px">元</span>
+      </el-form-item>
+
+      <el-form-item label="服务概述:" prop="serviceDescription">
+        <el-input
+          v-model="formData.serviceDescription"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入服务概述"
+          maxlength="2000"
+          show-word-limit
+          class="!w-full"
+        />
+      </el-form-item>
+
+      <el-form-item label="服务详情:" prop="serviceDetail">
+        <div class="flex flex-col gap-10px">
+          <UploadImgs
+            v-model="serviceDetailImages"
+            :limit="9"
+            height="100px"
+            width="140px"
+          />
+          <div class="text-gray-500 text-13px">
+            <div>尺寸:推荐 750px * 1000px(宽750px,高1000px)</div>
+            <div>最多可上传9张</div>
+          </div>
+        </div>
+      </el-form-item>
+
+      <!-- 更多信息 -->
+      <div class="section-title mt-30px">更多信息</div>
+      
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="是否上架:" prop="isShelf" label-width="206px">
+            <el-select
+              v-model="formData.isShelf"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="计费方式:" prop="billingMethod" label-width="206px">
+            <el-select
+              v-model="formData.billingMethod"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <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="deductElderFee" label-width="206px">
+            <el-select
+              v-model="formData.deductElderFee"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="服务人员提成方式:" prop="staffCommissionType" label-width="206px">
+            <div class="flex items-center gap-10px w-full">
+              <el-select
+                v-model="formData.staffCommissionType"
+                placeholder="请选择"
+                class="flex-1"
+                @change="handleCommissionTypeChange"
+              >
+                <el-option label="按单" value="按单" />
+                <el-option label="按比例" value="按比例" />
+              </el-select>
+              <el-input-number
+                v-model="formData.staffCommissionValue"
+                :min="0"
+                :precision="formData.staffCommissionType === '按比例' ? 1 : 2"
+                placeholder="请输入"
+                class="!w-150px"
+              />
+              <span>{{ formData.staffCommissionType === '按比例' ? '%' : '元/单' }}</span>
+            </div>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="志愿者服务是否扣费:" prop="volunteerDeductFee" label-width="206px">
+            <el-select
+              v-model="formData.volunteerDeductFee"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="服务积分:" prop="servicePoints" label-width="206px">
+            <el-input-number
+              v-model="formData.servicePoints"
+              :min="0"
+              :precision="0"
+              placeholder="请输入"
+              class="!w-full"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="是否需要回访:" prop="needVisit" label-width="206px">
+            <el-select
+              v-model="formData.needVisit"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        
+        <el-col :span="12">
+          <el-form-item label="是否需要查看长者用药禁忌:" prop="checkMedicationTaboo" label-width="206px">
+            <el-select
+              v-model="formData.checkMedicationTaboo"
+              placeholder="请选择"
+              class="!w-full"
+            >
+              <el-option label="是" value="是" />
+              <el-option label="否" value="否" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="服务内容及标准:" prop="serviceContentStandard">
+        <el-input
+          v-model="formData.serviceContentStandard"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入服务内容及标准"
+          maxlength="300"
+          show-word-limit
+          class="!w-full"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button v-if="formType !== 'detail'" type="primary" @click="submitForm">保 存</el-button>
+      <el-button @click="dialogVisible = false">{{ formType === 'detail' ? '关 闭' : '取 消' }}</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { FormRules } from 'element-plus'
+import UploadImg from '@/components/UploadFile/src/UploadImg.vue'
+import UploadImgs from '@/components/UploadFile/src/UploadImgs.vue'
+import * as ServiceItemApi from '@/api/living-home/elderly'
+
+defineOptions({ name: 'ServiceItemForm' })
+
+const { t } = useI18n()
+const message = useMessage()
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formLoading = ref(false)
+const formType = ref('')
+const serviceDetailImages = ref<string[]>([])
+const serviceTypeList = ref<any[]>([])
+
+const formData = ref<ServiceItemApi.ServiceItemVO>({
+  id: undefined,
+  serviceImage: '',
+  serviceTypeId: undefined,
+  serviceName: '',
+  servicePrice: undefined,
+  serviceDescription: '',
+  serviceDetail: '',
+  isShelf: '是',
+  billingMethod: '按次',
+  deductElderFee: '是',
+  staffCommissionType: '按单',
+  staffCommissionValue: undefined,
+  volunteerDeductFee: '否',
+  servicePoints: undefined,
+  needVisit: '是',
+  checkMedicationTaboo: '否',
+  serviceContentStandard: ''
+})
+
+const formRules = reactive<FormRules>({
+  serviceTypeId: [{ required: true, message: '请选择服务类别', trigger: 'change' }],
+  serviceName: [{ required: true, message: '请输入服务名称', trigger: 'blur' }],
+  servicePrice: [{ required: true, message: '请输入服务价格', trigger: 'blur' }],
+  isShelf: [{ required: true, message: '请选择是否上架', trigger: 'change' }],
+  billingMethod: [{ required: true, message: '请选择计费方式', trigger: 'change' }],
+  deductElderFee: [{ required: true, message: '请选择是否扣取长者费用', trigger: 'change' }],
+  staffCommissionType: [{ required: true, message: '请选择服务人员提成方式', trigger: 'change' }],
+  volunteerDeductFee: [{ required: true, message: '请选择志愿者服务是否扣费', trigger: 'change' }],
+  needVisit: [{ required: true, message: '请选择是否需要回访', trigger: 'change' }],
+  checkMedicationTaboo: [{ required: true, message: '请选择是否需要查看长者用药禁忌', trigger: 'change' }]
+})
+
+const formRef = ref()
+
+/** 获取服务类别列表 */
+const getServiceTypeList = async () => {
+  const data = await ServiceItemApi.getServiceTypeList()
+  serviceTypeList.value = data || []
+}
+
+/** 提成方式变化 */
+const handleCommissionTypeChange = (value: string) => {
+  formData.value.staffCommissionValue = undefined
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  resetForm()
+  
+  if (type === 'create') {
+    dialogTitle.value = '新增服务'
+    formType.value = 'create'
+  } else if (type === 'update') {
+    dialogTitle.value = '编辑服务'
+    formType.value = 'update'
+  } else if (type === 'detail') {
+    dialogTitle.value = '服务详情'
+    formType.value = 'detail'
+  }
+  
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ServiceItemApi.getServiceItem(id)
+      formData.value = data
+      if (data.serviceDetail) {
+        serviceDetailImages.value = data.serviceDetail.split(',')
+      }
+    } 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 {
+    const submitData = {
+      ...formData.value,
+      serviceDetail: serviceDetailImages.value.join(',')
+    }
+    
+    if (formType.value === 'create') {
+      await ServiceItemApi.createServiceItem(submitData)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ServiceItemApi.updateServiceItem(submitData)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    serviceImage: '',
+    serviceTypeId: undefined,
+    serviceName: '',
+    servicePrice: undefined,
+    serviceDescription: '',
+    serviceDetail: '',
+    isShelf: '是',
+    billingMethod: '按次',
+    deductElderFee: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: undefined,
+    volunteerDeductFee: '否',
+    servicePoints: undefined,
+    needVisit: '是',
+    checkMedicationTaboo: '否',
+    serviceContentStandard: ''
+  }
+  serviceDetailImages.value = []
+  formRef.value?.resetFields()
+}
+
+onMounted(() => {
+  getServiceTypeList()
+})
+</script>
+
+<style scoped lang="scss">
+.section-title {
+  position: relative;
+  padding-left: 15px;
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 20px;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 4px;
+    height: 18px;
+    background-color: var(--el-color-primary);
+    border-radius: 2px;
+  }
+}
+
+:deep(.el-textarea__inner) {
+  resize: none;
+}
+</style>

+ 452 - 5
src/views/living-home/serviceItemManage/serviceItem/index.vue

@@ -1,11 +1,458 @@
-<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="serviceTypeId">
+        <el-select
+          v-model="queryParams.serviceTypeId"
+          placeholder="全部"
+          clearable
+          class="!w-200px"
+        >
+          <el-option
+            v-for="item in serviceTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="服务名称:" prop="serviceName">
+        <el-input
+          v-model="queryParams.serviceName"
+          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 class="flex gap-10px">
+        <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>
+        <el-button type="success" plain @click="handleBatchShelf" :disabled="!selectedIds.length">
+          <Icon icon="ep:top" class="mr-5px" /> 批量上架
+        </el-button>
+        <el-button type="warning" plain @click="handleBatchUnshelf" :disabled="!selectedIds.length">
+          <Icon icon="ep:bottom" class="mr-5px" /> 批量下架
+        </el-button>
+        <el-button type="info" plain @click="handleImport">
+          <Icon icon="ep:upload2" class="mr-5px" /> 导入服务项目
+        </el-button>
+        <el-button type="info" plain @click="handleDownloadTemplate">
+          <Icon icon="ep:download" 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="serviceName" label="服务名称" align="center" min-width="120" />
+      <el-table-column prop="serviceTypeName" label="服务类别" align="center" min-width="100" />
+      <el-table-column label="服务图标" align="center" width="100">
+        <template #default="scope">
+          <el-image
+            v-if="scope.row.serviceImage"
+            :src="scope.row.serviceImage"
+            :preview-src-list="[scope.row.serviceImage]"
+            style="width: 50px; height: 50px"
+            fit="cover"
+            preview-teleported
+          />
+          <span v-else>/</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="servicePrice" label="金额(元)" align="center" width="100" />
+      <el-table-column prop="billingMethod" label="计费方式" align="center" width="100">
+        <template #default="scope">
+          {{ scope.row.billingMethod }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="servicePoints" label="服务积分" align="center" width="90" />
+      <el-table-column prop="deductElderFee" label="是否扣取长者费用" align="center" width="150">
+        <template #default="scope">
+          {{ scope.row.deductElderFee }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="volunteerDeductFee" label="志愿者服务是否扣费" align="center" width="160">
+        <template #default="scope">
+          {{ scope.row.volunteerDeductFee }}
+        </template>
+      </el-table-column>
+      <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="primary" @click="openForm('update', scope.row.id)">
+            <Icon icon="ep:edit" class="mr-5px" /> 编辑
+          </el-button>
+          <el-button link type="success" @click="handleShelf(scope.row, 'up')" v-if="scope.row.isShelf === '否'">
+            <Icon icon="ep:top" class="mr-5px" /> 上架
+          </el-button>
+          <el-button link type="warning" @click="handleShelf(scope.row, 'down')" v-if="scope.row.isShelf === '是'">
+            <Icon icon="ep:bottom" class="mr-5px" /> 下架
+          </el-button>
 
-<template>
-a
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <addItemForm ref="formRef" @success="getList" />
 </template>
 
-<style scoped lang="scss">
+<script lang="ts" setup>
+import addItemForm from './addItemForm.vue'
+import * as ServiceItemApi from '@/api/living-home/elderly'
+
+defineOptions({ name: 'ServiceItem' })
+
+const message = useMessage()
+const { t } = useI18n()
+
+const loading = ref(false)
+const list = ref<ServiceItemApi.ServiceItemVO[]>([])
+const selectedIds = ref<number[]>([])
+const serviceTypeList = ref<any[]>([])
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  serviceTypeId: undefined as number | undefined,
+  serviceName: '',
+})
+const queryFormRef = ref()
+
+/** 测试数据 - 服务类别 */
+const mockServiceTypes = [
+  { id: 1, name: '生活照料' },
+  { id: 2, name: '医疗护理' },
+  { id: 3, name: '康复保健' },
+  { id: 4, name: '精神慰藉' },
+  { id: 5, name: '文化娱乐' }
+]
+
+/** 测试数据 - 服务项目列表 */
+const mockServiceItems = [
+  {
+    id: 1,
+    serviceName: '居家清洁服务',
+    serviceTypeName: '生活照料',
+    serviceTypeId: 1,
+    serviceImage: '',
+    servicePrice: 80.00,
+    billingMethod: '按次',
+    servicePoints: 8,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: 15,
+    needVisit: '是',
+    checkMedicationTaboo: '否'
+  },
+  {
+    id: 2,
+    serviceName: '助餐送餐服务',
+    serviceTypeName: '生活照料',
+    serviceTypeId: 1,
+    serviceImage: '',
+    servicePrice: 25.00,
+    billingMethod: '按次',
+    servicePoints: 5,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按比例',
+    staffCommissionValue: 10,
+    needVisit: '否',
+    checkMedicationTaboo: '否'
+  },
+  {
+    id: 3,
+    serviceName: '陪同就医服务',
+    serviceTypeName: '医疗护理',
+    serviceTypeId: 2,
+    serviceImage: '',
+    servicePrice: 120.00,
+    billingMethod: '按时',
+    servicePoints: 15,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: 30,
+    needVisit: '是',
+    checkMedicationTaboo: '是'
+  },
+  {
+    id: 4,
+    serviceName: '健康体检服务',
+    serviceTypeName: '医疗护理',
+    serviceTypeId: 2,
+    serviceImage: '',
+    servicePrice: 200.00,
+    billingMethod: '按次',
+    servicePoints: 20,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '否',
+    staffCommissionType: '按比例',
+    staffCommissionValue: 8,
+    needVisit: '是',
+    checkMedicationTaboo: '是'
+  },
+  {
+    id: 5,
+    serviceName: '康复训练指导',
+    serviceTypeName: '康复保健',
+    serviceTypeId: 3,
+    serviceImage: '',
+    servicePrice: 150.00,
+    billingMethod: '按时',
+    servicePoints: 18,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: 40,
+    needVisit: '是',
+    checkMedicationTaboo: '是'
+  },
+  {
+    id: 6,
+    serviceName: '心理疏导服务',
+    serviceTypeName: '精神慰藉',
+    serviceTypeId: 4,
+    serviceImage: '',
+    servicePrice: 100.00,
+    billingMethod: '按时',
+    servicePoints: 12,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按比例',
+    staffCommissionValue: 15,
+    needVisit: '是',
+    checkMedicationTaboo: '否'
+  },
+  {
+    id: 7,
+    serviceName: '书法绘画培训',
+    serviceTypeName: '文化娱乐',
+    serviceTypeId: 5,
+    serviceImage: '',
+    servicePrice: 60.00,
+    billingMethod: '按次',
+    servicePoints: 6,
+    deductElderFee: '是',
+    volunteerDeductFee: '是',
+    isShelf: '否',
+    staffCommissionType: '按单',
+    staffCommissionValue: 10,
+    needVisit: '否',
+    checkMedicationTaboo: '否'
+  },
+  {
+    id: 8,
+    serviceName: '个人卫生护理',
+    serviceTypeName: '生活照料',
+    serviceTypeId: 1,
+    serviceImage: '',
+    servicePrice: 50.00,
+    billingMethod: '按次',
+    servicePoints: 5,
+    deductElderFee: '是',
+    volunteerDeductFee: '否',
+    isShelf: '是',
+    staffCommissionType: '按单',
+    staffCommissionValue: 12,
+    needVisit: '否',
+    checkMedicationTaboo: '否'
+  }
+]
+
+/** 获取服务类别列表 */
+const getServiceTypeList = async () => {
+  try {
+    const data = await ServiceItemApi.getServiceTypeList()
+    serviceTypeList.value = data || []
+  } catch (error) {
+    console.log('获取服务类别失败,使用测试数据')
+    serviceTypeList.value = mockServiceTypes
+  }
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ServiceItemApi.getServiceItemPage(queryParams)
+    list.value = data.list || data || []
+  } catch (error) {
+    console.log('获取服务项目列表失败,使用测试数据')
+    let filteredData = [...mockServiceItems]
+    if (queryParams.serviceTypeId) {
+      filteredData = filteredData.filter(item => item.serviceTypeId === queryParams.serviceTypeId)
+    }
+    if (queryParams.serviceName) {
+      filteredData = filteredData.filter(item => 
+        item.serviceName.includes(queryParams.serviceName)
+      )
+    }
+    list.value = filteredData
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
 
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServiceItemApi.ServiceItemVO[]) => {
+  selectedIds.value = selection.map(item => item.id as number)
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 详情 */
+const handleDetail = (row: ServiceItemApi.ServiceItemVO) => {
+  formRef.value.open('detail', row.id)
+}
+
+/** 批量删除 */
+const handleBatchDelete = async () => {
+  if (!selectedIds.value.length) {
+    message.warning('请选择要删除的数据')
+    return
+  }
+  try {
+    await message.delConfirm()
+    await ServiceItemApi.deleteServiceItem(selectedIds.value)
+    message.success(t('common.delSuccess'))
+    getList()
+  } catch {}
+}
+
+/** 批量上架 */
+const handleBatchShelf = async () => {
+  if (!selectedIds.value.length) {
+    message.warning('请选择要上架的数据')
+    return
+  }
+  try {
+    await message.confirm('确定要批量上架选中的服务项目吗?')
+    await ServiceItemApi.batchShelfServiceItem(selectedIds.value)
+    message.success('上架成功')
+    getList()
+  } catch {}
+}
+
+/** 批量下架 */
+const handleBatchUnshelf = async () => {
+  if (!selectedIds.value.length) {
+    message.warning('请选择要下架的数据')
+    return
+  }
+  try {
+    await message.confirm('确定要批量下架选中的服务项目吗?')
+    await ServiceItemApi.batchUnshelfServiceItem(selectedIds.value)
+    message.success('下架成功')
+    getList()
+  } catch {}
+}
+
+/** 单个上架/下架 */
+const handleShelf = async (row: ServiceItemApi.ServiceItemVO, action: 'up' | 'down') => {
+  const actionText = action === 'up' ? '上架' : '下架'
+  try {
+    await message.confirm(`确定要${actionText}该服务项目吗?`)
+    if (action === 'up') {
+      await ServiceItemApi.batchShelfServiceItem([row.id as number])
+    } else {
+      await ServiceItemApi.batchUnshelfServiceItem([row.id as number])
+    }
+    message.success(`${actionText}成功`)
+    getList()
+  } catch {}
+}
+
+/** 上移 */
+const handleMoveUp = async (index: number) => {
+  if (index === 0) return
+  const item = list.value[index]
+  await ServiceItemApi.sortServiceItem({ 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 ServiceItemApi.sortServiceItem({ id: item.id as number, direction: 'down' })
+  getList()
+}
+
+/** 导入服务项目 */
+const handleImport = () => {
+  message.info('导入功能开发中')
+}
+
+/** 下载导入模板 */
+const handleDownloadTemplate = () => {
+  message.info('下载模板功能开发中')
+}
+
+/** 初始化 */
+onMounted(() => {
+  getServiceTypeList()
+  getList()
+})
+</script>
+
+<style scoped lang="scss">
 </style>