Explorar el Código

Merge branch 'feature/task-219' of ssh://121.43.162.141:10022/six.fe/health.admin into feature/task-218

张田田 hace 5 meses
padre
commit
95dca281cc

+ 7 - 1
src/model/care.model.ts

@@ -356,8 +356,13 @@ export interface OpenConditioningSchemeModel {
 // 调理方案配置
 export interface ConditioningSchemeModel {
   id: string | number; // 调理方案配置ID
+  insId: string | number; // 机构id
+  insName?: string; // 机构名称
+  selectedCpIds: string[]; // 选中的项目ID
   orgId: string | number; // 组织id
   orgName: string; // 组织名称
+  institutionId: string; // 机构ID
+  institutionName: string; // 机构名称
   isConfig: string; // 是否配置
   configTimeStart: string; // 配置开始时间
   configTimeEnd: string; // 配置结束时间
@@ -371,7 +376,8 @@ export interface ConditioningSchemeModel {
     isShowForInfer: string; // 是否展示定制项目
     knowledgeCpShowType: string; // 智能推荐项目展示方式 1-展示 2-不展示 3-定制项目无结果时展示
     showCount: number; // 展示数量
-    
+      
   }[];
+  conditioningProgramType: string; // 调理方案类型
 }
 export type ConditioningSchemeQuery = Partial<ConditioningSchemeModel>;

+ 39 - 2
src/pages/index/care/configured.vue

@@ -50,6 +50,33 @@ const searchFormProps = reactive<VxeFormProps<ConditioningSchemeQuery>>({
           optionProps: { value: 'id', label: 'label' },
           clearable: true,
         },
+        events: {
+          change(val: any) {
+            insArr.value = [];
+            if (val.data.orgId) {
+              // 清空表单中的机构名称字段
+              if (model.value) {
+                // model.value.institutionId = '';
+                model.value.insId = '';
+              }
+              getInstitution(val.data.orgId);
+            }
+          },
+        },
+      },
+    },
+    {
+      field: 'insId',
+      title: '机构名称',
+      span: 6,
+      itemRender: {
+        name: 'VxeTreeSelect',
+        props: {
+          loading: computed(() => insLoading.value),
+          options: computed(() => insArr.value),
+          optionProps: { value: 'id', label: 'label' },
+          clearable: true,
+        },
       },
     },
     {
@@ -128,6 +155,7 @@ const gridOptions = reactive<VxeGridProps<ConditioningSchemeModel>>({
   },
   columns: [
     { field: 'orgName', title: '组织名称' },
+    { field: 'insName', title: '机构名称' },
     {
       field: 'isHaveForInfer',
       title: '是否定制项目',
@@ -135,7 +163,7 @@ const gridOptions = reactive<VxeGridProps<ConditioningSchemeModel>>({
         default: 'isHaveForInferSlot',
       },
     },
-    { field: 'forInferCount', title: '定制项目数' },
+    // { field: 'forInferCount', title: '定制项目数' },
 
     {
       field: 'isConfig',
@@ -198,7 +226,16 @@ async function getDeviceType() {
   }
   deviceTypesLoading.value = false;
 }
-
+const insLoading = ref(false);
+const insArr = ref<any[]>([]);
+async function getInstitution(orgId: string | number) {
+  insLoading.value = true;
+  const res = await branchMethod(1, 0, Number(orgId));
+  if (res && res.length > 0) {
+    insArr.value = res;
+  }
+  insLoading.value = false;
+}
 onMounted(() => {
   getDeviceType();
   model.value = toRaw(searchFormProps.data);

+ 17 - 4
src/request/api/care.api.ts

@@ -45,7 +45,7 @@ export function getAllSystemCpMethod() {
 }
 // 新增和编辑系统项目和新增编辑项目列表。  项目列表就是机构项目
 export function systemCpEditMethod(data: Partial<SystemItemModel>) {
-  if(data.isType === 'itemsList'){
+  if (data.isType === 'itemsList') {
     delete data.id;
   }
   if (data.addType === 'system') {
@@ -404,16 +404,29 @@ export function getConditioningSchemeMethod(page: number, size: number, query?:
   });
 }
 // 更新调理方案配置
-export function updateConditioningSchemeMethod(orgId: string | number, data: any) {
-  return request.Post(`/fdhb-pc/conditioningManage/config/updateCpConfig/${orgId}`, data, {
+export function updateConditioningSchemeMethod(insId: string | number, data: any) {
+  return request.Post(`/fdhb-pc/conditioningManage/config/updateCpConfig/${insId}`, data, {
     name: 'update-conditioning-scheme',
     cacheFor: null,
   });
 }
 // 根据调理方案id获取调理方案配置详情
 export function getConditioningDeviceDetailMethod(data: Partial<ConditioningSchemeModel>) {
-  return request.Post(`/fdhb-pc/conditioningManage/config/getCpConfigDetailById/${data.id || 0}/${data.orgId}`, {
+  // return request.Post(`/fdhb-pc/conditioningManage/config/getCpConfigDetailById/${data.id || 0}/${data.orgId}`, {
+  return request.Post(`/fdhb-pc/conditioningManage/config/getCpConfigDetailById/${data.insId}`, {
     name: 'get-conditioning-scheme-detail',
     cacheFor: null,
   });
 }
+// 根据机构id获取机构调理方案配置中某种类型下可供选择的项目
+export function getConditioningSchemeItemsMethod(data: Partial<ConditioningSchemeModel>) {
+  return request.Post(
+    `/fdhb-pc/conditioningManage/config/getOptionalCps/${data.insId}`,
+    {},
+    {
+      name: 'get-conditioning-scheme-items',
+      cacheFor: null,
+      params: { conditioningProgramType: data.conditioningProgramType ?? '' },
+    }
+  );
+}

+ 6 - 12
src/request/api/device.api.ts

@@ -1,5 +1,5 @@
 import type { List } from '@/model';
-import type { EquirementModel,DeviceManageModel,DeviceReportModel } from '@/model/device.model';
+import type { EquirementModel, DeviceManageModel, DeviceReportModel } from '@/model/device.model';
 import request from '@/request/alova';
 // 设备登记分页列表
 export function getDeviceRegisterMethod(page: number, size: number, query?: Record<string, any>) {
@@ -33,15 +33,11 @@ export function getDeviceRegisterDetailMethod(data: Partial<EquirementModel>) {
 
 // 批量修改设备登记组织
 export function updateDeviceRegisterOrganizationMethod(data: any) {
-  return request.Post(
-    `/fdhb-pc/deviceManage/device/register/batchUpdateDept`,
-    data.deviceIds,
-    {
-      name: 'update-device-register-organization',
-      cacheFor: null,
-      params: { orgId: data.orgId, institutionId: data.institutionId },
-    }
-  );
+  return request.Post(`/fdhb-pc/deviceManage/device/register/batchUpdateDept`, data.deviceIds, {
+    name: 'update-device-register-organization',
+    cacheFor: null,
+    params: { orgId: data.orgId, institutionId: data.institutionId },
+  });
 }
 
 // 一体机配置分页列表
@@ -79,5 +75,3 @@ export function deviceReportMethod(page: number, size: number, query?: Record<st
     params: { pageNum: page, pageSize: size },
   });
 }
-
-

+ 261 - 0
src/service/CustomConfiguration.vue

@@ -0,0 +1,261 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { VxeUI } from 'vxe-pc-ui';
+import { notification } from 'ant-design-vue';
+import type { ConditioningSchemeModel } from '@/model/care.model';
+import { getConditioningSchemeItemsMethod } from '@/request/api/care.api';
+type ConditioningModel = Partial<ConditioningSchemeModel>;
+const props = defineProps<{ data: ConditioningModel }>();
+
+const emit = defineEmits(['submit', 'onSubmit']);
+
+// 定义项目接口
+interface ProjectItem {
+  id: string | number;
+  name: string;
+  checked: boolean;
+}
+
+// 定义组织接口
+interface Organization {
+  id: string | number;
+  name: string;
+  projects: ProjectItem[];
+}
+// 上级项目数据
+const superiorProjects = ref<Organization[]>([]);
+
+// 本级项目数据
+const currentProjects = ref<ProjectItem[]>([]);
+
+// 关闭弹窗
+function handleClose() {
+  VxeUI.modal.close('custom-configuration-modal');
+}
+
+// 取消
+function handleCancel() {
+  handleClose();
+}
+
+// 确认
+function handleConfirm() {
+  // 收集选中的项目数据
+  const selectedSuperiorProjects: any[] = [];
+  const selectedProjects: any[] = [];
+  superiorProjects.value.forEach((org) => {
+    org.projects.forEach((project) => {
+      if (project.checked) {
+        selectedSuperiorProjects.push({
+          projectId: project.id,
+          projectName: project.name,
+        });
+      }
+    });
+  });
+  const selectedCurrentProjects = currentProjects.value
+    .filter((p) => p.checked)
+    .map((p) => ({
+      projectId: p.id,
+      projectName: p.name,
+    }));
+  selectedProjects.push(...selectedCurrentProjects, ...selectedSuperiorProjects);
+
+  (props.data as any).selectedCpIds = selectedProjects.map((item: any) => item.projectId) as string[];
+  const result = {
+    // superiorProjects: selectedSuperiorProjects,
+    currentProjects: selectedCurrentProjects,
+    originalData: props.data,
+    selectedProjects: selectedProjects,
+  };
+  // 必须选择一个项目
+  if (selectedProjects.length <= 0 && selectedProjects.length <= 0) {
+    notification.error({
+      message: '请至少选择一个项目',
+    });
+    return;
+  }
+  emit('submit', result);
+  emit('onSubmit', result);
+  handleClose();
+}
+// 获取定制项目列表
+const getCustomProjectList = async () => {
+  const res = await getConditioningSchemeItemsMethod(props.data);
+  (res as any[])?.forEach((item: any) => {
+    if (item.cps && item.cps.length > 0) {
+      item.cps.forEach((cp: any) => {
+        cp.checked = false;
+      });
+    }
+  });
+  if (props.data.selectedCpIds && props.data.selectedCpIds.length > 0) {
+    props.data.selectedCpIds.forEach((id: string) => {
+      (res as any[])?.forEach((item: any) => {
+        if (item.cps && item.cps.length > 0) {
+          item.cps.forEach((cp: any) => {
+            if (cp.id === id) {
+              cp.checked = true;
+            }
+          });
+        }
+      });
+    });
+  }
+  (res as any[])?.forEach((item: any) => {
+    if (item.insId === props.data.insId) {
+      currentProjects.value = item.cps;
+    } else {
+      superiorProjects.value.push({
+        id: item.insId,
+        name: item.insName,
+        projects: item.cps,
+      });
+    }
+  });
+};
+onMounted(() => {
+  if (props.data && props.data.conditioningProgramType) {
+    getCustomProjectList();
+  }
+});
+</script>
+
+<template>
+  <div class="custom-configuration">
+    <!-- 内容区域:左右分栏 -->
+    <div class="content-wrapper">
+      <!-- 上级项目 - 左侧 -->
+      <div class="section section-left" v-if="superiorProjects.length > 0">
+        <div class="section-title">上级项目</div>
+        <div class="organizations">
+          <div v-for="org in superiorProjects" :key="org.id" class="organization-item">
+            <div class="org-name">{{ org.name }}</div>
+            <div class="projects-list">
+              <a-checkbox v-for="project in org.projects" :key="project.id" v-model:checked="project.checked" class="project-checkbox">
+                {{ project.name }}
+              </a-checkbox>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 本级项目 - 右侧 -->
+      <div class="section section-right" v-if="currentProjects.length > 0">
+        <div class="section-title">本级项目</div>
+        <div class="projects-list">
+          <a-checkbox v-for="project in currentProjects" :key="project.id" v-model:checked="project.checked" class="project-checkbox">
+            {{ project.name }}
+          </a-checkbox>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部按钮 -->
+    <div class="footer-buttons">
+      <a-button @click="handleCancel">取消</a-button>
+      <a-button type="primary" @click="handleConfirm">确认</a-button>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.custom-configuration {
+  position: relative;
+  padding: 20px;
+  min-height: 500px;
+  display: flex;
+  flex-direction: column;
+}
+
+.close-btn {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  cursor: pointer;
+  color: #999;
+  font-size: 18px;
+  width: 24px;
+  height: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: color 0.3s;
+  z-index: 10;
+
+  &:hover {
+    color: #333;
+  }
+}
+
+.content-wrapper {
+  display: flex;
+  gap: 30px;
+  flex: 1;
+  margin-bottom: 20px;
+}
+
+.section {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.section-left {
+  padding-right: 15px;
+}
+
+.section-right {
+  padding-left: 15px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 500;
+  margin-bottom: 16px;
+  color: #333;
+}
+
+.organizations {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  flex: 1;
+}
+
+.organization-item {
+  .org-name {
+    font-size: 15px;
+    font-weight: 500;
+    margin-bottom: 12px;
+    color: black;
+  }
+}
+
+.projects-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.project-checkbox {
+  :deep(.ant-checkbox-wrapper) {
+    font-size: 14px;
+    color: #333;
+  }
+}
+
+.footer-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #f0f0f0;
+
+  .ant-btn {
+    min-width: 80px;
+    height: 32px;
+  }
+}
+</style>

+ 87 - 14
src/service/EditConfigured.vue

@@ -1,11 +1,13 @@
 <script setup lang="ts">
-import { ref, watch } from 'vue';
+import { ref, watch, nextTick, h, onMounted, reactive } from 'vue';
 import { type VxeTablePropTypes, type VxeTableEvents, VxeUI } from 'vxe-table';
 import { getDictionaryMethod } from '@/request/api/dictionary.api';
-import { getConditioningDeviceDetailMethod, updateConditioningSchemeMethod } from '@/request/api/care.api';
+import { getConditioningDeviceDetailMethod, updateConditioningSchemeMethod, getConditioningSchemeItemsMethod } from '@/request/api/care.api';
 import type { ConditioningSchemeModel } from '@/model/care.model';
 import { useRequest } from 'alova/client';
 import { notification } from 'ant-design-vue';
+import { MenuUnfoldOutlined } from '@ant-design/icons-vue';
+import CustomConfiguration from '@/service/CustomConfiguration.vue';
 type ConditioningModel = Partial<ConditioningSchemeModel>;
 
 // 定义表格行数据类型
@@ -16,7 +18,11 @@ interface TableRowData {
   knowledgeCpShowType: string;
   showCount: number;
   isChecked: boolean;
+  selectedCpIds?: string[];
+  isShowForInfer: string;
   isNewRow?: boolean; // 用于标识是否是新添加的行
+  showListIcon?: boolean; // 是否显示列表图标
+  previousChecked?: boolean; // 上一次的勾选状态,用于判断状态变化
 }
 const emits = defineEmits<{
   submit: [success: boolean, data?: Array<TableRowData>];
@@ -73,6 +79,7 @@ const addRow = () => {
     knowledgeCpShowType: '1', // 1-展示 2-不展示 3-定制项目无结果时展示
     showCount: globalItemLimit.value,
     isChecked: false,
+    isShowForInfer: 'N',
     isHaveForInfer: null,
     isCustomize: false,
     isNewRow: true, //新行
@@ -89,6 +96,7 @@ const deleteRow = (row: any) => {
 
 // 保存
 const saveData = async () => {
+  console.log(tableData.value, '保存数据');
   // 数据验证和格式化
   formattedData = tableData.value.map((row) => ({
     ...row,
@@ -110,16 +118,16 @@ const saveData = async () => {
     return;
   }
 
-  if (!props.data.orgId) {
+  if (!props.data.insId) {
     notification.error({
-      message: '未找到组织ID',
+      message: '未找到机构ID',
     });
     return;
   }
 
   // 使用 async/await 确保正确处理成功和失败
   try {
-    await submit(props.data.orgId, formattedData);
+    await submit(props.data.insId, formattedData);
     // 成功时通知父组件
     emits('submit', true, formattedData);
   } catch (error) {
@@ -146,8 +154,7 @@ const rowConfig = reactive<VxeTablePropTypes.RowConfig>({
   drag: true,
 });
 const columnConfig = reactive<VxeTablePropTypes.ColumnConfig>({});
-const rowDragstartEvent: VxeTableEvents.RowDragstart = ({ row }) => {
-};
+const rowDragstartEvent: VxeTableEvents.RowDragstart = ({ row }) => {};
 
 const rowDragendEvent: VxeTableEvents.RowDragend = ({ newRow, oldRow }) => {
   const oldIndex = tableData.value.indexOf(oldRow as TableRowData);
@@ -159,10 +166,14 @@ const rowDragendEvent: VxeTableEvents.RowDragend = ({ newRow, oldRow }) => {
   }
 };
 const organizationName = ref('');
+const insName = ref('');
+const insId = ref<number>(0);
 onMounted(async () => {
   getConditioningProgramType();
   const res: any = await getConditioningDeviceDetailMethod(props.data);
   organizationName.value = res.orgName ?? '';
+  insName.value = res.insName ?? '';
+  insId.value = res.insId ?? 0;
   tableData.value = res.items ?? [];
   tableData.value.forEach((row) => {
     // 只有 isHaveForInfer 不是 null 的情况下才是定制项目
@@ -171,13 +182,14 @@ onMounted(async () => {
       row.isCustomize = false;
     } else if (row.isHaveForInfer === 'Y') {
       // 是定制项目且展示
-      row.isChecked = true;
       row.isCustomize = true;
+      row.showListIcon = true; // 已勾选,显示列表图标
     } else if (row.isHaveForInfer === 'N') {
       // 是定制项目但不展示
-      row.isChecked = false;
       row.isCustomize = true;
+      row.showListIcon = false; // 未勾选,隐藏列表图标
     }
+    row.showListIcon = row.previousChecked = row.isChecked = row.isShowForInfer == 'Y' ? true : false;
   });
 });
 
@@ -362,12 +374,66 @@ const handleItemLimitPaste = (params: any) => {
     return false;
   }
 };
+// 处理复选框变化
+function handleCheckboxChange(row: TableRowData) {
+  const wasChecked = row.previousChecked ?? false;
+  const isChecked = row.isChecked;
+
+  // 更新 previousChecked
+  row.previousChecked = isChecked;
+
+  // 从不勾选到勾选:显示列表图标并自动打开详情编辑
+  if (!wasChecked && isChecked) {
+    row.showListIcon = true;
+    row.isChecked = true;
+    row.isShowForInfer = 'Y';
+    // 延迟一下,确保UI更新后再打开弹窗
+    nextTick(() => {
+      handleCustomProjectTrue(row);
+    });
+  }
+  // 从勾选到不勾选:隐藏列表图标
+  else if (wasChecked && !isChecked) {
+    row.showListIcon = false;
+    row.isChecked = false;
+    row.isShowForInfer = 'N';
+  }
+}
+
+const handleCustomProjectTrue = (row: any) => {
+  VxeUI.modal.open({
+    title: '定制项目',
+    width: 1100,
+    height: 750,
+    id: 'custom-configuration-modal',
+    remember: true,
+    storage: true,
+    slots: {
+      default() {
+        return h(CustomConfiguration, {
+          data: { ...row, insId: insId.value },
+          onSubmit: (data: any) => {
+            tableData.value.forEach((item: any) => {
+              if (item.conditioningProgramType === row.conditioningProgramType) {
+                item.selectedCpIds = data.selectedProjects.map((item: any) => item.projectId) as string[];
+              }
+            });
+            VxeUI.modal.close('custom-configuration-modal');
+          },
+        });
+      },
+    },
+  });
+};
 </script>
 
 <template>
   <div class="edit-configured">
     <!-- 组织名称 -->
-    <div class="organization-name">组织名称:{{ organizationName }}</div>
+    <div class="flex">
+      <div class="organization-name mr-15" v-if="organizationName">组织名称:{{ organizationName }}</div>
+      <div class="organization-name" v-if="insName">机构名称:{{ insName }}</div>
+    </div>
     <!-- 表格 -->
     <vxe-table
       border
@@ -406,9 +472,13 @@ const handleItemLimitPaste = (params: any) => {
       <vxe-column field="isHaveForInfer" title="定制项目" width="160" align="center">
         <template #default="{ row }">
           <div class="custom-project-indicator">
-            <div v-if="row.isCustomize" class="custom-project-true">
-              <a-checkbox v-model:checked="row.isChecked">展示</a-checkbox>
+            <div v-if="row.isCustomize" class="custom-project-true-container">
+              <a-checkbox v-model:checked="row.isChecked" @change="handleCheckboxChange(row)"> 展示 </a-checkbox>
+              <div v-if="row.showListIcon" class="flex items-center custom-project-true" @click="handleCustomProjectTrue(row)">
+                <MenuUnfoldOutlined />
+              </div>
             </div>
+
             <div v-else class="custom-project-false">
               <div class="red-x-icon">×</div>
             </div>
@@ -490,7 +560,7 @@ const handleItemLimitPaste = (params: any) => {
     <!-- 底部操作按钮 -->
     <div class="action-buttons">
       <vxe-button @click="cancelEdit">取消</vxe-button>
-      <vxe-button type="primary" status="warning" @click="saveData">保存</vxe-button>
+      <vxe-button type="primary" status="warning" @click="saveData" :loading="submitting">保存</vxe-button>
     </div>
   </div>
 </template>
@@ -517,7 +587,10 @@ const handleItemLimitPaste = (params: any) => {
   justify-content: center;
   gap: 8px;
 }
-
+.custom-project-true-container {
+  display: flex;
+  align-items: center;
+}
 .custom-project-true {
   display: flex;
   align-items: center;