소스 검색

满意度管理功能以及优化健康宣教以及通知管理

张田田 4 달 전
부모
커밋
e42dc042b2

+ 88 - 46
src/components/EditEducation.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, reactive, shallowRef, onBeforeUnmount, onMounted } from 'vue';
+import { ref, reactive, onMounted, onUnmounted, computed, nextTick } from 'vue';
 import { PlusOutlined } from '@ant-design/icons-vue';
 import type { FormInstance } from 'ant-design-vue';
 import type { UploadFile } from 'ant-design-vue/es/upload/interface';
@@ -14,6 +14,7 @@ import { Editor, EditorPreview } from '@/pages/editor';
 import upload from '@/request/upload';
 
 import { UploadIFile } from '@/request/api/follow.api';
+import { list2Groups } from '@/tools/data';
 
 const instance = useTemplateRef('editor');
 const config = ref<EditorProps>({
@@ -28,33 +29,31 @@ const config = ref<EditorProps>({
   },
 });
 
-const get = () => {
-  console.log('编辑器', instance.value?.editor);
-  console.log('工具栏', instance.value?.toolbar);
-};
-const save = () => {
-  const value = instance.value?.save();
-  console.group('保存');
-  console.log('html:', value?.html);
-  console.log('text:', value?.text);
-  console.log('失效资源:', value?.lapsedResources);
-  console.groupEnd();
-};
 const props = defineProps<{
   data?: EducationModel;
 }>();
 
 const emits = defineEmits<{
   submit: [data?: EducationModel];
-  // preview: [content: string];
   back: [];
 }>();
 // 获取用户标签
 const { data: selectable, loading: tagsLoading } = useRequest(searchTagsFromSelectableMethod, { initialData: [] });
+// 用户标签选项(分组)
+const tagOptions = computed(() =>
+  list2Groups(
+    selectable.value.filter((tag: any) => !tag.disabled),
+    'category',
+    (key) => ({ 1: '系统标签', 2: '个人标签' })[key]!
+  )
+);
 // 更新宣教
 const { loading: updating, send: submitUpdate } = useRequest(editEducationMethod, { immediate: false });
 const formRef = ref<FormInstance>();
 const loading = ref<boolean>(false);
+const formContentRef = ref<HTMLElement>();
+const headerActionsRef = ref<HTMLElement>();
+const offsetTop = ref<number>(0);
 
 // 表单数据
 const form = reactive<EducationModel>({
@@ -67,7 +66,7 @@ const form = reactive<EducationModel>({
   createTime: '',
   status: '0',
   pushType: '0',
-  isSwitch: false,
+  isSwitch: true,
   tagNameStr: '',
   tagNames: [],
 });
@@ -121,7 +120,6 @@ const rules: any = {
   title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
   status: [{ required: true, message: '请选择启用状态', trigger: 'change' }],
   pushType: [{ required: true, message: '请选择推送形式', trigger: 'change' }],
-  tagIds: [{ required: true, message: '请选择用户标签', trigger: 'change' }],
   briefImg: [{ required: true, message: '请上传文章主图', trigger: 'change' }],
   content: [
     {
@@ -179,7 +177,6 @@ const handleBack = () => {
   emits('back');
   VxeUI.modal.close(`education-modal`);
 };
-
 // 获取宣教详情
 const getEducationDetail = async (id?: string) => {
   const res: any = await getEducationDetailMethod(id || '');
@@ -208,31 +205,63 @@ const getEducationDetail = async (id?: string) => {
     }
   }
 };
+
+// 获取滚动容器(用于 a-affix 的 target)
+const getScrollTarget = (): HTMLElement | Window => {
+  return formContentRef.value || window;
+};
+
+// 计算 offset-top 高度(按钮区域的底部位置)
+const calculateOffsetTop = () => {
+  if (!headerActionsRef.value || !formContentRef.value) return;
+  const headerActions = (headerActionsRef.value as any)?.$el || headerActionsRef.value;
+  if (!headerActions) return;
+
+  // 获取滚动容器的位置
+  const containerRect = formContentRef.value.getBoundingClientRect();
+  // 获取按钮区域的位置
+  const headerRect = headerActions.getBoundingClientRect();
+
+  // offset-top 应该是按钮区域底部距离滚动容器顶部的距离
+  offsetTop.value = headerRect.bottom - containerRect.top;
+};
+
 onMounted(() => {
   if (props.data && props.data.id) {
     getEducationDetail(props.data.id);
   }
+
+  // 等待 DOM 渲染完成后计算 offset-top
+  nextTick(() => {
+    calculateOffsetTop();
+    // 监听窗口大小变化,重新计算 offset-top
+    window.addEventListener('resize', calculateOffsetTop);
+  });
+});
+
+onUnmounted(() => {
+  // 清理事件监听
+  window.removeEventListener('resize', calculateOffsetTop);
 });
 </script>
 
 <template>
   <div class="education-form-container">
-    <!-- 顶部按钮 -->
-    <div class="header-actions">
-      <editor-preview v-if="!config.previewable" :content="form.content">
-        <template #default="{ toggle }">
-          <a-button @click="toggle()">预览</a-button>
-        </template>
-        <template #preview-header></template>
-        <template #preview-content></template>
-        <template #preview-footer></template>
-      </editor-preview>
-      <vxe-button type="primary" content="返回" @click="handleBack"></vxe-button>
-      <vxe-button type="submit" status="primary" content="保存" @click="handleSave" :loading="updating"></vxe-button>
-    </div>
-
     <!-- 表单内容 -->
-    <div class="form-content">
+    <div class="form-content" ref="formContentRef">
+      <!-- 顶部按钮 -->
+      <div class="header-actions" ref="headerActionsRef">
+        <editor-preview v-if="!config.previewable" :content="form.content">
+          <template #default="{ toggle }">
+            <a-button @click="toggle()">预览</a-button>
+          </template>
+          <template #preview-header></template>
+          <template #preview-content></template>
+          <template #preview-footer></template>
+        </editor-preview>
+        <vxe-button type="primary" content="返回" @click="handleBack"></vxe-button>
+        <vxe-button type="submit" status="primary" content="保存" @click="handleSave" :loading="updating"></vxe-button>
+      </div>
       <a-form ref="formRef" :model="form" :rules="rules" layout="horizontal" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
         <div class="form-row">
           <!-- 标题 -->
@@ -255,12 +284,18 @@ onMounted(() => {
         </a-form-item>
 
         <!-- 用户标签 -->
-        <a-form-item label="用户标签" name="tagIds" required class="form-row-item">
-          <a-select v-model:value="form.tagIds" placeholder="请选择" style="width: 100%" mode="multiple" show-search>
-            <a-select-option v-for="option in selectable" :key="option.id" :value="option.id">
-              {{ option.name }}
-            </a-select-option>
-          </a-select>
+        <a-form-item label="用户标签" name="tagIds" class="form-row-item">
+          <vxe-select
+            v-model="form.tagIds"
+            :options="tagOptions"
+            placeholder="请选择"
+            style="width: 100%"
+            multiple
+            filterable
+            clearable
+            :optionProps="{ value: 'id', label: 'name' }"
+            :optionGroupProps="{ options: 'groups' }"
+          />
         </a-form-item>
 
         <!-- 文章主图 -->
@@ -283,14 +318,16 @@ onMounted(() => {
         </a-form-item>
 
         <!-- 宣教内容 -->
-        <a-form-item label="宣教内容" name="content" required class="form-row-item">
-          <div class="flex flex-col" style="height: 600px">
-            <editor ref="editor" class="flex-auto" v-bind="config" v-model:content="form.content">
-              <template #preview-header>test:header</template>
-              <template #preview-footer>test:footer</template>
-            </editor>
-          </div>
-        </a-form-item>
+        <a-affix :target="getScrollTarget" :offset-top="offsetTop" :z-index="10">
+          <a-form-item label="宣教内容" name="content" required class="form-row-item">
+            <div class="flex flex-col" style="height: 600px">
+              <editor ref="editor" class="flex-auto" v-bind="config" v-model:content="form.content">
+                <template #preview-header>test:header</template>
+                <template #preview-footer>test:footer</template>
+              </editor>
+            </div>
+          </a-form-item>
+        </a-affix>
       </a-form>
 
       <!-- 图片预览 -->
@@ -311,6 +348,10 @@ onMounted(() => {
 }
 
 .header-actions {
+  position: sticky;
+  top: 0;
+  z-index: 20;
+  background: #fff;
   display: flex;
   justify-content: flex-end;
   gap: 12px;
@@ -322,6 +363,7 @@ onMounted(() => {
 .form-content {
   flex: 1;
   overflow: auto;
+  position: relative;
 }
 
 .form-row {

+ 54 - 88
src/components/EditNotify.vue

@@ -1,10 +1,11 @@
 <script setup lang="ts">
-import { ref, reactive, onMounted, watch } from 'vue';
+import { ref, reactive, onMounted, watch, computed } from 'vue';
 import type { FormInstance } from 'ant-design-vue';
 import dayjs, { type Dayjs } from 'dayjs';
 import { VxeUI } from 'vxe-pc-ui';
 import { useRequest } from 'alova/client';
 import { searchTagsFromSelectableMethod } from '@/request/api/patient.api';
+import { list2Groups } from '@/tools/data';
 import { educationMethod } from '@/request/api/education.api';
 import { editNotifyMethod, getNotifyDetailMethod } from '@/request/api/notify.api';
 import { notification } from 'ant-design-vue';
@@ -21,16 +22,16 @@ const emits = defineEmits<{
 const formRef = ref<FormInstance>();
 const loading = ref<boolean>(false);
 
-// 表单数据(使用 Omit 排除 sendTime,然后添加自定义类型)
+// 表单数据
 const form = reactive<Partial<Omit<NotifyModel, 'sendTime'>> & { sendTime: Dayjs | undefined }>({
-  isSwitch: false, // 是否启用
+  isSwitch: true, // 是否启用
   name: '', // 名称
   status: '0', // 启用状态
   pushType: '0' as '0' | '1', // 通知渠道:1-企业微信
   sendTime: undefined as Dayjs | undefined, // 发送时间
   content: '', // 发送内容
   popularScienceArticleTitle: '', // 宣教文章标题
-  popularScienceArticleId: 0, //宣教id
+  popularScienceArticleId: undefined, //宣教id
   tagIds: [] as string[], // 用户标签ID
   tagNameStr: '',
   tagNames: [] as string[],
@@ -41,6 +42,14 @@ const form = reactive<Partial<Omit<NotifyModel, 'sendTime'>> & { sendTime: Dayjs
 const { loading: updating, send: submitUpdate } = useRequest(editNotifyMethod, { immediate: false });
 // 获取用户标签
 const { data: selectable, loading: tagsLoading } = useRequest(searchTagsFromSelectableMethod, { initialData: [] });
+// 用户标签选项(分组)
+const tagOptions = computed(() =>
+  list2Groups(
+    selectable.value.filter((tag: any) => !tag.disabled),
+    'category',
+    (key) => ({ 1: '系统标签', 2: '个人标签' })[key]!
+  )
+);
 
 // 获取宣教列表(不传page和size,获取全部数据)
 const educationList = ref<any[]>([]);
@@ -52,17 +61,6 @@ onEducationSuccess(({ data: { data } }) => {
   educationList.value = data || [];
 });
 
-// 自定义搜索过滤函数,让搜索匹配 title 字段
-const filterOption = (input: string, option: any) => {
-  if (!input) return true;
-  // 通过 option.value (即 id) 找到对应的原始数据
-  const item = educationList.value.find((item) => item.id === option.value);
-  if (item && item.title) {
-    return item.title.toLowerCase().includes(input.toLowerCase());
-  }
-  return false;
-};
-
 // 通知渠道选项
 const notificationChannelOptions = [
   // { label: '企业微信', value: '1' },
@@ -100,50 +98,14 @@ const rules = {
   ],
 };
 
-// 禁用早于当前时间的日期
-const disabledDate = (current: Dayjs) => {
-  // 禁用今天之前的日期
-  return current && current.isBefore(dayjs(), 'day');
-};
-
-// 禁用早于当前时间的时间
-const disabledTime = (current: Dayjs | null) => {
-  if (!current) return {};
-  const now = dayjs();
-  // 如果选择的是今天,则禁用当前时间之前的时间
-  if (current.isSame(now, 'day')) {
-    return {
-      disabledHours: () => {
-        const hours = [];
-        for (let i = 0; i < now.hour(); i++) {
-          hours.push(i);
-        }
-        return hours;
-      },
-      disabledMinutes: (selectedHour: number) => {
-        if (selectedHour === now.hour()) {
-          const minutes = [];
-          for (let i = 0; i <= now.minute(); i++) {
-            minutes.push(i);
-          }
-          return minutes;
-        }
-        return [];
-      },
-      disabledSeconds: (selectedHour: number, selectedMinute: number) => {
-        if (selectedHour === now.hour() && selectedMinute === now.minute()) {
-          const seconds = [];
-          for (let i = 0; i <= now.second(); i++) {
-            seconds.push(i);
-          }
-          return seconds;
-        }
-        return [];
-      },
-    };
-  }
-  return {};
-};
+const sendTimeDate = computed({
+  get: () => {
+    return form.sendTime ? form.sendTime.toDate() : undefined;
+  },
+  set: (value: Date | undefined) => {
+    form.sendTime = value ? dayjs(value) : undefined;
+  },
+});
 
 const getNotifyDetail = async (id?: string) => {
   const res: any = await getNotifyDetailMethod(id || '');
@@ -173,7 +135,16 @@ onMounted(() => {
 const handleSave = async () => {
   try {
     await formRef.value?.validate();
-    loading.value = true;
+    
+    // 验证发送时间不能早于当前时间
+    if (form.sendTime && form.sendTime.isBefore(dayjs())) {
+      notification.error({
+        message: '发送时间不能早于现在',
+      });
+      return;
+    }
+    
+    updating.value = true;
     const tagIdsArray = Array.isArray(form.tagIds) ? form.tagIds : form.tagIds ? [form.tagIds] : [];
     const tagNames = tagIdsArray.map((id: string) => selectable.value.find((option: any) => option.id === id)?.name || '');
     const status = form.isSwitch ? '0' : '1';
@@ -184,7 +155,6 @@ const handleSave = async () => {
       tagNameStr: tagNames.join(',') || '',
       tagNames: tagNames,
     };
-    console.log(submitData, 'submitData');
     submitUpdate({
       ...submitData,
     }).then(() => {
@@ -198,9 +168,10 @@ const handleSave = async () => {
       emits('submit', { ...form });
     });
   } catch (error) {
+    updating.value = false;
     console.error('表单验证失败', error);
   } finally {
-    loading.value = false;
+    updating.value = false;
   }
 };
 
@@ -226,7 +197,7 @@ watch(
     <!-- 顶部按钮 -->
     <div class="header-actions">
       <vxe-button type="primary" content="返回" @click="handleBack"></vxe-button>
-      <vxe-button type="submit" status="primary" content="保存" @click="handleSave" :loading="loading"></vxe-button>
+      <vxe-button type="submit" status="primary" content="保存" @click="handleSave" :loading="updating"></vxe-button>
     </div>
 
     <!-- 表单内容 -->
@@ -239,11 +210,17 @@ watch(
           </a-form-item>
 
           <a-form-item label="用户标签" name="tagIds" class="form-row-item mr-10 ml-10">
-            <a-select v-model:value="form.tagIds" placeholder="请选择" style="width: 100%" mode="multiple" show-search>
-              <a-select-option v-for="option in selectable" :key="option.id" :value="option.id">
-                {{ option.name }}
-              </a-select-option>
-            </a-select>
+            <vxe-select
+              v-model="form.tagIds"
+              :options="tagOptions"
+              placeholder="请选择"
+              style="width: 100%"
+              multiple
+              filterable
+              clearable
+              :optionProps="{ value: 'id', label: 'name' }"
+              :optionGroupProps="{ options: 'groups' }"
+            />
           </a-form-item>
 
           <a-form-item label="启用" name="status" required class="form-row-item">
@@ -264,15 +241,7 @@ watch(
 
         <!-- 发送时间 -->
         <a-form-item label="发送时间" name="sendTime" required class="form-row-item">
-          <a-date-picker
-            v-model:value="form.sendTime"
-            placeholder="请输入"
-            show-time
-            format="YYYY-MM-DD HH:mm"
-            style="width: 100%"
-            :disabled-date="disabledDate"
-            :disabled-time="disabledTime"
-          />
+          <vxe-date-picker v-model="sendTimeDate" type="datetime" placeholder="请输入" format="yyyy-MM-dd HH:mm:ss" :time-format="'HH:mm:ss'" style="width: 100%" />
         </a-form-item>
         <!--  查阅时长-->
         <a-form-item label="查阅时长" name="isExpire" required class="form-row-item">
@@ -294,19 +263,16 @@ watch(
 
         <!-- 宣教文章链接 -->
         <a-form-item label="宣教文章链接" name="articleLink" class="form-row-item">
-          <a-select
-            v-model:value="form.popularScienceArticleId"
+          <vxe-select
+            v-model="form.popularScienceArticleId"
+            :options="educationList"
             placeholder="请输入搜索"
-            allow-clear
             :loading="educationLoading"
-            show-search
-            clear-icon
-            :filter-option="filterOption"
-          >
-            <a-select-option v-for="option in educationList" :key="option.id" :value="Number(option.id)" :title="option.title">
-              {{ option.title }}
-            </a-select-option>
-          </a-select>
+            clearable
+            filterable
+            :optionProps="{ value: 'id', label: 'title' }"
+            style="width: 100%"
+          />
         </a-form-item>
       </a-form>
     </div>

+ 0 - 106
src/components/EducationPreview.vue

@@ -1,106 +0,0 @@
-<script setup lang="ts">
-const props = defineProps<{
-  content?: string;
-}>();
-</script>
-
-<template>
-  <div class="education-preview-content" v-html="props.content || '<p>暂无内容</p>'"></div>
-</template>
-
-<style scoped lang="scss">
-.education-preview-content {
-  padding-left: 10px;
-  min-height: 400px;
-  line-height: 1.8;
-  color: #333;
-
-  :deep(img) {
-    max-width: 100%;
-    height: auto;
-    display: block;
-    margin: 16px 0;
-  }
-
-  :deep(video) {
-    max-width: 100%;
-    height: auto;
-    display: block;
-    margin: 16px 0;
-  }
-
-  :deep(p) {
-    // margin: 12px 0;
-  }
-
-  :deep(h1),
-  :deep(h2),
-  :deep(h3),
-  :deep(h4),
-  :deep(h5),
-  :deep(h6) {
-    margin: 16px 0 12px 0;
-    font-weight: bold;
-  }
-
-  :deep(ul),
-  :deep(ol) {
-    margin: 12px 0;
-    padding-left: 24px;
-  }
-
-  :deep(li) {
-    margin: 8px 0;
-  }
-
-  :deep(blockquote) {
-    margin: 12px 0;
-    padding: 12px 16px;
-    border-left: 4px solid #d9d9d9;
-    background: #f5f5f5;
-  }
-
-  :deep(code) {
-    padding: 2px 4px;
-    background: #f5f5f5;
-    border-radius: 2px;
-    font-family: 'Courier New', monospace;
-  }
-
-  :deep(pre) {
-    padding: 12px;
-    background: #f5f5f5;
-    border-radius: 4px;
-    overflow-x: auto;
-    margin: 12px 0;
-  }
-
-  :deep(a) {
-    color: #1890ff;
-    text-decoration: none;
-
-    &:hover {
-      text-decoration: underline;
-    }
-  }
-
-  :deep(table) {
-    width: 100%;
-    border-collapse: collapse;
-    margin: 12px 0;
-
-    th,
-    td {
-      border: 1px solid #d9d9d9;
-      padding: 8px 12px;
-      text-align: left;
-    }
-
-    th {
-      background: #fafafa;
-      font-weight: bold;
-    }
-  }
-}
-</style>
-

+ 6 - 5
src/model/satisfaction.model.ts

@@ -27,7 +27,6 @@ export interface SatisfactionModel {
 
 export type SatisfactionQuery = Partial<SatisfactionModel>;
 
-
 //发送记录
 export interface SatisfactionSendRecordModel {
   id: string; //满意度调研问卷ID
@@ -37,7 +36,9 @@ export interface SatisfactionSendRecordModel {
   patientName: string; //姓名
   phone: string; //手机号码
   score: number; //得分
-
+  sendTimeStart: string; //发送开始时间
+  sendTimeEnd: string; //发送结束时间
+  isFilled: boolean; //是否已填写
 }
 export type SatisfactionSendRecordQuery = Partial<SatisfactionSendRecordModel>;
 
@@ -48,7 +49,7 @@ export interface SatisfactionStatisticsModel {
   name: string; //满意度调研问卷名称
   sendTimeStart: string; // 发送起始时间
   sendTimeEnd: string; // 发送结束时间
-  xaxis:string[]; //x轴数据
-  yaxis:number[]; //y轴数据
+  xaxis: string[]; //x轴数据
+  yaxis: number[]; //y轴数据
 }
-export type SatisfactionStatisticsQuery = Partial<SatisfactionStatisticsModel>;
+export type SatisfactionStatisticsQuery = Partial<SatisfactionStatisticsModel>;

+ 2 - 42
src/pages/index/healthy/education.vue

@@ -1,15 +1,11 @@
 <script setup lang="ts">
 import { h } from 'vue';
 import EditEducation from '@/components/EditEducation.vue';
-import EducationPreview from '@/components/EducationPreview.vue';
-
 import type { EducationModel, EducationQuery } from '@/model/education.model';
-
-// 接口数据
-import { deleteEducationMethod, educationMethod, updateEducationStatusMethod } from '@/request/api/education.api';
 import { usePagination } from 'alova/client';
 import { notification } from 'ant-design-vue';
-
+// 接口数据
+import { deleteEducationMethod, educationMethod, updateEducationStatusMethod } from '@/request/api/education.api';
 import { type VxeFormListeners, type VxeFormProps, type VxeGridInstance, type VxeGridListeners, type VxeGridProps, VxeUI } from 'vxe-pc-ui';
 
 const model = shallowRef<EducationQuery>();
@@ -25,20 +21,6 @@ const searchFormProps = reactive<VxeFormProps<EducationQuery>>({
       span: 4,
       itemRender: { name: 'VxeInput', props: { placeholder: '请输入' } },
     },
-
-    // {
-    //   field: 'status',
-    //   title: '针对',
-    //   span: 5,
-    //   itemRender: {
-    //     name: 'VxeRadioGroup',
-    //     options: [
-    //       { label: '理疗项目', value: '1' },
-    //       { label: '医生', value: '2' },
-    //       { label: '无针对性', value: '3' },
-    //     ],
-    //   },
-    // },
     {
       field: 'createBy',
       title: '创建者',
@@ -150,7 +132,6 @@ const gridOptions = reactive<VxeGridProps<EducationModel>>({
           mode: 'text',
         },
         options: [
-          // { content: '详情', status: 'primary', name: 'detailEducation' },
           { content: '编辑', status: 'primary', name: 'editEducation' },
           { content: '删除', status: 'primary', name: 'deleteEducation' },
         ],
@@ -185,10 +166,6 @@ onSuccess(({ data: { data } }) => {
 
 onMounted(async () => {
   model.value = toRaw(searchFormProps.data);
-  const request = await fetch(`${location.origin}/manager/fdhb-mobile/psarticle/getPsarticleDetailById?shortToken=29a71cc5-6d7c-4ae8-84d7-a9d08107cf71`);
-  const result = await request.json();
-  console.log(result, 'result===result',result.data,decodeURIComponent(result.data?.content || ''));
-  console.log(result.status, 'result.text()===result.text()');
 });
 
 function updatePlanStatus(model: EducationModel, index: number, status: EducationModel['status']) {
@@ -246,23 +223,6 @@ function editEducation(model?: EducationModel, index?: number) {
             refresh(page.value);
             VxeUI.modal.close(`education-modal`);
           },
-          // onPreview: (content: string) => {
-          //   // 打开预览弹窗
-          //   VxeUI.modal.open({
-          //     title: '预览宣教内容',
-          //     fullscreen: true,
-          //     escClosable: true,
-          //     destroyOnClose: true,
-          //     id: `education-preview-modal`,
-          //     slots: {
-          //       default: () => {
-          //         return h(EducationPreview, {
-          //           content: content,
-          //         });
-          //       },
-          //     },
-          //   });
-          // },
         });
       },
     },

+ 1 - 0
src/pages/index/online/onlineConsult.vue

@@ -861,6 +861,7 @@ function openHistoryPreviewHandle(row: any) {
       color: #999;
       font-size: 12px;
       padding: 4px 0;
+      text-align: center;
     }
   }
 }

+ 7 - 13
src/pages/index/satisfaction/survey.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
-import { ref, unref, shallowReactive, defineAsyncComponent } from 'vue';
-import { usePermission } from '@/core/usePermission';
+import { ref, shallowReactive, defineAsyncComponent } from 'vue';
 
 const panels = shallowReactive([
   {
@@ -18,14 +17,14 @@ const panels = shallowReactive([
     title: '满意度统计',
     component: defineAsyncComponent(() => import('@/satisfaction/Statistics.vue')),
   },
-].filter(item => !unref(item.hide)));
+]);
 
 const activePanel = ref(panels[0].id);
 const currentComponent = ref<any>(null);
 
 // 获取当前激活的组件
 const getCurrentComponent = () => {
-  return panels.find(panel => panel.id === activePanel.value);
+  return panels.find((panel) => panel.id === activePanel.value);
 };
 
 // 切换面板
@@ -35,9 +34,9 @@ function handleChange(panelId: string) {
   setTimeout(() => {
     if (currentComponent.value && typeof currentComponent.value.send === 'function') {
       currentComponent.value?.send();
-    } 
+    }
   }, 100);
-};
+}
 </script>
 
 <template>
@@ -50,15 +49,10 @@ function handleChange(panelId: string) {
         </a-radio-button>
       </a-radio-group>
     </div>
-    
+
     <!-- 内容区域 -->
     <div class="content-wrapper">
-      <component 
-        :is="getCurrentComponent()?.component" 
-        :title="getCurrentComponent()?.title" 
-        ref="currentComponent"
-        :key="activePanel"
-      ></component>
+      <component :is="getCurrentComponent()?.component" :title="getCurrentComponent()?.title" ref="currentComponent" :key="activePanel"></component>
     </div>
   </div>
 </template>

+ 11 - 10
src/request/api/account.api.ts

@@ -69,16 +69,17 @@ export function getMenusMethod(account: AccountModel) {
   return request.Get<AccountModel, any[]>(`/system/menu/getRouters`, {
     headers: { Authorization: account.token },
     transform(data) {
-    // data[5]?.children?.push({
-    //     path: 'configured',
-    //     meta: { title: '辨识仪配置' }
-    //   },
-    //   {
-    //     path: 'reportManagement',
-    //     meta: { title: '报告管理' }
-    //   },
-    // );
-    //   console.log(data, 'push之后的data', transformMenus(data));
+      // data.push({
+      //   path: '/',
+      //   children: [
+      //     {
+      //       path: 'satisfaction/survey',
+      //       name: 'satisfactionSurvey',
+      //       meta: { title: '满意度管理' },
+      //     },
+      //   ],
+      // });
+      //   console.log(data, 'push之后的data', transformMenus(data));
       return { ...account, menus: transformMenus(data) };
     },
   });

+ 6 - 4
src/request/api/notify.api.ts

@@ -7,19 +7,21 @@ import request from '@/request/alova';
 export function notifyMethod(page: number, size: number, query?: NotifyQuery) {
   return request.Post<List<NotifyModel>, List<any>>(`/fdhb-pc/noticeManage/pageNotice`, query ?? {}, {
     hitSource: /notify$/,
-    params: { pageNum: page, pageSize: size, ...query },
+    params: { pageNum: page, pageSize: size },
   });
 }
 // 获取通知记录分页列表
 export function getNotifyRecordMethod(page: number, size: number, query?: NotifyQuery) {
   return request.Post<List<NotifyModel>, List<any>>(`/fdhb-pc/noticeManage/selectNoticeSendRecord`, query ?? {}, {
     hitSource: /notify-record$/,
-    params: { pageNum: page, pageSize: size, ...query },
+    params: { pageNum: page, pageSize: size },
   });
 }
 // 新增/修改通知
 export function editNotifyMethod(data: Partial<NotifyModel>) {
-  return data.id ? request.Post(`/fdhb-pc/noticeManage/updateNotice`, data, { name: 'edit-notify' }) : request.Post(`/fdhb-pc/noticeManage/addNotice`, data, { name: 'add-notify' });
+  return data.id
+    ? request.Post(`/fdhb-pc/noticeManage/updateNotice`, data, { name: 'edit-notify' })
+    : request.Post(`/fdhb-pc/noticeManage/addNotice`, data, { name: 'add-notify' });
 }
 // 删除通知
 export function deleteNotifyMethod(data: Partial<NotifyModel>) {
@@ -32,4 +34,4 @@ export function updateNotifyStatusMethod(data: Partial<NotifyModel>) {
 //根据通知ID获取通知详情
 export function getNotifyDetailMethod(id: string) {
   return request.Post(`/fdhb-pc/noticeManage/detail/${id}`, { name: 'get-notify-detail' });
-}
+}

+ 13 - 8
src/request/api/satisfaction.api.ts

@@ -14,21 +14,26 @@ import request from '@/request/alova';
 export function satisfactionSendRecordMethod(page: number, size: number, query?: SatisfactionSendRecordQuery) {
   return request.Post<List<SatisfactionSendRecordModel>, List<any>>(`/fdhb-pc/satisfiesyManage/selectSatisfiesySendRecord`, query ?? {}, {
     hitSource: /satisfaction-send-record$/,
-    params: { pageNum: page, pageSize: size, ...query },
+    params: { pageNum: page, pageSize: size },
   });
 }
 // 获取满意度调研问卷列表
-export function satisfactionMethod(page: number, size: number, query?: SatisfactionQuery) {
-  return request.Post<List<SatisfactionModel>, List<any>>(`/fdhb-pc/satisfiesyManage/pageSatisfiesy`, query ?? {}, {
+export function satisfactionMethod(page?: number, size?: number, query?: SatisfactionQuery) {
+  const params: any = { ...query };
+  if (page !== undefined && size !== undefined) {
+    params.pageNum = page;
+    params.pageSize = size;
+  }
+  return request.Post<List<SatisfactionModel>, List<any>>(`/fdhb-pc/satisfiesyManage/pageSatisfiesy`, params, {
     hitSource: /satisfaction$/,
-    params: { pageNum: page, pageSize: size, ...query },
+    // params,
   });
 }
 //满意度问卷统计
 export function satisfactionStatisticsMethod(query?: SatisfactionStatisticsQuery) {
   return request.Post<SatisfactionStatisticsModel, List<any>>(`/fdhb-pc/satisfiesyManage/satisfiesyStatistics`, query ?? {}, {
     hitSource: /satisfaction-statistics$/,
-    params: { ...query },
+    params: {},
   });
 }
 
@@ -40,9 +45,9 @@ export function getSatisfactionDetailMethod(id: string) {
 // 设置触发条件
 export function setSatisfactionTriggerMethod(data: Partial<SatisfactionModel>) {
   return request.Post(
-    `/fdhb-pc/satisfiesyManage/setSatisfiesyTrigger/${data.id}/${data.status}/${data.pushType}`,
-    {},
-    { name: 'set-satisfaction-trigger', params: { triggerType: data.satisfiesyPmr?.triggerType, triggerIntervalHours: data.satisfiesyPmr?.triggerIntervalHours } }
+    `/fdhb-pc/satisfiesyManage/configTrigger/${data.id}/${data.status}/${data.pushType}`,
+    { triggerType: data.satisfiesyPmr?.triggerType, triggerIntervalHours: data.satisfiesyPmr?.triggerIntervalHours },
+    { name: 'set-satisfaction-trigger' }
   );
 }
 //新增满意度调研问卷

+ 135 - 63
src/satisfaction/EditQuestionnaire.vue

@@ -1,9 +1,13 @@
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue';
+import { ref, reactive, onMounted, computed } from 'vue';
 import type { FormInstance } from 'ant-design-vue';
+import type { Rule } from 'ant-design-vue/es/form';
 import { VxeUI } from 'vxe-pc-ui';
 import { PlusOutlined, ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons-vue';
-
+import type { SatisfactionModel } from '@/model/satisfaction.model';
+import { addSatisfactionMethod } from '@/request/api/satisfaction.api';
+import { useRequest } from 'alova/client';
+import { notification } from 'ant-design-vue';
 const props = defineProps<{
   data?: any;
 }>();
@@ -12,85 +16,117 @@ const emits = defineEmits<{
   submit: [data?: any];
   back: [];
 }>();
-
+const { loading: submitting, send: onSubmit } = useRequest(addSatisfactionMethod, { immediate: false });
 const formRef = ref<FormInstance>();
 const loading = ref<boolean>(false);
 
-// 问题接口
-interface Question {
-  id: string;
-  content: string;
-}
-
-// 问题分组接口
-interface QuestionGroup {
-  id: string;
-  name: string;
-  questions: Question[];
-}
-
 // 表单数据
-const form = reactive({
-  name: '', // 问卷名称
-  openingRemarks: '', // 开头语
-  closingRemarks: '', // 结束语
-  questionGroups: [
+const form = reactive<Partial<SatisfactionModel['satisfiesyContent']>>({
+  name: '',
+  startSentence: '',
+  endSentence: '',
+  suggestion: '',
+  groups: [
     {
-      id: '1',
       name: '',
-      questions: [
+      items: [
         {
-          id: '1',
-          content: '',
+          name: '',
+          score: '',
         },
       ],
     },
-  ] as QuestionGroup[],
+  ],
 });
 
 // 表单验证规则
-const rules = {
-  name: [{ required: true, message: '请输入问卷名称', trigger: 'blur' }],
-  openingRemarks: [{ required: true, message: '请输入开头语', trigger: 'blur' }],
-  closingRemarks: [{ required: true, message: '请输入结束语', trigger: 'blur' }],
-  questionGroups: [{ required: true, message: '请输入问题分组', trigger: 'blur' }],
-  questions: [{ required: true, message: '请输入问题', trigger: 'blur' }],
+const getRules = (): Record<string, Rule[]> => {
+  const baseRules: Record<string, Rule[]> = {
+    name: [
+      {
+        validator: (_rule: any, value: any) => {
+          if (!value || (typeof value === 'string' && !value.trim())) {
+            return Promise.reject('请输入问卷名称');
+          }
+          return Promise.resolve();
+        },
+        trigger: 'blur',
+      },
+    ],
+    startSentence: [
+      {
+        validator: (_rule: any, value: any) => {
+          if (!value || (typeof value === 'string' && !value.trim())) {
+            return Promise.reject('请输入开头语');
+          }
+          return Promise.resolve();
+        },
+        trigger: 'blur',
+      },
+    ],
+    endSentence: [
+      {
+        validator: (_rule: any, value: any) => {
+          if (!value || (typeof value === 'string' && !value.trim())) {
+            return Promise.reject('请输入结束语');
+          }
+          return Promise.resolve();
+        },
+        trigger: 'blur',
+      },
+    ],
+  };
+
+  return baseRules;
+};
+
+const rules = computed(() => getRules());
+
+// 验证规则辅助函数
+const groupNameValidator = (_rule: any, value: any) => {
+  if (!value || (typeof value === 'string' && !value.trim())) {
+    return Promise.reject('请输入问题分组名称');
+  }
+  return Promise.resolve();
+};
+
+const questionNameValidator = (_rule: any, value: any) => {
+  if (!value || (typeof value === 'string' && !value.trim())) {
+    return Promise.reject('请输入问题');
+  }
+  return Promise.resolve();
 };
 
 // 初始化数据
 onMounted(() => {
   if (props.data) {
-    if (props.data.name) form.name = props.data.name;
-    if (props.data.openingRemarks) form.openingRemarks = props.data.openingRemarks;
-    if (props.data.closingRemarks) form.closingRemarks = props.data.closingRemarks;
-    if (props.data.questionGroups) form.questionGroups = props.data.questionGroups;
+    Object.assign(form, props.data);
   }
 });
 
 // 添加问题分组
 const addQuestionGroup = () => {
-  const newGroup: QuestionGroup = {
-    id: Date.now().toString(),
+  const newGroup = {
     name: '',
-    questions: [
+    items: [
       {
-        id: Date.now().toString() + '_1',
-        content: '',
+        name: '',
+        score: '',
       },
     ],
   };
-  form.questionGroups.push(newGroup);
+  form.groups?.push(newGroup);
 };
 
 // 添加问题
-const addQuestion = (groupId: string) => {
-  const group = form.questionGroups.find(g => g.id === groupId);
+const addQuestion = (groupIndex: number) => {
+  const group = form.groups?.[groupIndex];
   if (group) {
-    const newQuestion: Question = {
-      id: Date.now().toString(),
-      content: '',
+    const newQuestion = {
+      name: '',
+      score: '',
     };
-    group.questions.push(newQuestion);
+    group.items.push(newQuestion);
   }
 };
 
@@ -102,9 +138,14 @@ const handleSave = async () => {
     const submitData = {
       ...form,
     };
-    emits('submit', submitData);
+    onSubmit(submitData).then(() => {
+      notification.success({
+        message: '保存成功',
+      });
+      emits('submit', submitData);
+    });
   } catch (error) {
-    console.error('表单验证失败', error);
+    console.log(error, 'error===error');
   } finally {
     loading.value = false;
   }
@@ -123,27 +164,47 @@ const handleBack = () => {
     <div class="form-content">
       <a-form ref="formRef" :model="form" :rules="rules" layout="horizontal" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
         <!-- 问卷名称 -->
-        <a-form-item label="问卷名称" name="name" required>
+        <a-form-item name="name">
+          <template #label>
+            <span style="color: #ff4d4f">*</span> 问卷名称
+          </template>
           <a-input v-model:value="form.name" placeholder="请输入" />
         </a-form-item>
 
         <!-- 开头语 -->
-        <a-form-item label="开头语" name="openingRemarks" required>
-          <a-textarea v-model:value="form.openingRemarks" placeholder="请输入" :rows="4" />
+        <a-form-item name="startSentence">
+          <template #label>
+            <span style="color: #ff4d4f">*</span> 开头语
+          </template>
+          <a-textarea v-model:value="form.startSentence" placeholder="请输入开头语" :rows="4" />
         </a-form-item>
 
         <!-- 结束语 -->
-        <a-form-item label="结束语" name="closingRemarks" required>
-          <a-textarea v-model:value="form.closingRemarks" placeholder="请输入" :rows="4" />
+        <a-form-item name="endSentence">
+          <template #label>
+            <span style="color: #ff4d4f">*</span> 结束语
+          </template>
+          <a-textarea v-model:value="form.endSentence" placeholder="请输入结束语" :rows="4" />
         </a-form-item>
 
         <!-- 问题分组列表 -->
         <div class="question-groups-section">
-          <div v-for="(group, groupIndex) in form.questionGroups" :key="group.id" class="question-group-item">
+          <div v-for="(group, groupIndex) in form.groups" :key="`group-${groupIndex}`" class="question-group-item">
             <!-- 问题分组 -->
-            <a-form-item :label="`问题分组${groupIndex + 1}`" name="questionGroups" required>
+            <a-form-item
+              :name="['groups', groupIndex, 'name']"
+              :rules="[
+                {
+                  validator: groupNameValidator,
+                  trigger: 'blur',
+                },
+              ]"
+            >
+              <template #label>
+                <span style="color: #ff4d4f">*</span> 问题分组{{ groupIndex + 1 }}
+              </template>
               <div class="group-input-wrapper">
-                <a-input v-model:value="group.name" placeholder="请输入" />
+                <a-input v-model:value="group.name" placeholder="请输入问题分组名称" />
                 <button class="add-btn" @click="addQuestionGroup">
                   <PlusOutlined />
                 </button>
@@ -152,11 +213,22 @@ const handleBack = () => {
 
             <!-- 问题列表 -->
             <div class="questions-list">
-              <div v-for="(question, questionIndex) in group.questions" :key="question.id" class="question-item">
-                <a-form-item :label="`问题${questionIndex + 1}`" name="questions" required>
+              <div v-for="(question, questionIndex) in group.items" :key="`question-${groupIndex}-${questionIndex}`" class="question-item">
+                <a-form-item
+                  :name="['groups', groupIndex, 'items', questionIndex, 'name']"
+                  :rules="[
+                    {
+                      validator: questionNameValidator,
+                      trigger: 'blur',
+                    },
+                  ]"
+                >
+                  <template #label>
+                    <span style="color: #ff4d4f">*</span> 问题{{ questionIndex + 1 }}
+                  </template>
                   <div class="question-input-wrapper">
-                    <a-input v-model:value="question.content" placeholder="请输入" />
-                    <button class="add-btn" @click="addQuestion(group.id)">
+                    <a-input v-model:value="question.name" placeholder="请输入" />
+                    <button class="add-btn" @click="addQuestion(groupIndex)">
                       <PlusOutlined />
                     </button>
                   </div>
@@ -176,7 +248,7 @@ const handleBack = () => {
         </template>
         取消
       </vxe-button>
-      <vxe-button type="primary" status="warning" @click="handleSave" :loading="loading">
+      <vxe-button type="primary" status="warning" @click="handleSave" :loading="submitting">
         <template #icon>
           <SaveOutlined />
         </template>

+ 92 - 67
src/satisfaction/SeeQuestionnaire.vue

@@ -1,57 +1,83 @@
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue';
+import { reactive, onMounted } from 'vue';
+import type { SatisfactionModel } from '@/model/satisfaction.model';
+import { getSatisfactionDetailMethod } from '@/request/api/satisfaction.api';
+import { notification } from 'ant-design-vue';
 const props = defineProps<{
   data?: any;
 }>();
 
-const emits = defineEmits<{
-  submit: [data?: any];
-  back: [];
-}>();
-
-// 问卷数据结构
-interface Question {
-  id: string;
-  content: string;
-  required?: boolean;
-}
-
-interface Section {
-  id: string;
-  title: string;
-  questions: Question[];
-}
+const scoreExplanation = '1分表示非常不满意, 2分表示不满意, 3分表示一般, 4分表示满意, 5分表示非常满意';
+
+// 将数字转换为中文大写数字
+const numberToChinese = (num: number): string => {
+  const chineseNumbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
+  if (num <= 10) {
+    return chineseNumbers[num];
+  } else if (num < 20) {
+    return `十${chineseNumbers[num - 10]}`;
+  } else if (num < 100) {
+    const tens = Math.floor(num / 10);
+    const ones = num % 10;
+    return `${chineseNumbers[tens]}十${chineseNumbers[ones]}`;
+  }
+  return num.toString();
+};
 
 // 表单数据
-const form = reactive({
-  title: '就诊体验满意度', // 问卷标题
-  introduction:
-    '尊敬的患者及家属:\n 您好!感谢您选择我院就诊。为了不断的提升我们的医院服务质量,更好地为您和其他患者提供优质、高效的医疗服务,我们特开展此次就诊满意度回访调查,您的反馈对我们至关重要。请您根据实际就诊体验,恳请您抽出宝贵时间,根据您的实际就诊体验填写以下内容。我们将对您的信息严格保密。感谢您的支持与配合!', // 介绍文字
-  scoreExplanation: '1分表示非常不满意, 2分表示不满意, 3分表示一般, 4分表示满意, 5分表示非常满意', // 分数说明
-  sections: [
-    {
-      id: '1',
-      title: '一、就诊环境评价',
-      questions: [
-        { id: '1', content: '医院门诊 / 住院部的整洁度:', required: true },
-        { id: '2', content: '医院的通风情况:', required: true },
-        { id: '3', content: '医院的采光情况:', required: true },
-        { id: '4', content: '医院的噪音控制:', required: true },
-        { id: '5', content: '候诊区域的舒适度 (座椅、空间等)', required: true },
-      ],
-    },
-  ] as Section[],
-  otherComments: '', // 其他意见
+const form = reactive<SatisfactionModel>({
+  id: '',
+  name: '',
+  satisfiesyContent: {
+    name: '',
+    startSentence: '',
+    endSentence: '',
+    suggestion: '',
+    groups: [
+      {
+        name: '',
+        items: [],
+      },
+    ],
+  },
+  satisfiesyPmr: {
+    triggerType: '',
+    triggerIntervalHours: 0,
+  },
+  triggerStr: '',
+  pushType: '',
+  status: '',
+  createTime: '',
+  sendCount: 0,
 });
 
+// 存储每个问题的答案
+const answers = reactive<Record<string, number>>({});
+
+// 初始化答案数据
+const initAnswers = () => {
+  form.satisfiesyContent.groups.forEach((group, groupIndex) => {
+    group.items.forEach((question, questionIndex) => {
+      const key = `group-${groupIndex}-question-${questionIndex}`;
+      if (!(key in answers)) {
+        answers[key] = 0;
+      }
+    });
+  });
+};
+
 // 初始化数据
-onMounted(() => {
-  if (props.data) {
-    if (props.data.title) form.title = props.data.title;
-    if (props.data.introduction) form.introduction = props.data.introduction;
-    if (props.data.scoreExplanation) form.scoreExplanation = props.data.scoreExplanation;
-    if (props.data.sections) form.sections = props.data.sections;
-    if (props.data.otherComments) form.otherComments = props.data.otherComments;
+onMounted(async () => {
+  if (props.data && props.data.id) {
+    try {
+      const res = await getSatisfactionDetailMethod(props.data.id);
+      if (res && typeof res === 'object') {
+        Object.assign(form, res);
+        initAnswers();
+      }
+    } catch (error) {
+      notification.error({ message: '获取满意度问卷详情失败', description: (error as Error).message });
+    }
   }
 });
 </script>
@@ -63,43 +89,40 @@ onMounted(() => {
       <a-form ref="formRef" :model="form" layout="vertical">
         <!-- 问卷标题 -->
         <h1 class="questionnaire-title-section">
-          {{ form.title }}
+          {{ form.name }}
         </h1>
 
         <!-- 介绍文字 -->
         <div class="introduction-section">
-          {{ form.introduction }}
+          {{ form.satisfiesyContent.startSentence }}
         </div>
 
         <!-- 分数说明 -->
         <div class="score-explanation-section">
           <div class="label">分数说明:</div>
           <div class="score-explanation-input label">
-            {{ form.scoreExplanation }}
+            {{ scoreExplanation }}
           </div>
         </div>
 
-        <!-- 章节列表 -->
         <div class="sections-container">
-          <div v-for="section in form.sections" :key="section.id" class="section-item">
-            <!-- 章节标题 -->
-            <!-- <div class="section-header"> -->
+          <div v-for="(group, groupIndex) in form.satisfiesyContent.groups" :key="`group-${groupIndex}-${group.name}`" class="section-item">
             <h2 class="section-title-input label">
-              {{ section.title }}
+              {{ `${numberToChinese(groupIndex + 1)}、${group.name}` }}
             </h2>
 
             <!-- 问题列表 -->
             <div class="questions-list">
-              <div v-for="(question, questionIndex) in section.questions" :key="question.id" class="question-item">
+              <div v-for="(question, questionIndex) in group.items" :key="question.name" class="question-item">
                 <div class="question-content">
                   <span class="question-number">{{ questionIndex + 1 }}、</span>
                   <div class="question-input label">
-                    {{ question.content }}
+                    {{ question.name }}
                   </div>
                 </div>
                 <!-- 评分选项(1-5分) -->
                 <div class="score-options">
-                  <a-radio-group :value="1">
+                  <a-radio-group>
                     <a-radio :value="1">1分</a-radio>
                     <a-radio :value="2">2分</a-radio>
                     <a-radio :value="3">3分</a-radio>
@@ -115,13 +138,10 @@ onMounted(() => {
         <!-- 其他意见 -->
         <div class="other-comments-section">
           <div class="other-comments-label">其他意见:</div>
-          <a-textarea
-            v-model:value="form.otherComments"
-            placeholder="请给我们留言您的其他意见"
-            :rows="4"
-            class="other-comments-input"
-          />
+          <a-textarea v-model:value="form.satisfiesyContent.suggestion" placeholder="请给我们留言您的其他意见" :rows="4" class="other-comments-input" disabled />
         </div>
+        <!--结束语 -->
+        <div class="end-sentence-section">{{ form.satisfiesyContent.endSentence }}</div>
       </a-form>
     </div>
   </div>
@@ -129,7 +149,6 @@ onMounted(() => {
 
 <style scoped lang="scss">
 .questionnaire-editor-container {
-  // border: 1px solid #f0f0f0;
   padding: 24px;
   background: #fff;
   height: 100%;
@@ -178,15 +197,14 @@ onMounted(() => {
 // 介绍文字
 .introduction-section {
   margin-bottom: 24px;
+  font-size: 17px;
 
   .introduction-textarea {
     :deep(.ant-input) {
       font-size: 14px;
       line-height: 2;
       white-space: pre-wrap;
-      // border: 1px solid #d9d9d9;
       padding: 12px;
-      // border-radius: 4px;
     }
   }
 }
@@ -197,11 +215,11 @@ onMounted(() => {
   display: flex;
   align-items: center;
   gap: 8px;
+  font-size: 16px;
+  font-weight: 500;
+  color:black
 
   .label {
-    font-size: 14px;
-    color: rgba(0, 0, 0, 0.85);
-    font-weight: 500;
     white-space: nowrap;
   }
 
@@ -292,6 +310,7 @@ onMounted(() => {
   .question-input {
     flex: 1;
     font-weight: bold;
+    font-size: 16px;
     :deep(.ant-input) {
       font-size: 14px;
       border: none;
@@ -344,6 +363,7 @@ onMounted(() => {
   border: 1px solid #e8e8e8;
   border-radius: 4px;
   background: #fff;
+  margin-bottom: 20px;
 
   .other-comments-label {
     font-size: 14px;
@@ -359,6 +379,11 @@ onMounted(() => {
   }
 }
 
+// 结束语
+.end-sentence-section {
+  font-size: 17px;
+}
+
 :deep(.ant-input),
 :deep(.ant-textarea),
 :deep(.ant-select-selector) {

+ 37 - 105
src/satisfaction/SendRecord.vue

@@ -1,22 +1,19 @@
 <script setup lang="ts">
-import { ref, reactive, shallowRef, onBeforeUnmount, onMounted } from 'vue';
-import type { SystemItemModel, SystemIteQuery } from '@/model/care.model';
-import SeeQuestionnaire from '@/satisfaction/SeeQuestionnaire.vue';
+import { ref, reactive, shallowRef, onMounted } from 'vue';
 import SeeSatisfaction from '@/satisfaction/SeeQuestionnaire.vue';
+import type { SatisfactionSendRecordModel, SatisfactionSendRecordQuery } from '@/model/satisfaction.model';
 // 接口数据
-
-import { pageConfirmedCpMethod, deleteConfirmedCpMethod } from '@/request/api/care.api';
-import { usePagination, useRequest } from 'alova/client';
-import { notification } from 'ant-design-vue';
+import { satisfactionSendRecordMethod } from '@/request/api/satisfaction.api';
+import { usePagination } from 'alova/client';
 import dayjs from 'dayjs';
 import { type VxeFormListeners, type VxeFormProps, type VxeGridInstance, type VxeGridListeners, type VxeGridProps, VxeUI } from 'vxe-pc-ui';
 
-const model = shallowRef<SystemIteQuery>();
-const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
+const model = shallowRef<SatisfactionSendRecordQuery>();
+const searchFormProps = reactive<VxeFormProps<SatisfactionSendRecordQuery>>({
   titleWidth: 100,
   titleAlign: 'right',
   titleColon: true,
-  data: { types: ['1'] as any, status: '0' as any },
+  data: { name: '' },
   items: [
     {
       field: 'name',
@@ -33,15 +30,15 @@ const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
       },
     },
     {
-      field: 'status',
+      field: 'isFilled',
       title: '填写状态',
       span: 6,
       itemRender: {
         name: 'VxeRadioGroup',
         props: {
           options: [
-            { label: '已填写', value: '0' },
-            { label: '未填写', value: '1' },
+            { label: '已填写', value: true },
+            { label: '未填写', value: false },
           ],
         },
       },
@@ -53,47 +50,36 @@ const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
         options: [
           { name: 'submits', type: 'submit', content: '查询', status: 'primary' },
           { name: 'reset', type: 'reset', content: '重置', status: 'warning' },
-          // { name: 'add', content: '新增', status: 'primary' },
         ],
-        events: {
-          click(slotParams: any, { name }: any) {
-            if (name === 'add') {
-              // 新增
-              editConfirmed();
-            }
-          },
-        },
       },
     },
   ],
 });
-const searchFormEmits: VxeFormListeners<SystemIteQuery> = {
-  // 查询随访计划
+const searchFormEmits: VxeFormListeners<SatisfactionSendRecordQuery> = {
   submit({ data }) {
+    data.sendTimeStart = sendTimeStart.value ? dayjs(sendTimeStart.value).format('YYYY-MM-DD HH:mm') : '';
+    data.sendTimeEnd = sendTimeEnd.value ? dayjs(sendTimeEnd.value).format('YYYY-MM-DD HH:mm') : '';
     onSearch(data);
   },
 
   // 重置
   reset({ data }) {
+    sendTimeStart.value = '';
+    sendTimeEnd.value = '';
     onSearch(data);
   },
 };
-function onSearch(data: SystemIteQuery) {
-  const types: string[] = (data as any).types?.length ? (data as any).types : ['1'];
-  const status: string = (data as any).status ?? '0';
-  const isForWrap: SystemIteQuery['isForWrap'] = types.includes('1') ? 'Y' : null;
-  const isForInfer: SystemIteQuery['isForInfer'] = types.includes('2') ? 'Y' : null;
-
-  model.value = { ...data, types: [...types] as any, status: status as any, isForWrap, isForInfer };
+function onSearch(data: SatisfactionSendRecordQuery) {
+  model.value = { ...data };
   nextTick(() => {
-    (searchFormProps.data as any)!.types = [...types];
-    (searchFormProps.data as any)!.status = status;
-    (searchFormProps.data as any)!.isForInfer = isForInfer;
-    (searchFormProps.data as any)!.isForWrap = isForWrap;
+    (searchFormProps.data as any)!.name = data.name;
+    (searchFormProps.data as any)!.isFilled = data.isFilled;
+    (searchFormProps.data as any)!.sendTimeStart = data.sendTimeStart;
+    (searchFormProps.data as any)!.sendTimeEnd = data.sendTimeEnd;
   });
 }
-const gridRef = ref<VxeGridInstance<SystemItemModel>>();
-const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
+const gridRef = ref<VxeGridInstance<SatisfactionSendRecordModel>>();
+const gridOptions = reactive<VxeGridProps<SatisfactionSendRecordModel>>({
   id: 'tag-list',
   border: true,
   showOverflow: true,
@@ -105,7 +91,6 @@ const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
     custom: true,
     zoom: true,
     slots: {
-      // buttons: 'handle',
       tools: 'toolbar-extra',
     },
   },
@@ -117,14 +102,12 @@ const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
   },
   columns: [
     { type: 'seq', width: 70, fixed: 'left' },
-    { field: 'name', title: '名称' },
-    { field: 'conditioningProgramType', title: '问卷名称' },
-    { field: 'cpFixedPricingRule.unitPrice', title: '用户姓名' },
-    { field: 'cpFixedPricingRule.pricingUnit', title: '手机号码' },
-    { field: 'cpFixedPricingRule.convertDose', title: '得分' },
-    { field: 'cpFixedPricingRule.convertDose', title: '发送时间' },
-    { field: 'cpFixedPricingRule.convertDose', title: '提交时间' },
-    // { field: 'conditioningProgramSupplierName', title: '启用状态', slots: { default: 'conditioningProgramSupplierNameCell' } },
+    { field: 'name', title: '问卷名称' },
+    { field: 'patientName', title: '用户姓名' },
+    { field: 'phone', title: '手机号码' },
+    { field: 'score', title: '得分' },
+    { field: 'sendTime', title: '发送时间' },
+    { field: 'submitTime', title: '提交时间' },
     {
       field: 'action',
       title: '操作',
@@ -138,18 +121,12 @@ const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
         },
         options: [
           { content: '查看', status: 'primary', name: 'seeDetail' },
-          // { content: '编辑', status: 'primary', name: 'editConfirmed' },
-          // { content: '删除', status: 'primary', name: 'deleteConfirmed' },
         ],
         events: {
           click({ row, rowIndex }: any, { name }: any) {
             let method;
             if (name === 'seeDetail') {
               method = seeDetail;
-            } else if (name === 'editConfirmed') {
-              method = editConfirmed;
-            } else if (name === 'deleteConfirmed') {
-              method = deleteConfirmed;
             }
             method?.(row, rowIndex);
           },
@@ -171,7 +148,7 @@ const {
   refresh,
   remove,
   send: sendRefresh,
-} = usePagination((page, size) => pageConfirmedCpMethod(page, size, model.value), {
+} = usePagination((page, size) => satisfactionSendRecordMethod(page, size, model.value), {
   initialData: { data: [], total: 0 },
   initialPage: 1,
   initialPageSize: 100,
@@ -185,52 +162,7 @@ onMounted(() => {
   onSearch(toRaw(searchFormProps.data) as any);
 });
 
-function deleteConfirmed(model: SystemItemModel, index: number) {
-  const { name } = model;
-  VxeUI.modal.confirm({
-    title: `删除项目`,
-    content: `确认要删除 ${name} 项目吗?`,
-    showClose: false,
-    onConfirm() {
-      deleteConfirmedCpMethod(model).then(() => {
-        notification.success({
-          message: `删除项目: ${name}`,
-          description: '操作成功',
-        });
-        refresh(page.value);
-      });
-    },
-  });
-}
-
-function editConfirmed(model?: SystemItemModel, index?: number) {
-  const addType = `itemsList`;
-  VxeUI.modal.open({
-    title: model?.conditioningProgramType ?? '项目',
-    fullscreen: true,
-    escClosable: true,
-    destroyOnClose: true,
-    id: `edit-notify-modal`,
-    remember: true,
-    storage: true,
-    slots: {
-      default() {
-        return h(SeeQuestionnaire, <any>{
-          data: {
-            ...model,
-            addType,
-          },
-          onSubmit(data: SystemItemModel) {
-            refresh(page.value);
-            VxeUI.modal.close(`edit-notify-modal`);
-          },
-        });
-      },
-    },
-  });
-}
-
-function seeDetail(model?: SystemItemModel, index?: number) {
+function seeDetail(model?: SatisfactionSendRecordModel, index?: number) {
   VxeUI.modal.open({
     title: model?.name,
     fullscreen: true,
@@ -249,12 +181,12 @@ function seeDetail(model?: SystemItemModel, index?: number) {
   });
 }
 // 日期验证
-const updateTimeStart = ref<string>('');
-const updateTimeEnd = ref<string>('');
+const sendTimeStart = ref<string>('');
+const sendTimeEnd = ref<string>('');
 // 禁用结束时间的日期(早于开始时间的日期)
 function disabledEndDate(current: any) {
-  if (!updateTimeStart.value) return false;
-  return current && current < dayjs(updateTimeStart.value);
+  if (!sendTimeStart.value) return false;
+  return current && current < dayjs(sendTimeStart.value);
 }
 defineExpose({
   send: sendRefresh,
@@ -266,9 +198,9 @@ defineExpose({
       <vxe-form v-bind="searchFormProps" v-on="searchFormEmits">
         <template #createTimes>
           <div class="date-range-container">
-            <a-date-picker v-model:value="updateTimeStart" placeholder="请选择开始日期" style="flex: 1" :show-time="{ format: 'HH:mm' }" />
+            <a-date-picker v-model:value="sendTimeStart" placeholder="请选择开始日期" style="flex: 1" :show-time="{ format: 'HH:mm' }" />
             <span class="date-separator">至</span>
-            <a-date-picker v-model:value="updateTimeEnd" placeholder="请选择结束日期" style="flex: 1" :disabledDate="disabledEndDate" :show-time="{ format: 'HH:mm' }" />
+            <a-date-picker v-model:value="sendTimeEnd" placeholder="请选择结束日期" style="flex: 1" :disabledDate="disabledEndDate" :show-time="{ format: 'HH:mm' }" />
           </div>
         </template>
       </vxe-form>

+ 72 - 133
src/satisfaction/SetQuestionnaire.vue

@@ -2,127 +2,105 @@
 import { ref, reactive, onMounted } from 'vue';
 import type { FormInstance } from 'ant-design-vue';
 import { VxeUI } from 'vxe-pc-ui';
-import { PlusOutlined, ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons-vue';
+import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons-vue';
+import { setSatisfactionTriggerMethod } from '@/request/api/satisfaction.api';
+import { useRequest } from 'alova/client';
+import { notification } from 'ant-design-vue';
 
 const props = defineProps<{
   data?: any;
 }>();
-
+const { loading: setting, send: onSetting } = useRequest(setSatisfactionTriggerMethod, { immediate: false });
 const emits = defineEmits<{
   submit: [data?: any];
   back: [];
 }>();
 
 const formRef = ref<FormInstance>();
-const loading = ref<boolean>(false);
-
-// 触发条件逻辑类型:且/或
-const triggerLogic = ref<'and' | 'or'>('and');
 
 // 条件类型选项
 const conditionTypeOptions = [
-  { label: '就诊后', value: 'afterVisit' },
-  { label: '患者标签', value: 'patientTag' },
+  { label: '就诊后', value: '0' },
+  { label: '调养付款后', value: '1' },
+  { label: '调养结束后', value: '2' },
 ];
 
-// 操作符选项(用于患者标签)
-// const operatorOptions = [
-//   { label: '包含', value: 'contains' },
-//   { label: '不包含', value: 'notContains' },
-//   { label: '等于', value: 'equals' },
-// ];
-
-// 用户标签选项(示例数据,实际应从API获取)
-// const userTagOptions = ref([
-//   { label: '标签1', value: '1' },
-//   { label: '标签2', value: '2' },
-//   { label: '标签3', value: '3' },
-// ]);
-
-// 触发条件列表
-interface TriggerCondition {
-  id: string;
-  type: string; // 'afterVisit' | 'patientTag'
-  value?: string | string[]; // 就诊后的小时数 或 患者标签数组
-  operator?: string; // 操作符:contains, notContains, equals
-}
-
-const triggerConditions = ref<TriggerCondition[]>([
-  {
-    id: '1',
-    type: 'afterVisit',
-    value: '',
-  },
-  {
-    id: '2',
-    type: 'patientTag',
-    operator: 'contains',
-    value: [],
-  },
-]);
-
 // 表单数据
 const form = reactive({
   name: '', // 问卷名称
   enabled: false, // 启用状态
-  notificationChannel: [], // 通知渠道:站内等
-  conditionType: '', // 触发条件类型
-  conditionValue: '', // 触发条件值
+  pushType: ['0'], // 通知渠道:站内等,默认选中站内
+  status: '0', // 启用状态:0-启用,1-禁用
+  satisfiesyPmr: {
+    triggerType: '0', // 触发方式:0-就诊后,1-调养付款后,2-调养结束后
+    triggerIntervalHours: null as any, // 触发间隔小时数
+  },
 });
 
 // 表单验证规则
-const rules = {
-  // name: [{ required: true, message: '请输入问卷名称', trigger: 'blur' }],
-  enabled: [{ required: true, message: '请选择启用状态', trigger: 'change' }],
-  notificationChannel: [{ required: true, message: '请选择通知渠道', trigger: 'change' }],
+const rules: any = {
+  pushType: [{ required: true, message: '请选择通知渠道', trigger: 'change', type: 'array', min: 1 }],
 };
 
 // 初始化数据
 onMounted(() => {
   if (props.data) {
     Object.assign(form, props.data);
-    if (props.data.triggerLogic) {
-      triggerLogic.value = props.data.triggerLogic;
-    }
-    if (props.data.triggerConditions) {
-      triggerConditions.value = props.data.triggerConditions;
+    form.enabled = props.data.status === '0' ? true : false;
+    // 确保 pushType 是数组格式
+    if (typeof form.pushType === 'string') {
+      form.pushType = [form.pushType];
+    } else if (!Array.isArray(form.pushType)) {
+      form.pushType = ['0'];
     }
+  } else {
+    // 默认选中站内
+    form.pushType = ['0'];
+  }
+  // 确保 satisfiesyPmr 始终存在
+  if (!form.satisfiesyPmr) {
+    form.satisfiesyPmr = {
+      triggerType: '0',
+      triggerIntervalHours: null as any,
+    };
+  }
+  // 如果 triggerIntervalHours 为 0,设置为 null 以显示占位符
+  if (form.satisfiesyPmr.triggerIntervalHours === 0) {
+    form.satisfiesyPmr.triggerIntervalHours = null as any;
   }
 });
 
-// 添加条件行
-const addCondition = () => {
-  const newCondition: TriggerCondition = {
-    id: Date.now().toString(),
-    type: 'patientTag',
-    operator: 'contains',
-    value: [],
-  };
-  triggerConditions.value.push(newCondition);
-};
-
 // 保存
 const handleSave = async () => {
   try {
     await formRef.value?.validate();
-    loading.value = true;
+    setting.value = true;
+    const status = form.enabled ? '0' : '1';
     const submitData = {
       ...form,
-      triggerLogic: triggerLogic.value,
-      triggerConditions: triggerConditions.value,
+      status,
+      pushType: Array.isArray(form.pushType) ? form.pushType[0] || '0' : form.pushType || '0',
+      satisfiesyPmr: {
+        ...form.satisfiesyPmr,
+        triggerIntervalHours: form.satisfiesyPmr.triggerIntervalHours === null ? 0 : form.satisfiesyPmr.triggerIntervalHours,
+      },
     };
-    emits('submit', submitData);
+    onSetting(submitData).then(() => {
+      notification.success({
+        message: '保存成功',
+      });
+      emits('submit', submitData);
+    });
   } catch (error) {
-    console.error('表单验证失败', error);
+    notification.error({ message: '表单验证失败', description: (error as Error).message });
   } finally {
-    loading.value = false;
+    setting.value = false;
   }
 };
 
 // 返回
 const handleBack = () => {
-  emits('back');
-  VxeUI.modal.close(`edit-notify-modal`);
+  VxeUI.modal.close(`set-satisfaction-modal`);
 };
 </script>
 
@@ -130,15 +108,14 @@ const handleBack = () => {
   <div class="satisfaction-form-container">
     <!-- 表单内容 -->
     <div class="form-content">
-      <a-form ref="formRef" :model="form" :rules="rules" layout="horizontal" >
+      <a-form ref="formRef" :model="form" :rules="rules" layout="horizontal">
         <!-- 问卷名称和启用 - 同一行 -->
         <div class="form-row">
           <a-form-item label="问卷名称" name="name" class="form-row-item form-row-item-name">
-            <!-- <a-input v-model:value="form.name" placeholder="请输入" /> -->
             {{ form.name }}
           </a-form-item>
 
-          <a-form-item label="启用:" name="enabled" class="form-row-item form-row-item-enabled" required>
+          <a-form-item label="启用:" name="enabled" class="form-row-item form-row-item-enabled">
             <a-switch v-model:checked="form.enabled" />
           </a-form-item>
         </div>
@@ -146,61 +123,29 @@ const handleBack = () => {
         <!-- 触发条件 -->
         <div class="trigger-conditions-section">
           <div class="flex items-center">
-          <div class="section-label">触发条件:</div>
-
-          <!-- 且/或按钮 -->
-          <!-- <div class="logic-buttons">
-            <button class="logic-btn" :class="{ active: triggerLogic === 'and' }" @click="triggerLogic = 'and'">且</button>
-            <button class="logic-btn" :class="{ active: triggerLogic === 'or' }" @click="triggerLogic = 'or'">或</button>
-          </div> -->
-        </div>
+            <div class="section-label">触发条件:</div>
+          </div>
 
           <!-- 条件列表 -->
           <div class="condition-row">
-          <!-- <div class="conditions-list condition-row"> -->
-            
-            <!-- <div v-for="(condition, index) in triggerConditions" :key="condition.id" class="condition-row"> -->
-              <!-- <span v-if="index > 0" class="condition-logic-label">{{ triggerLogic === 'and' ? '且' : '或' }}</span> -->
-
-              <!-- 就诊后条件 -->
-              <!-- <template v-if="condition.type === 'afterVisit'"> -->
-                <a-select v-model:value="form.conditionType" :options="conditionTypeOptions" style="width: 200px" class="condition-select" />
-                <a-input v-model:value="form.conditionValue" placeholder="请输入" style="width: 160px" class="condition-input" />
-                <span class="condition-unit">小时</span>
-              <!-- </template> -->
-
-              <!-- 患者标签条件 -->
-              <!-- <template v-else-if="condition.type === 'patientTag'">
-                <a-select v-model:value="condition.type" :options="conditionTypeOptions" style="width: 120px" class="condition-select" />
-                <a-select v-model:value="condition.operator" :options="operatorOptions" style="width: 100px" class="condition-select" />
-                <a-select
-                  v-model:value="condition.value"
-                  mode="multiple"
-                  :options="userTagOptions"
-                  placeholder="请选择标签"
-                  style="width: 200px"
-                  class="condition-select"
-                  :max-tag-count="2"
-                >
-                  <template #tagRender="{ label, closable, onClose }">
-                    <a-tag :closable="closable" @close="onClose" style="margin-right: 3px">
-                      {{ label }}
-                    </a-tag>
-                  </template>
-                </a-select>
-
-                <button v-if="index === triggerConditions.length - 1" class="add-condition-btn" @click="addCondition">
-                  <PlusOutlined />
-                </button>
-              </template> -->
-            <!-- </div> -->
+            <!-- 就诊后条件 -->
+            <a-select v-model:value="form.satisfiesyPmr.triggerType" :options="conditionTypeOptions" style="width: 200px" class="condition-select" />
+            <a-input 
+              placeholder="请输入" 
+              style="width: 160px" 
+              class="condition-input"
+              type="number"
+              :value="form.satisfiesyPmr.triggerIntervalHours === 0 || form.satisfiesyPmr.triggerIntervalHours === null ? undefined : form.satisfiesyPmr.triggerIntervalHours"
+              @input="(e: any) => { form.satisfiesyPmr.triggerIntervalHours = e.target.value === '' || e.target.value === null ? null : (Number(e.target.value) || null) }"
+            />
+            <span class="condition-unit">小时</span>
           </div>
         </div>
 
         <!-- 通知渠道 -->
-        <a-form-item label="通知渠道:" name="notificationChannel" class="form-row-item" required>
-          <a-checkbox-group v-model:value="form.notificationChannel">
-            <a-checkbox value="inSite">站内</a-checkbox>
+        <a-form-item label="通知渠道:" name="pushType" class="form-row-item" required>
+          <a-checkbox-group v-model:value="form.pushType">
+            <a-checkbox value="0">站内</a-checkbox>
           </a-checkbox-group>
         </a-form-item>
       </a-form>
@@ -214,12 +159,7 @@ const handleBack = () => {
         </template>
         取消
       </vxe-button>
-      <vxe-button
-        type="submit"
-        status="warning"
-        @click="handleSave"
-        :loading="loading"
-      >
+      <vxe-button type="submit" status="warning" @click="handleSave" :loading="setting">
         <template #icon>
           <SaveOutlined />
         </template>
@@ -248,7 +188,6 @@ const handleBack = () => {
   display: flex;
   gap: 16px;
   align-items: flex-start;
-  // margin-bottom: 24px;
 }
 
 .form-row-item {

+ 212 - 164
src/satisfaction/Statistics.vue

@@ -1,91 +1,138 @@
 <script setup lang="ts">
-import { computed, ref, onMounted } from 'vue';
+import { computed, ref, watch } from 'vue';
 import type { Dayjs } from 'dayjs';
 import { use } from 'echarts/core';
 import { BarChart } from 'echarts/charts';
 import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components';
 import { CanvasRenderer } from 'echarts/renderers';
 import VChart from 'vue-echarts';
-// import { useRequest } from 'alova/client';
-// import type { SatisfactionStatisticItem  } from '@/request/api/satisfaction.api';
-// import { satisfactionStatisticsMethod } from '@/request/api/satisfaction.api';
+import type { SatisfactionStatisticsModel, SatisfactionStatisticsQuery, SatisfactionModel } from '@/model/satisfaction.model';
+import { satisfactionStatisticsMethod, satisfactionMethod } from '@/request/api/satisfaction.api';
+import { useRequest } from 'alova/client';
+import { notification } from 'ant-design-vue';
 
 use([CanvasRenderer, BarChart, GridComponent, TooltipComponent, TitleComponent]);
 
 defineOptions({
   name: 'SatisfactionStatisticsPage',
 });
-const questionnaireList = ref<any[]>([
-  { label: '问卷1', value: '1' },
-  { label: '问卷2', value: '2' },
-  { label: '问卷3', value: '3' },
-]);
+
+const questionnaireList = ref<Array<{ label: string; value: string }>>([]);
+const { onSuccess } = useRequest(satisfactionMethod, {
+  immediate: true,
+});
+onSuccess((res: any) => {
+  if (res && res.data && Array.isArray(res.data?.data)) {
+    questionnaireList.value = res.data.data.map((item: SatisfactionModel) => ({
+      label: item.name || '',
+      value: item.id || '',
+    }));
+  }
+});
+
+// 默认选中第一张问卷
 const questionnaireId = ref<Array<{ label: string; value: string }>>([]);
 type RangeValue = [Dayjs, Dayjs] | null;
 
-const selectedRange = ref<RangeValue>(null);
-
-const statistics = ref<any[]>([]);
-
-const mockStatistics = [
-  { date: '2024-09-01', score: 5, count: 620 },
-  { date: '2024-09-05', score: 4, count: 140 },
-  { date: '2024-09-06', score: 3, count: 86 },
-  { date: '2024-09-07', score: 2, count: 18 },
-  { date: '2024-09-08', score: 1, count: 6 },
-  { date: '2024-09-10', score: 4, count: 212 },
-  { date: '2024-09-15', score: 5, count: 280 },
-  { date: '2024-09-20', score: 3, count: 35 },
-];
-
-// const { loading, send: fetchStatistics } = useRequest((params?: SatisfactionStatisticsQuery) => satisfactionStatisticsMethod(params), {
-//   immediate: false,
-// });
-
-function normalizeResponse(res: any): any[] {
-  const list = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : Array.isArray(res?.records) ? res.records : [];
-  return list.map((item: any & Record<string, any>) => ({
-    score: Number(item.score ?? item.rating ?? item.result ?? 0),
-    count: Number(item.count ?? item.total ?? item.value ?? 0),
-    date: item.date ?? item.sendDate ?? item.createTime ?? item.statDate ?? '',
-  }));
-}
+const selectedRange = ref<RangeValue>(null) as any;
+
+const statisticsData = ref<Record<string, SatisfactionStatisticsModel>>({});
+
+const { loading, send: fetchStatistics } = useRequest((params?: SatisfactionStatisticsQuery) => satisfactionStatisticsMethod(params), {
+  immediate: false,
+});
 
 async function loadStatistics(range?: RangeValue) {
-  const params = range
-    ? {
-        startDate: range[0].startOf('day').format('YYYY-MM-DD HH:mm:ss'),
-        endDate: range[1].endOf('day').format('YYYY-MM-DD HH:mm:ss'),
-      }
-    : undefined;
+  const selected = questionnaireId.value;
+  if (!selected || !Array.isArray(selected) || selected.length === 0) {
+    statisticsData.value = {};
+    return;
+  }
+
+  // 获取所有选中的问卷ID
+  const ids = selected.map((item) => {
+    if (typeof item === 'object' && item !== null) {
+      return String(item.value || '');
+    }
+    return String(item);
+  });
+
+  //时间范围
+  const baseParams: Partial<SatisfactionStatisticsQuery> = {};
+  if (range) {
+    baseParams.sendTimeStart = range[0].startOf('day').format('YYYY-MM-DD');
+    baseParams.sendTimeEnd = range[1].endOf('day').format('YYYY-MM-DD');
+  }
+
+  // 为每个问卷ID分别调用接口
+  const dataMap: Record<string, SatisfactionStatisticsModel> = {};
+
   try {
-    // const res = await fetchStatistics(params);
-    // const list = normalizeResponse(res);
-    const list = mockStatistics;
-    statistics.value = list.length ? list : mockStatistics;
+    // 并发请求所有问卷的统计数据
+    const promises = ids.map(async (id) => {
+      const params: SatisfactionStatisticsQuery = {
+        id,
+        ...baseParams,
+      } as SatisfactionStatisticsQuery;
+
+      try {
+        const res = await fetchStatistics(params);
+        console.log(`问卷 ${id} 的统计数据:`, res);
+        if (res && typeof res === 'object') {
+          const data = { ...res, id: id } as SatisfactionStatisticsModel;
+          if (Array.isArray(data.xaxis) && Array.isArray(data.yaxis)) {
+            return { id, data };
+          }
+        }
+        return null;
+      } catch (error) {
+        notification.error({ message: `获取问卷 ${id} 的统计数据失败`, description: (error as Error).message });
+        return null;
+      }
+    });
+
+    const results = await Promise.all(promises);
+
+    // 处理返回结果
+    results.forEach((result) => {
+      if (result && result.data) {
+        dataMap[result.id] = result.data;
+      }
+    });
+
+    statisticsData.value = dataMap;
   } catch (error) {
-    console.error('获取满意度统计失败,使用本地数据兜底。', error);
-    statistics.value = mockStatistics;
+    notification.error({ message: '获取满意度统计失败', description: (error as Error).message });
+    statisticsData.value = {};
   }
 }
 
-function handleRangeChange(dates: RangeValue) {
-  selectedRange.value = dates;
-  loadStatistics(dates ?? undefined);
+// 处理查询按钮点击
+function handleQuery() {
+  loadStatistics(selectedRange.value ?? undefined);
 }
 
-const aggregatedCounts = computed(() => {
-  const map: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
-  statistics.value.forEach((item) => {
-    const score = Number(item.score);
-    if (score >= 1 && score <= 5) {
-      map[score] += Number(item.count ?? 0);
+// 监听问卷列表变化,自动选中第一张问卷
+let isInitialLoad = true;
+watch(
+  questionnaireList,
+  (newList) => {
+    if (newList.length > 0 && questionnaireId.value.length === 0) {
+      questionnaireId.value = [
+        {
+          label: newList[0].label,
+          value: newList[0].value,
+        },
+      ];
+      // 默认选中第一个问卷时,自动加载一次数据
+      if (isInitialLoad) {
+        isInitialLoad = false;
+        loadStatistics(selectedRange.value ?? undefined);
+      }
     }
-  });
-  return map;
-});
-
-const totalSamples = computed(() => Object.values(aggregatedCounts.value).reduce((sum, val) => sum + val, 0));
+  },
+  { immediate: true }
+);
 
 // 为每个选中的问卷生成图表配置
 const chartOptions = computed(() => {
@@ -94,102 +141,105 @@ const chartOptions = computed(() => {
     return [];
   }
 
-  const counts = aggregatedCounts.value;
-  const categories = ['1', '2', '3', '4', '5'];
-  
-  return selected.map((questionnaire) => {
-    // labelInValue 返回的数据结构:{label: string, value: string}
-    let qValue: string;
-    let qLabel: string;
-    
-    if (typeof questionnaire === 'object' && questionnaire !== null) {
-      qValue = String(questionnaire.value || '');
-      
-      // 尝试获取 label,确保是字符串类型
-      let tempLabel: any = questionnaire.label;
-      if (tempLabel && typeof tempLabel === 'string') {
-        qLabel = tempLabel;
+  return selected
+    .map((questionnaire) => {
+      let qValue: string;
+      let qLabel: string;
+
+      if (typeof questionnaire === 'object' && questionnaire !== null) {
+        qValue = String(questionnaire.value || '');
+
+        let tempLabel: any = questionnaire.label;
+        if (tempLabel && typeof tempLabel === 'string') {
+          qLabel = tempLabel;
+        } else {
+          const found = questionnaireList.value.find((item) => String(item.value) === qValue);
+          qLabel = found?.label || `问卷 ${qValue}`;
+        }
       } else {
-        // 如果 label 不存在或不是字符串,从 questionnaireList 中查找
-        const found = questionnaireList.value.find(item => String(item.value) === qValue);
+        qValue = String(questionnaire);
+        const found = questionnaireList.value.find((item) => String(item.value) === qValue);
         qLabel = found?.label || `问卷 ${qValue}`;
       }
-    } else {
-      qValue = String(questionnaire);
-      // 从 questionnaireList 中查找对应的 label
-      const found = questionnaireList.value.find(item => String(item.value) === qValue);
-      qLabel = found?.label || `问卷 ${qValue}`;
-    }
-    
-    // 最终确保 qLabel 是有效的字符串(防止任何意外情况)
-    qLabel = String(qLabel || `问卷 ${qValue}`);
-    
-    // 这里可以根据不同的问卷ID加载不同的统计数据
-    // 目前使用相同的统计数据作为示例
-    const seriesData = categories.map((score) => counts[Number(score)] ?? 0);
-    
-    return {
-      questionnaireId: qValue,
-      questionnaireLabel: qLabel,
-      option: {
-        title: {
-          text: String(qLabel), // 确保是字符串
-          left: 'center',
-          top: 0,
-          textStyle: { 
-            fontSize: 16, 
-            fontWeight: 600,
-            color: '#333'
+
+      qLabel = String(qLabel || `问卷 ${qValue}`);
+
+      // 获取该问卷的统计数据
+      const statData = statisticsData.value[qValue];
+
+      if (!statData || !Array.isArray(statData.xaxis) || !Array.isArray(statData.yaxis)) {
+        return null;
+      }
+
+      // 使用返回的数据 xaxis 和 yaxis
+      const xaxis = statData.xaxis;
+      const yaxis = statData.yaxis;
+
+      return {
+        questionnaireId: qValue,
+        questionnaireLabel: qLabel,
+        option: {
+          title: {
+            text: String(qLabel), // 确保是字符串
+            left: 'center',
+            top: 0,
+            textStyle: {
+              fontSize: 16,
+              fontWeight: 600,
+              color: '#333',
+            },
           },
-        },
-        tooltip: { trigger: 'axis' },
-        grid: { left: 60, right: 40, top: 50, bottom: 50 },
-        xAxis: {
-          type: 'category',
-          data: categories,
-          name: '分',
-          nameGap: 25,
-          axisTick: { alignWithLabel: true },
-          axisLabel: { fontSize: 12 },
-        },
-        yAxis: {
-          type: 'value',
-          name: '人',
-          minInterval: 1,
-          splitLine: {
-            lineStyle: { type: 'dashed', color: '#eaeaea' },
+          tooltip: { trigger: 'axis' },
+          grid: { left: 60, right: 40, top: 50, bottom: 50 },
+          xAxis: {
+            type: 'category',
+            data: xaxis,
+            name: '分',
+            nameGap: 25,
+            axisTick: { alignWithLabel: true },
+            axisLabel: { fontSize: 12 },
           },
-          axisLine: { lineStyle: { color: '#d9d9d9' } },
-        },
-        series: [
-          {
-            type: 'bar',
-            data: seriesData,
-            barWidth: 40,
-            itemStyle: {
-              color: '#69c0ff',
-              borderRadius: [6, 6, 0, 0],
+          yAxis: {
+            type: 'value',
+            name: '人',
+            minInterval: 1,
+            splitLine: {
+              lineStyle: { type: 'dashed', color: '#eaeaea' },
             },
-            emphasis: {
-              focus: 'series',
+            axisLine: { lineStyle: { color: '#d9d9d9' } },
+          },
+          series: [
+            {
+              type: 'bar',
+              data: yaxis,
+              barWidth: 40,
               itemStyle: {
-                color: '#4096ff',
+                color: '#69c0ff',
+                borderRadius: [6, 6, 0, 0],
+              },
+              emphasis: {
+                focus: 'series',
+                itemStyle: {
+                  color: '#4096ff',
+                },
+              },
+              label: {
+                show: true,
+                position: 'top',
+                formatter: '{c}',
               },
             },
-            label: {
-              show: true,
-              position: 'top',
-              formatter: '{c}',
-            },
-          },
-        ],
-      },
-    };
-  });
+          ],
+        },
+      };
+    })
+    .filter((config) => config !== null) as Array<{
+    questionnaireId: string;
+    questionnaireLabel: string;
+    option: any;
+  }>;
 });
 
-onMounted(() => loadStatistics());
-
 defineExpose({
   send: () => loadStatistics(selectedRange.value ?? undefined),
 });
@@ -200,39 +250,41 @@ defineExpose({
     <section class="filter-card">
       <div class="filter-item mr-10">
         <span class="label">发送日期:</span>
-        <a-range-picker v-model:value="selectedRange" allow-clear format="YYYY-MM-DD" :placeholder="['开始日期', '结束日期']" @change="handleRangeChange" />
+        <a-range-picker v-model:value="selectedRange" allow-clear format="YYYY-MM-DD" :placeholder="['开始日期', '结束日期']" />
       </div>
       <div class="filter-item">
         <span class="label">选择问卷:</span>
-        <a-select v-model:value="questionnaireId" placeholder="请选择问卷名称" style="width: 300px" mode="multiple" :labelInValue="true" >
+        <a-select v-model:value="questionnaireId" placeholder="请选择问卷名称" style="width: 300px" mode="multiple" :labelInValue="true">
           <a-select-option v-for="item in questionnaireList" :key="item.value" :value="item.value" :label="item.label">{{ item.label }}</a-select-option>
         </a-select>
       </div>
+      <div class="button-item"><a-button type="primary" @click="handleQuery">查询</a-button></div>
     </section>
 
     <section class="chart-card">
       <main>
-        <!-- <a-spin :spinning="loading"> -->
-        <template v-if="chartOptions.length > 0">
-          <div v-for="(chartConfig, index) in chartOptions" :key="`chart-${String(chartConfig.questionnaireId)}-${index}`" class="chart-wrapper">
-            <VChart class="chart" :option="chartConfig.option" autoresize />
-          </div>
-        </template>
-        <a-empty v-else description="请选择问卷查看统计" />
-        <!-- </a-spin> -->
+        <a-spin :spinning="loading">
+          <template v-if="chartOptions.length > 0">
+            <div v-for="(chartConfig, index) in chartOptions" :key="`chart-${String(chartConfig.questionnaireId)}-${index}`" class="chart-wrapper">
+              <VChart class="chart" :option="chartConfig.option" autoresize />
+            </div>
+          </template>
+          <a-empty v-else description="请选择问卷查看统计" />
+        </a-spin>
       </main>
     </section>
   </div>
 </template>
 
 <style scoped lang="scss">
+.button-item {
+  margin-left: 80px;
+}
 .statistics-page {
   padding: 24px;
   height: 100%;
   display: flex;
   flex-direction: column;
-  // gap: 16px;
-  // background: #f5f6fa;
   border: 1px solid #f0f0f0;
 }
 
@@ -241,9 +293,7 @@ defineExpose({
   border-radius: 8px;
   padding: 16px 24px;
   display: flex;
-  // justify-content: space-between;
   align-items: center;
-  // border: 1px solid #f0f0f0;
 }
 
 .filter-item {
@@ -266,7 +316,6 @@ defineExpose({
   background: #fff;
   border-radius: 8px;
   padding: 24px;
-  // border: 1px solid #f0f0f0;
   display: flex;
   flex-direction: column;
 
@@ -282,8 +331,6 @@ defineExpose({
     display: flex;
     flex-direction: column;
     gap: 24px;
-    // align-items: center;
-    // justify-content: center;
   }
 }
 
@@ -293,6 +340,7 @@ defineExpose({
   border-radius: 8px;
   padding: 16px;
   border: 1px solid #f0f0f0;
+  margin-bottom: 10px;
 }
 
 .chart-title {

+ 30 - 54
src/satisfaction/SurveyList.vue

@@ -1,15 +1,16 @@
 <script setup lang="ts">
-import { ref, reactive, shallowRef, onBeforeUnmount, onMounted } from 'vue';
+import { ref, reactive, shallowRef, onMounted } from 'vue';
 import type { SystemItemModel, SystemIteQuery } from '@/model/care.model';
+import type { SatisfactionModel } from '@/model/satisfaction.model';
 import SetQuestionnaire from '@/satisfaction/SetQuestionnaire.vue';
 import SeeSatisfaction from '@/satisfaction/SeeQuestionnaire.vue';
 import EditQuestionnaire from '@/satisfaction/EditQuestionnaire.vue';
 // 接口数据
 
-import { pageConfirmedCpMethod, deleteConfirmedCpMethod } from '@/request/api/care.api';
-import { usePagination, useRequest } from 'alova/client';
-import { notification } from 'ant-design-vue';
+import { satisfactionMethod } from '@/request/api/satisfaction.api';
+import { usePagination } from 'alova/client';
 import dayjs from 'dayjs';
+
 import { type VxeFormListeners, type VxeFormProps, type VxeGridInstance, type VxeGridListeners, type VxeGridProps, VxeUI } from 'vxe-pc-ui';
 
 const model = shallowRef<SystemIteQuery>();
@@ -17,7 +18,7 @@ const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
   titleWidth: 100,
   titleAlign: 'right',
   titleColon: true,
-  data: { types: ['1'] as any, status: '0' as any },
+  data: { name: '', status: '' },
   items: [
     {
       field: 'name',
@@ -26,7 +27,7 @@ const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
       itemRender: { name: 'VxeInput', props: { placeholder: '请输入满意度问卷名称' } },
     },
     {
-      field: 'sendTime',
+      field: 'status',
       title: '状态',
       span: 6,
       itemRender: {
@@ -62,7 +63,7 @@ const searchFormProps = reactive<VxeFormProps<SystemIteQuery>>({
   ],
 });
 const searchFormEmits: VxeFormListeners<SystemIteQuery> = {
-  // 查询随访计划
+  //查询
   submit({ data }) {
     onSearch(data);
   },
@@ -73,17 +74,10 @@ const searchFormEmits: VxeFormListeners<SystemIteQuery> = {
   },
 };
 function onSearch(data: SystemIteQuery) {
-  const types: string[] = (data as any).types?.length ? (data as any).types : ['1'];
   const status: string = (data as any).status ?? '0';
-  const isForWrap: SystemIteQuery['isForWrap'] = types.includes('1') ? 'Y' : null;
-  const isForInfer: SystemIteQuery['isForInfer'] = types.includes('2') ? 'Y' : null;
-
-  model.value = { ...data, types: [...types] as any, status: status as any, isForWrap, isForInfer };
+  model.value = { ...data, status: status as any };
   nextTick(() => {
-    (searchFormProps.data as any)!.types = [...types];
     (searchFormProps.data as any)!.status = status;
-    (searchFormProps.data as any)!.isForInfer = isForInfer;
-    (searchFormProps.data as any)!.isForWrap = isForWrap;
   });
 }
 const gridRef = ref<VxeGridInstance<SystemItemModel>>();
@@ -112,11 +106,11 @@ const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
   columns: [
     { type: 'seq', width: 70, fixed: 'left' },
     { field: 'name', title: '问卷' },
-    { field: 'conditioningProgramType', title: '通知渠道' },
-    { field: 'cpFixedPricingRule.unitPrice', title: '触发条件' },
-    { field: 'cpFixedPricingRule.pricingUnit', title: '创建时间' },
-    { field: 'cpFixedPricingRule.convertDose', title: '已发条数' },
-    { field: 'conditioningProgramSupplierName', title: '启用状态', slots: { default: 'conditioningProgramSupplierNameCell' } },
+    { field: 'pushType', title: '通知渠道', slots: { default: 'pushTypeCell' } },
+    { field: 'triggerStr', title: '触发条件' },
+    { field: 'createTime', title: '创建时间' },
+    { field: 'sendCount', title: '已发条数' },
+    { field: 'status', title: '启用状态', slots: { default: 'statusCell' } },
     {
       field: 'action',
       title: '操作',
@@ -139,8 +133,6 @@ const gridOptions = reactive<VxeGridProps<SystemItemModel>>({
               method = seeDetail;
             } else if (name === 'setQuestionnaire') {
               method = setQuestionnaire;
-            } else if (name === 'deleteConfirmed') {
-              method = deleteConfirmed;
             }
             method?.(row, rowIndex);
           },
@@ -162,7 +154,7 @@ const {
   refresh,
   remove,
   send: sendRefresh,
-} = usePagination((page, size) => pageConfirmedCpMethod(page, size, model.value), {
+} = usePagination((page, size) => satisfactionMethod(page, size, model.value), {
   initialData: { data: [], total: 0 },
   initialPage: 1,
   initialPageSize: 100,
@@ -176,23 +168,6 @@ onMounted(() => {
   onSearch(toRaw(searchFormProps.data) as any);
 });
 
-function deleteConfirmed(model: SystemItemModel, index: number) {
-  const { name } = model;
-  VxeUI.modal.confirm({
-    title: `删除项目`,
-    content: `确认要删除 ${name} 项目吗?`,
-    showClose: false,
-    onConfirm() {
-      deleteConfirmedCpMethod(model).then(() => {
-        notification.success({
-          message: `删除项目: ${name}`,
-          description: '操作成功',
-        });
-        refresh(page.value);
-      });
-    },
-  });
-}
 // 新增满意度问卷
 function editQuestionnaire() {
   VxeUI.modal.open({
@@ -203,13 +178,15 @@ function editQuestionnaire() {
     id: `edit-questionnaire-modal`,
     slots: {
       default() {
-        return h(EditQuestionnaire
-        , <any>{
+        return h(EditQuestionnaire, <any>{
           data: {},
-          onSubmit(data: SystemItemModel) {
+          onSubmit(data: Partial<SatisfactionModel['satisfiesyContent']>) {
             refresh(page.value);
             VxeUI.modal.close(`edit-questionnaire-modal`);
           },
+          back() {
+            VxeUI.modal.close(`edit-questionnaire-modal`);
+          },
         });
       },
     },
@@ -217,25 +194,21 @@ function editQuestionnaire() {
 }
 // 设置满意度问卷
 function setQuestionnaire(model?: SystemItemModel, index?: number) {
-  const addType = `itemsList`;
   VxeUI.modal.open({
-    title: model?.conditioningProgramType ?? '项目',
+    title: model?.name ?? '满意度问卷',
     fullscreen: true,
     escClosable: true,
     destroyOnClose: true,
-    id: `edit-notify-modal`,
+    id: `set-satisfaction-modal`,
     remember: true,
     storage: true,
     slots: {
       default() {
         return h(SetQuestionnaire, <any>{
-          data: {
-            ...model,
-            addType,
-          },
-          onSubmit(data: SystemItemModel) {
+          data: model,
+          onSubmit(data: SatisfactionModel) {
             refresh(page.value);
-            VxeUI.modal.close(`edit-notify-modal`);
+            VxeUI.modal.close(`set-satisfaction-modal`);
           },
         });
       },
@@ -288,8 +261,11 @@ defineExpose({
     </header>
     <main class="flex-auto overflow-hidden">
       <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents" :loading="loading">
-        <template #conditioningProgramSupplierNameCell="{ row }">
-          {{ row.conditioningProgramSupplierName == '0' ? '启用' : '禁用' }}
+        <template #statusCell="{ row }">
+          {{ row.status == '0' ? '启用' : '停用' }}
+        </template>
+        <template #pushTypeCell="{ row }">
+          {{ row.pushType == '0' ? '站内' : '企业微信' }}
         </template>
         <template #toolbar-extra>
           <vxe-button style="margin-right: 12px" icon="vxe-icon-repeat" circle @click="refresh(page)"></vxe-button>

+ 11 - 27
src/service/NotifyManageList.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import { ref, reactive, shallowRef, onBeforeUnmount, onMounted } from 'vue';
+import { ref, reactive, shallowRef, onMounted, nextTick } from 'vue';
 import type { NotifyModel, NotifyQuery } from '@/model/notify.model';
 import EditNotify from '@/components/EditNotify.vue';
 // 接口数据
 
 import { notifyMethod, deleteNotifyMethod, updateNotifyStatusMethod } from '@/request/api/notify.api';
-import { usePagination, useRequest } from 'alova/client';
+import { usePagination } from 'alova/client';
 import { notification } from 'ant-design-vue';
 import dayjs from 'dayjs';
 import { type VxeFormListeners, type VxeFormProps, type VxeGridInstance, type VxeGridListeners, type VxeGridProps, VxeUI } from 'vxe-pc-ui';
@@ -53,7 +53,7 @@ const searchFormProps = reactive<VxeFormProps<NotifyQuery>>({
   ],
 });
 const searchFormEmits: VxeFormListeners<NotifyQuery> = {
-  // 查询随访计划
+  // 查询
   submit({ data }) {
     data.sendTimeStart = sendTimeStart.value ? dayjs(sendTimeStart.value).format('YYYY-MM-DD HH:mm') : '';
     data.sendTimeEnd = sendTimeEnd.value ? dayjs(sendTimeEnd.value).format('YYYY-MM-DD HH:mm') : '';
@@ -62,11 +62,16 @@ const searchFormEmits: VxeFormListeners<NotifyQuery> = {
 
   // 重置
   reset({ data }) {
-    onSearch(data);
+    // 清空开始时间和结束时间
+    sendTimeStart.value = '';
+    sendTimeEnd.value = '';
+    const resetData = { ...data };
+    resetData.sendTimeStart = '';
+    resetData.sendTimeEnd = '';
+    onSearch(resetData);
   },
 };
 function onSearch(data: NotifyQuery) {
-
   model.value = { ...data };
   nextTick(() => {
     (searchFormProps.data as any)! = { ...data };
@@ -85,7 +90,6 @@ const gridOptions = reactive<VxeGridProps<NotifyModel>>({
     custom: true,
     zoom: true,
     slots: {
-      // buttons: 'handle',
       tools: 'toolbar-extra',
     },
   },
@@ -134,16 +138,13 @@ const gridOptions = reactive<VxeGridProps<NotifyModel>>({
           mode: 'text',
         },
         options: [
-          { content: '查看', status: 'primary', name: 'seeDetail' },
           { content: '编辑', status: 'primary', name: 'editConfirmed' },
           { content: '删除', status: 'primary', name: 'deleteConfirmed' },
         ],
         events: {
           click({ row, rowIndex }: any, { name }: any) {
             let method;
-            if (name === 'seeDetail') {
-              method = seeDetail;
-            } else if (name === 'editConfirmed') {
+            if (name === 'editConfirmed') {
               method = editConfirmed;
             } else if (name === 'deleteConfirmed') {
               method = deleteConfirmed;
@@ -245,23 +246,6 @@ function updatePlanStatus(model: NotifyModel, index: number, status: NotifyModel
     },
   });
 }
-function seeDetail(model?: NotifyModel, index?: number) {
-  // VxeUI.modal.open({
-  //   title: model?.name,
-  //   height: 500,
-  //   width: 750,
-  //   id: `see-notify-modal`,
-  //   remember: true,
-  //   storage: true,
-  //   slots: {
-  //     default() {
-  //       return h(SeeNotify, <any>{
-  //         data: model,
-  //       });
-  //     },
-  //   },
-  // });
-}
 // 日期验证
 const sendTimeStart = ref<string>('');
 const sendTimeEnd = ref<string>('');

+ 10 - 6
src/service/NotifyManageRecord.vue

@@ -2,7 +2,6 @@
 import type { NotifyRecordModel, NotifyRecordQuery } from '@/model/notify.model';
 import dayjs from 'dayjs';
 // 接口数据
-
 import { getNotifyRecordMethod } from '@/request/api/notify.api';
 import { usePagination } from 'alova/client';
 
@@ -56,7 +55,7 @@ const searchFormProps = reactive<VxeFormProps<NotifyRecordQuery>>({
   ],
 });
 const searchFormEmits: VxeFormListeners<NotifyRecordQuery> = {
-  // 查询随访计划
+  // 查询
   submit({ data }) {
     data.sendTimeStart = sendTimeStart.value ? dayjs(sendTimeStart.value).format('YYYY-MM-DD HH:mm') : '';
     data.sendTimeEnd = sendTimeEnd.value ? dayjs(sendTimeEnd.value).format('YYYY-MM-DD HH:mm') : '';
@@ -65,11 +64,19 @@ const searchFormEmits: VxeFormListeners<NotifyRecordQuery> = {
 
   // 重置
   reset({ data }) {
-    onSearch(data);
+    sendTimeStart.value = '';
+    sendTimeEnd.value = '';
+    const resetData = { ...data };
+    resetData.sendTimeStart = '';
+    resetData.sendTimeEnd = '';
+    onSearch(resetData);
   },
 };
 function onSearch(data: NotifyRecordQuery) {
   model.value = { ...data };
+  nextTick(() => {
+    (searchFormProps.data as any)! = { ...data };
+  });
 }
 const gridRef = ref<VxeGridInstance<NotifyRecordModel>>();
 const gridOptions = reactive<VxeGridProps<NotifyRecordModel>>({
@@ -96,7 +103,6 @@ const gridOptions = reactive<VxeGridProps<NotifyRecordModel>>({
   columns: [
     { type: 'seq', width: 70, fixed: 'left' },
     { field: 'noticeName', title: '名称' },
-    { field: 'patientName', title: '用户ID' },
     { field: 'patientName', title: '用户姓名' },
     { field: 'phone', title: '手机号码' },
     { field: 'sendTime', title: '发送时间' },
@@ -112,9 +118,7 @@ const {
   pageSize,
   total,
   onSuccess,
-  replace,
   refresh,
-  remove,
   send: sendRefresh,
 } = usePagination((page, size) => getNotifyRecordMethod(page, size, model.value), {
   initialData: { data: [], total: 0 },