소스 검색

feat:修改数据源,度量,维度数据逻辑结构,增加草稿自动保存,模板保存,增加公共维度

韩洋 7 달 전
부모
커밋
9a1f8966bc
27개의 변경된 파일960개의 추가작업 그리고 305개의 파일을 삭제
  1. 56 9
      src/base/store/use-report-editor.js
  2. 4 3
      src/modules/chat-item/DialogOutlineActions.vue
  3. 8 1
      src/modules/conversation-chat/DialogInput.vue
  4. 31 4
      src/modules/conversation-dialog/api-template.js
  5. 3 3
      src/modules/conversation-dialog/format-html-md-switch-for-report.js
  6. 122 11
      src/modules/report-template/TemplateDetail.vue
  7. 69 3
      src/modules/report-template/TemplateEditor.vue
  8. 1 1
      src/modules/report-template/common-dimension/DimensionItem.vue
  9. 141 60
      src/modules/report-template/editor-model/CommonDimension.vue
  10. 22 10
      src/modules/report-template/editor-model/EditorDetail.vue
  11. 120 57
      src/modules/report-template/editor-model/EditorTitleHeader.vue
  12. 2 0
      src/modules/report-template/editor-model/SaveTemplateModel.vue
  13. 10 5
      src/modules/report-template/editor-model/TipsModel.vue
  14. 8 9
      src/modules/report-template/editor-select/MentionDropdown.vue
  15. 6 2
      src/modules/report-template/filter-model/DimensionItem.vue
  16. 37 8
      src/modules/report-template/filter-model/MentionFilter.vue
  17. 81 17
      src/modules/report-template/filter-model/model/DualListSelector.vue
  18. 47 22
      src/modules/report-template/filter-model/model/StringModel.vue
  19. 42 8
      src/modules/report-template/source-model/DataSource.vue
  20. 9 1
      src/modules/report-template/source-model/DimensionSource.vue
  21. 9 10
      src/modules/report-template/source-model/MetricsSource.vue
  22. 12 1
      src/modules/report-template/template-detail/EditorModel.vue
  23. 11 1
      src/modules/report-template/template-detail/FilterModel.vue
  24. 31 46
      src/modules/report-template/template-detail/SourceModel.vue
  25. 16 8
      src/modules/report/Editor.vue
  26. 57 5
      src/modules/template-manage/template/TemplateDetailBody.vue
  27. 5 0
      src/views/conversation/ConversationChatContainer.vue

+ 56 - 9
src/base/store/use-report-editor.js

@@ -8,14 +8,36 @@ export const useReportEditor = defineStore('report-editor', {
   state: () => {
     return {
       metrics_list: [], // 编辑器中添加到指标数据
+      common_config: {
+        common_start_time: '', // 公共维度-时间范围-开始时间
+        common_end_time: '', // 公共维度-时间范围-结束时间
+        common_area: '', // 公共维度-区域
+      }, // 公共维度配置放在这里
+      existing_match_list: [],// 存在的度量数据,用于匹配富文本中的指标
       target_metric_id: null, // 当前选中的指标数据
       target_report_markdown: '',// 当前富文本中内容md格式
       target_template_id: '', // 当前编辑的模板id
+      is_need_save: false, // 判断富文本及过滤器中是否有指标要修改,若有则为true,否则为false
+      is_need_save_template: false, // 上同,区分为模板
       // metrics_list_metrics_configs: []  //
       // is_need_save: false, // 判断当前指标中中间过滤器组件,是否有维度或者过滤器需要保存,若有则为true,否则为false
     };
   },
   actions: {
+    setCommonConfig(key, value) {
+      this.common_config[key] = value;
+    },
+    // 更新是否需要保存
+    setIsNeedSave(status) {
+      this.is_need_save = status;
+    },
+    setIsNeedSaveTemplate(status) {
+      this.is_need_save_template = status;
+    },
+    // 更新存在的度量数据,用于匹配富文本中的指标
+    setExistingMatchList(list) {
+      this.existing_match_list = list;
+    },
     // 更新当前富文本中内容md格式
     setTargetReportMarkdown(md) {
       this.target_report_markdown = md;
@@ -112,9 +134,9 @@ export const useReportEditor = defineStore('report-editor', {
       const map_item = { ...item }
       map_item.index = item.uid;
       map_item.metric_base_info = {
-        "metric_name": item.option.metric_name || '',
-        "metric_id": item.option.id,
-        "data_source_type": item.option.data_source_type / 1 === 1 ? "api" : 'db'
+        "metric_name": item.option.source.metric_name || '',
+        "metric_id": item.option.source.id,
+        "data_source_type": item.option.source.data_source_type / 1 === 1 ? "api" : 'db'
       }
 
       if (item.metric_config) {
@@ -131,10 +153,20 @@ export const useReportEditor = defineStore('report-editor', {
       }
 
       try {
-        map_item.api_config = JSON.parse(item.option.api_config) || {}
-        map_item.metric_config.sort_field_key = map_item.api_config.metric_main_field || ''
-        map_item.metric_config.default_metric_field_key = map_item.api_config.metric_main_field || ''
-        map_item.metric_config.format_field_key = map_item.api_config.metric_main_field || ''
+        map_item.api_config = JSON.parse(item.option.source.api_config) || {}
+        const need_field = { name: item.option.field_key, show_name: item.option.field_name }
+        if (item.option.filter_config) {
+          let filter_config = JSON.parse(item.option.filter_config)
+          if (filter_config.field_from_table) {
+            need_field.from_table = filter_config.field_from_table
+          }
+        }
+        map_item.api_config.need_fields.push(need_field)
+        // map_item.api_config.need_fields.push({ name: 11111111, show_name: 2222222222 })
+        console.log('map_item.api_config', map_item)
+        map_item.metric_config.sort_field_key = map_item.option.field_key || ''
+        map_item.metric_config.default_metric_field_key = map_item.option.field_key || ''
+        map_item.metric_config.format_field_key = map_item.option.field_key || ''
       } catch (e) {
         map_item.api_config = {}
       }
@@ -163,7 +195,7 @@ export const useReportEditor = defineStore('report-editor', {
           } else {
             item.aggs_config = {
               aggs_type: 'sum',
-              aggs_field_key: item.api_config.metric_main_field,
+              aggs_field_key: item.option.field_key,
               group_by_field_key: [],
             }
             item.aggs_config.group_by_field_key = [...dimension_list.map(it => it.field_key)]
@@ -181,7 +213,7 @@ export const useReportEditor = defineStore('report-editor', {
           } else {
             item.aggs_config = {
               aggs_type: 'sum',
-              aggs_field_key: item.api_config.metric_main_field,
+              aggs_field_key: item.option.field_key,
               group_by_field_key: [dimension_item.field_key],
             }
           }
@@ -226,6 +258,21 @@ export const useReportEditor = defineStore('report-editor', {
     getTargetMd: (state) => {
       return state.target_report_markdown
     },
+    // 获取当前富文本中已存在的指标
+    QueryExistingMatchList: (state) => {
+      return state.existing_match_list || []
+    },
+    // 查询是否需要保存
+    QueryIsNeedSave: (state) => {
+      return state.is_need_save
+    },
+    // 查询是否需要保存模板
+    QueryIsNeedSaveTemplate: (state) => {
+      return state.is_need_save_template
+    },
+    QueryCommonConfig: (state) => {
+      return state.common_config
+    },
 
   },
 });

+ 4 - 3
src/modules/chat-item/DialogOutlineActions.vue

@@ -5,8 +5,8 @@
       <div classs="tips-text">{{ tips }}</div>
       <div class="tools">
         <a-space>
-          <a-button @click="toUpdate">修改</a-button>
-          <a-button type="primary" @click="handleConfirm">确认</a-button>
+          <a-button type="primary" @click="toUpdate">确认</a-button>
+          <!-- <a-button type="primary" @click="handleConfirm">确认</a-button> -->
         </a-space>
       </div>
     </div>
@@ -18,7 +18,8 @@ import { ref } from 'vue';
 
 const emit = defineEmits(['update', 'confirm']);
 
-const tips = ref('请确认上述报告大纲是否符合您的需求');
+const tips = ref('请根据报告大纲添加相关指标配置');
+// const tips = ref('请确认上述报告大纲是否符合您的需求');
 
 const toUpdate = () => {
   emit('update');

+ 8 - 1
src/modules/conversation-chat/DialogInput.vue

@@ -405,6 +405,8 @@ const openTemplateManage = () => {
 // 处理编辑模板事件
 const handleEditTemplate = (record) => {
   emit('edit-template', record);
+  show_template_select_model.value = false;
+  show_draft_model.value = false;
 };
 
 /**
@@ -461,6 +463,10 @@ const handleTextKeyUp = (e) => {
     }
   }
 };
+const initInput = () => {
+  is_template_input.value = true;
+  show_template_input.value = false;
+};
 
 watch(
   () => upload_file_list.value,
@@ -486,7 +492,8 @@ onUnmounted(() => {
 });
 
 defineExpose({
-  clear: clear
+  clear: clear,
+  initInput: initInput
 });
 </script>
 

+ 31 - 4
src/modules/conversation-dialog/api-template.js

@@ -67,13 +67,13 @@ export const getTemplateListByGroupId = ({ group_id, page, size, type = 1, filte
   })
 }
 // 新增模板
-export const addTemplate = async ({ group_id, name, remark, content, template_config, type = 1, status = 0 }) => {
+export const addTemplate = async ({ group_id, name, remark, content, template_config, common_config, type = 1, status = 0 }) => {
   const creator = (await getUserName() || '').toLowerCase()
   return new Promise((resolve, reject) => {
     ajax.post(
       '/iaserverapi/v1/report/flexibleTemplate/template/add',
       {
-        name, remark, creator, content, group_id, status, template_config, type
+        name, remark, creator, content, group_id, status, template_config, type, common_config
       },
       {
         headers: {
@@ -178,9 +178,9 @@ export const deleteTemplateItem = async ({ template_id_list }) => {
   })
 }
 // 编辑模板
-export const editTemplateItem = async ({ id, name, remark, status, content, template_config, type = 1 }) => {
+export const editTemplateItem = async ({ id, name, remark, status, content, template_config, common_config, type = 1 }) => {
   const data = Object.fromEntries(
-    Object.entries({ id, name, remark, status, content, template_config, type }).filter(([key, value]) => {
+    Object.entries({ id, name, remark, status, content, template_config, common_config, type }).filter(([key, value]) => {
       if (key === 'status') return value !== undefined;
       return value;
     })
@@ -226,3 +226,30 @@ export const checkTemplateDetail = async ({ id }) => {
   })
 }
 
+
+
+// /iaserverapi/v1/report/flexibleTemplate/front/template/enum/info
+
+// 获取地区枚举值
+export const getRegionEnum = async ({ enum_type_id }) => {
+
+
+  return new Promise((resolve, reject) => {
+    ajax.post(
+      '/iaserverapi/v1/report/flexibleTemplate/front/template/enum/info',
+      {
+        enum_type_id
+      },
+      {
+        headers: {
+          'Content-Type': 'application/json'
+        }
+      }
+    ).then((res) => {
+      resolve({ code: res.code, data: res.data });
+    })
+      .catch((err) => {
+        reject(err);
+      });
+  })
+}

+ 3 - 3
src/modules/conversation-dialog/format-html-md-switch-for-report.js

@@ -45,7 +45,8 @@ export const formatMdToHtml = (exist_metrics, md) => {
 
 
   // 创建存在的指标名称集合,用于快速查找
-  const existMetricNames = new Set(exist_metrics.map(metric => metric.metric_name));
+  const existMetricNames = new Set(exist_metrics.map(metric => metric.field_name));
+  console.log(existMetricNames, 999)
 
   // 先解析markdown为html
   let html = marked.parse(md);
@@ -79,9 +80,8 @@ export const getExistMetrics = (exist_metrics, md) => {
 
   while ((match = regex.exec(md)) !== null) {
     const [, metricName, uid] = match;
-
     // 查找exist_metrics中匹配的指标
-    const matchedMetric = exist_metrics.find(metric => metric.metric_name === metricName);
+    const matchedMetric = exist_metrics.find(metric => metric.field_name === metricName);
     if (matchedMetric) {
       metrics.push({
         uid,

+ 122 - 11
src/modules/report-template/TemplateDetail.vue

@@ -4,7 +4,8 @@
       <EditorModel
         ref="editorModel"
         :style="StyleEditor"
-        :metrics_list="metrics_list"
+        :metrics_list="existingMatchList"
+        :dimensions_list="dimensions_list"
         :extracted_metrics="extracted_metrics"
         :render_html="render_html"
         :template_name="template_name"
@@ -18,26 +19,37 @@
         @to-save-setting="handleToSaveSetting"
         @submit-html="handleSubmitHtml"
         @save-template="handleSaveTemplate"
+        @content-changed="handleContentChanged"
+        @update-draft="handleUpdateDraft"
       ></EditorModel>
-      <FilterModel v-if="show_filter_model" ref="filterModelRef" :filter_option="target_filter_option"></FilterModel>
+      <FilterModel
+        v-if="show_filter_model"
+        ref="filterModelRef"
+        :filter_option="target_filter_option"
+        @content-changed="handleContentChanged"
+      ></FilterModel>
     </div>
     <div class="body-right">
       <SourceModel
+        :target_source_id="target_source_id"
+        :data_source_list="data_source_list"
         :metrics_list="metrics_list"
+        :dimensions_list="dimensions_list"
         :is_metrics_loading="is_metrics_loading"
-        :target_filter_option="target_filter_option"
         @metrics-click="handleMetricsClick"
+        @change-source="handleChangeSource"
       ></SourceModel>
     </div>
   </div>
 </template>
 
 <script setup >
-import { ref, computed, onMounted, nextTick } from 'vue';
+import { ref, computed, onMounted, nextTick, watch } from 'vue';
 import EditorModel from './template-detail/EditorModel.vue';
 import FilterModel from './template-detail/FilterModel.vue';
 import SourceModel from './template-detail/SourceModel.vue';
 import { getMetrics } from '../conversation-dialog/api-template';
+import { funcThrottle, funcDebounce } from '../../utils/utils';
 import {
   formatHtmlToMd,
   formatMdToHtml,
@@ -75,12 +87,16 @@ const props = defineProps({
   }
 });
 
-const emit = defineEmits(['save-template']);
+const emit = defineEmits(['save-template', 'update-draft']);
 
 const show_filter_model = ref(false);
 const target_filter_option = ref(null);
 const is_metrics_loading = ref(false); // 指标列表加载中
-const metrics_list = ref([]); // 指标列表
+const data_source_list = ref([]); // 数据源列表
+const metrics_list = ref([]); // 度量列表
+const dimensions_list = ref([]); // 维度列表
+const target_source_id = ref('');
+
 const extracted_metrics = ref([]);
 
 const render_html = ref('');
@@ -100,11 +116,16 @@ const StyleEditor = computed(() => {
   };
 });
 
+const existingMatchList = computed(() => {
+  return reportEditor.QueryExistingMatchList;
+});
 const handleChange = () => {
   show_filter_model.value = !show_filter_model.value;
 };
 
 const handleMentionClick = (option) => {
+  console.log(option, 890);
+  target_source_id.value = option.option.source.id || '';
   target_filter_option.value = null;
   nextTick(() => {
     target_filter_option.value = option;
@@ -134,7 +155,19 @@ const getMetricsFun = async () => {
     const res = await getMetrics({ metric_name: '', page: 1, size: 100000 });
     console.log(res);
     if (res.code / 1 === 200) {
-      metrics_list.value = res.data || [];
+      data_source_list.value = res.data;
+      target_source_id.value = data_source_list.value[0]?.id;
+      if (data_source_list.value.length > 0) {
+        const flexible_template_metric_fields = data_source_list.value[0].flexible_template_metric_fields;
+        if (flexible_template_metric_fields.length) {
+          setDataToMetricsAndDimension(flexible_template_metric_fields, data_source_list.value[0]);
+        }
+      }
+      // 获取所有数据源中的度量数据
+      const all_metrics = extractMeasMetrics(data_source_list.value);
+      // console.log('all_metrics', all_metrics);
+      reportEditor.setExistingMatchList(all_metrics);
+      // console.log(all_metrics);
     }
   } catch (error) {
     console.log(error);
@@ -143,11 +176,39 @@ const getMetricsFun = async () => {
   }
 };
 
+const setDataToMetricsAndDimension = (flexible_template_metric_fields, orgin_source) => {
+  metrics_list.value = flexible_template_metric_fields.filter((item) => item.column_role === 'meas');
+  metrics_list.value = metrics_list.value.map((item) => ({
+    ...item,
+    source: orgin_source
+  }));
+  dimensions_list.value = flexible_template_metric_fields.filter((item) => item.column_role === 'dim');
+};
+const extractMeasMetrics = (data) => {
+  return data
+    .flatMap((item) => {
+      return item.flexible_template_metric_fields.map((field) => ({
+        ...field,
+        source: item
+      }));
+    })
+    .filter((field) => field.column_role === 'meas');
+};
+
 const handleToSaveSetting = () => {
   if (filterModelRef.value) {
     filterModelRef.value.saveSettingToStore();
   }
 };
+// 切换数据源时,更新指标和维度列表
+const handleChangeSource = (id) => {
+  data_source_list.value.forEach((item) => {
+    if (item.id === id) {
+      setDataToMetricsAndDimension(item.flexible_template_metric_fields, item);
+    }
+  });
+  show_filter_model.value = false;
+};
 const handleSubmitHtml = (html) => {
   submit_html.value = html;
   const md = formatHtmlToMd(submit_html.value);
@@ -158,13 +219,63 @@ const handleSaveTemplate = (data) => {
   emit('save-template', data);
 };
 
-onMounted(async () => {
-  await getMetricsFun();
-  render_html.value = formatMdToHtml(metrics_list.value, props.md_data);
-  const extract_metrics = getExistMetrics(metrics_list.value, props.md_data);
+const handleContentChanged = funcDebounce(() => {
+  console.log('需要各项');
+  if (props.template_id) {
+    reportEditor.setIsNeedSaveTemplate(true);
+  } else {
+    reportEditor.setIsNeedSave(false);
+    nextTick(() => {
+      reportEditor.setIsNeedSave(true);
+    });
+  }
+}, 5000);
+
+const handleUpdateDraft = (data) => {
+  emit('update-draft', data);
+};
+
+// 标记 getMetricsFun 是否完成
+const isMetricsLoaded = ref(false);
+
+// 执行渲染逻辑
+const performRender = () => {
+  const existing_match_list = reportEditor.QueryExistingMatchList;
+  render_html.value = formatMdToHtml(existing_match_list, props.md_data);
+  console.log(props.md_data, 99999999);
+  console.log(existing_match_list, render_html.value, 99999999999);
+  const extract_metrics = getExistMetrics(existing_match_list, props.md_data);
   extracted_metrics.value = extract_metrics || [];
   console.log('xxxxx', extracted_metrics.value);
   reportEditor.addMetric(extract_metrics);
+};
+
+// 检查是否可以执行渲染
+const checkRenderReady = () => {
+  // 只有当 getMetricsFun 完成且模板详情不在加载中时,才执行渲染
+  if (isMetricsLoaded.value && !props.is_loading_template_detail) {
+    performRender();
+  }
+};
+
+// 监听模板详情加载状态变化
+watch(
+  () => props.is_loading_template_detail,
+  () => {
+    // 状态变化时检查是否可以渲染
+    checkRenderReady();
+  }
+);
+
+onMounted(async () => {
+  // 总是调用 getMetricsFun
+  await getMetricsFun();
+  isMetricsLoaded.value = true;
+
+  // 检查是否可以立即渲染
+  checkRenderReady();
+
+  // 如果模板详情还在加载中,等待 watch 监听状态变化后再渲染
 });
 </script>
 <style scoped lang="css">

+ 69 - 3
src/modules/report-template/TemplateEditor.vue

@@ -10,9 +10,11 @@
         :template_save_time="template_save_time"
         :is_loading_template_detail="is_loading_template_detail"
         @save-template="handleSaveTemplate"
+        @update-draft="handleUpdateDraft"
       ></TemplateDetail>
     </div>
   </a-modal>
+  <TipsModel v-model:visible="showTips" :loading="is_loading_edit" @ok="toSaveSetting" @cancel="toNotSaveSetting" />
 </template>
 
 <script setup lang="js">
@@ -20,10 +22,11 @@ import TemplateHeader from './TemplateHeader.vue'
 import TemplateDetail from './TemplateDetail.vue'
 import { ref,computed,watch } from 'vue'
 import {str_md} from './moke.js'
-import {checkTemplateDetail} from '../conversation-dialog/api-template.js'
+import {checkTemplateDetail,editTemplateItem} from '../conversation-dialog/api-template.js'
 import { useReportEditor } from '../../base/store/use-report-editor';
 import {formatDateTime} from '../../utils/utils.js'
-
+import TipsModel  from './editor-model/TipsModel.vue'
+import { Message } from '@arco-design/web-vue';
 
 const reportEditor = useReportEditor();
 
@@ -39,11 +42,15 @@ const props = defineProps({
 })
 const template_content = ref(props.target_outline_md||'')
 const template_config = ref([])
+const common_config = ref({})
+
 const template_name = ref('')
 const template_id = ref('')  // 模板id,用于保存模板
 const draft_id = ref('') // 草稿id,用户编辑草稿时使用
 const template_save_time = ref('')
 const is_loading_template_detail = ref(false)
+const showTips = ref(false)
+const is_loading_edit = ref(false)
 
 const emit = defineEmits(['toBackHome']);
 
@@ -63,15 +70,21 @@ const getTemplateDetail = async () => {
         if(res.data.template_config){
           template_config.value = JSON.parse(res.data.template_config)
         }
-        console.log(JSON.parse(res.data.template_config),'"模板配置"')
+        if(res.data.common_config){
+         common_config.value = JSON.parse(res.data.common_config)
+        }
         template_name.value = res.data.name
         template_save_time.value =formatDateTime(res.data.updated_at) || ''
+        // common_config.value
         if(res.data.type/1 === 1){
           template_id.value = res.data.id
         }else {
           draft_id.value = res.data.id
         }
         reportEditor.setMetricsList(template_config.value)
+        for(const key in common_config.value) {
+          reportEditor.setCommonConfig(key,common_config.value[key])
+        }
       }
     }catch(e) {
       console.log(e)
@@ -87,15 +100,68 @@ const handleSaveTemplate = (data) => {
   template_save_time.value = formatDateTime(data.created_at) || ''
 }
 
+const handleUpdateDraft = (data) => {
+  // console.log(data,'保存草稿')
+  draft_id.value = data.id
+  template_name.value = data.name
+  template_save_time.value = formatDateTime(data.created_at) || ''
+}
+
 const toBack = () => {
+  const is_need_save_template = reportEditor.QueryIsNeedSaveTemplate
+  if(is_need_save_template && template_id.value) {
+    // ElMessage.warning('当前模板有未保存的内容,是否确认放弃?')
+    showTips.value = true
+  } else {
+    emit('toBackHome');
+  }
+  // emit('toBackHome');
+}
+const toSaveSetting = async () => {
+  const res = await editTemplateFunc(template_id.value)
+  if(res.code/1 === 200) {
+    Message.success('保存成功')
+    showTips.value = false
+    template_save_time.value = formatDateTime(res.data.updated_at) || ''
+    reportEditor.setIsNeedSaveTemplate(false)
+    emit('toBackHome');
+  }
+}
+
+const toNotSaveSetting = () => {
+  showTips.value = false
+  reportEditor.setIsNeedSaveTemplate(false)
   emit('toBackHome');
 }
+const editTemplateFunc = async (id) => {
+  try {
+    is_loading_edit.value = true
+    const res = await editTemplateItem({
+      id,
+      name: template_name.value,
+      content: reportEditor.target_report_markdown,
+      template_config: JSON.stringify(reportEditor.metrics_list),
+      common_config: JSON.stringify(common_config.value)
+    });
+    if (res.code / 1 === 200) {
+      return res;
+    } else {
+      Message.error('保存失败');
+    }
+  } catch (error) {
+    Message.error('保存失败');
+  } finally {
+    is_loading_edit.value = false
+  }
+};
 watch(()=>props.target_template_id, () => {
   if(props.target_template_id) {
     getTemplateDetail();
   }
 }, {immediate: true})
 
+
+
 </script>
 <style scoped lang="css">
 .container {

+ 1 - 1
src/modules/report-template/common-dimension/DimensionItem.vue

@@ -4,7 +4,7 @@
     <span>{{ dimension_item.field_name }}</span>
     <icon-delete class="item-end-icon" @click.stop="handleClick(dimension_item)" />
   </div>
-  <DeleteModal :visible="show_delete_modal" @ok="handleConfirm" @cancel="handleCancel"></DeleteModal>
+  <!-- <DeleteModal :visible="show_delete_modal" @ok="handleConfirm" @cancel="handleCancel"></DeleteModal> -->
 </template>
 
 <script setup >

+ 141 - 60
src/modules/report-template/editor-model/CommonDimension.vue

@@ -1,74 +1,123 @@
 <template>
   <div class="common-dimension">
     <div class="common-dimension-items">
-      <DimensionItem
-        v-for="item in dimension_items"
-        :key="item.id"
-        :dimension_item="item"
-        @remove="handleRemove"
-        @click="handleItemClick"
-      ></DimensionItem>
+      <div class="item">
+        <span>时间范围:</span>
+        <a-range-picker
+          showTime
+          style="width: 380px"
+          :time-picker-props="{ defaultValue: '00:00:00' }"
+          format="YYYY-MM-DD HH:mm:ss"
+          v-model="time_range"
+          @change="handleTimeRangeChange"
+        >
+          <template #suffix-icon> <icon-caret-down class="item-end-icon" /> </template>
+          <template #prefix>
+            <img class="icon_prefix" :src="DimensionDate" alt="date" />
+          </template>
+        </a-range-picker>
+        <!-- <a-date-picker
+          v-model="common_start_time"
+          style="width: '120px'"
+          show-time
+          :time-picker-props="{ defaultValue: '00:00:00' }"
+          format="YYYY-MM-DD HH:mm:ss"
+        >
+          <template #suffix-icon> <icon-caret-down class="item-end-icon" /> </template>
+          <template #prefix>
+            <img class="icon_prefix" :src="DimensionDate" alt="date" />
+          </template>
+        </a-date-picker>
+        <span>{{ ' &nbsp; &nbsp;-&nbsp; &nbsp; ' }}</span>
+        <a-date-picker
+          v-model="common_end_time"
+          show-time
+          :time-picker-props="{ defaultValue: '00:00:00' }"
+          format="YYYY-MM-DD HH:mm:ss"
+        >
+          <template #suffix-icon> <icon-caret-down class="item-end-icon" /> </template>
+          <template #prefix>
+            <img class="icon_prefix" :src="DimensionDate" alt="date" />
+          </template>
+        </a-date-picker> -->
+      </div>
+      <div class="item">
+        <span>数据范围:</span>
+        <a-select
+          v-model="common_area"
+          @change="handleAreaChange"
+          :style="{ width: '200px' }"
+          placeholder="请选择数据范围"
+        >
+          <a-option v-for="item in options" :key="item.value" :value="item.value">{{ item.display_name }}</a-option>
+        </a-select>
+      </div>
     </div>
-    <div class="common-dimension-tips" v-if="dimension_items.length === 0">+请拖拽维度字段到此处</div>
   </div>
 </template>
 
 <script setup>
 import { ref, watch, computed, onMounted, nextTick } from 'vue';
-import DimensionItem from '../common-dimension/DimensionItem.vue';
+import DimensionDate from '../../../assets/report-template/diemension_date.svg';
+import { getRegionEnum } from '../../../modules/conversation-dialog/api-template.js';
+import { useReportEditor } from '../../../base/store/use-report-editor.js';
 
-const dimension_items = ref([
-  {
-    id: 1,
-    field_name: '维度1',
-    field_type: 'str'
-  },
-  {
-    id: 2,
-    field_name: '维度2',
-    field_type: 'str'
-  },
-  {
-    id: 3,
-    field_name: '维度3',
-    field_type: 'str'
-  },
-  {
-    id: 4,
-    field_name: '维度4',
-    field_type: 'str'
-  },
-  {
-    id: 5,
-    field_name: '维度5',
-    field_type: 'str'
-  },
-  {
-    id: 6,
-    field_name: '维度6',
-    field_type: 'str'
-  },
-  {
-    id: 7,
-    field_name: '维度7',
-    field_type: 'str'
-  },
-  {
-    id: 8,
-    field_name: '维度8',
-    field_type: 'str'
-  },
-  {
-    id: 9,
-    field_name: '维度9',
-    field_type: 'str'
+const reportEditor = useReportEditor();
+const emit = defineEmits(['content-changed']);
+
+// const common_start_time = ref('');
+// const common_end_time = ref('');
+const time_range = ref([]);
+const common_area = ref('');
+
+const options = ref([]);
+
+const getRegionEnumFun = async () => {
+  const res = await getRegionEnum({
+    enum_type_id: '1'
+  });
+  if (res.code / 1 === 200) {
+    options.value = res.data?.flexible_template_enum_values || [];
+  }
+  console.log(res);
+};
+const handleTimeRangeChange = (newVal, oldVal) => {
+  console.log(newVal, oldVal, 999);
+  if (newVal.length === 2) {
+    reportEditor.setCommonConfig('common_start_time', newVal[0]);
+    reportEditor.setCommonConfig('common_end_time', newVal[1]);
+  }
+  emit('content-changed');
+};
+
+const handleAreaChange = (newVal) => {
+  console.log(newVal, 999);
+  if (newVal) {
+    reportEditor.setCommonConfig('common_area', newVal);
+  }
+  emit('content-changed');
+};
+watch(
+  () => reportEditor.QueryCommonConfig,
+  (newVal) => {
+    if (newVal.common_area) {
+      common_area.value = newVal.common_area;
+    }
+    if (newVal.common_start_time) {
+      time_range.value[0] = newVal.common_start_time;
+    }
+    if (newVal.common_end_time) {
+      time_range.value[1] = newVal.common_end_time;
+    }
   },
   {
-    id: 10,
-    field_name: '维度10',
-    field_type: 'str'
+    deep: true
   }
-]);
+);
+
+onMounted(() => {
+  getRegionEnumFun();
+});
 </script>
 <style scoped lang="css">
 .common-dimension {
@@ -92,8 +141,40 @@ const dimension_items = ref([
   overflow-y: hidden;
   white-space: nowrap;
 }
-.common-dimension-tips {
+.item {
+  display: flex;
+  align-items: center;
+  flex-direction: row;
+}
+:deep(.item .arco-picker) {
+  width: 200px;
+  background: #e0eaff;
+  color: #111;
+  border-radius: 8px;
+}
+:deep(.item .arco-picker-input) {
+  padding-left: 10px;
+}
+.icon_prefix {
+  /* margin-top: 4px; */
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+}
+.item-end-icon {
   color: #275fd4;
-  font-size: 12px;
+}
+:deep(.arco-picker-focused, .arco-picker-focused:hover) {
+  border-color: #275fd4;
+}
+:deep(.arco-select-view-single) {
+  background: #e0eaff;
+  border-radius: 8px;
+}
+:deep(.arco-select-view-single:focus-within, .arco-select-view-single.arco-select-view-focus) {
+  border-color: #275fd4;
+}
+:deep(.arco-picker-cell-selected .arco-picker-date-value) {
+  background-color: #275fd4 !important;
 }
 </style>

+ 22 - 10
src/modules/report-template/editor-model/EditorDetail.vue

@@ -73,7 +73,7 @@
       </div>
     </div>
     <div class="common-config">
-      <CommonDimension></CommonDimension>
+      <CommonDimension @content-changed="handleContentChanged"></CommonDimension>
     </div>
 
     <!-- 可编辑区域 -->
@@ -173,7 +173,8 @@ const emit = defineEmits([
   'close-filter-model',
   'to-save-setting',
   'submitHtml',
-  'chart-click'
+  'chart-click',
+  'content-changed'
 ]);
 
 // ============ Refs ============
@@ -575,6 +576,7 @@ const toggleFormat = (command) => {
     const content = editorRef.value.innerHTML;
     emit('submitHtml', content);
     emit('update:modelValue', content);
+    emit('content-changed', content);
     immediateSave();
     updateToolbar();
   });
@@ -592,6 +594,7 @@ const applyHeading = () => {
     const content = editorRef.value.innerHTML;
     emit('submitHtml', content);
     emit('update:modelValue', content);
+    emit('content-changed', content);
     immediateSave();
     updateToolbar();
   });
@@ -668,6 +671,7 @@ const applyFontSize = () => {
     const content = editorRef.value.innerHTML;
     emit('submitHtml', content);
     emit('update:modelValue', content);
+    emit('content-changed', content);
     immediateSave();
     updateToolbar();
   });
@@ -678,11 +682,16 @@ const onInput = () => {
   if (editorRef.value && !isRestoring) {
     emit('submitHtml', editorRef.value.innerHTML);
     emit('update:modelValue', editorRef.value.innerHTML);
+    emit('content-changed', editorRef.value.innerHTML);
     debouncedSave();
     syncMentionsData();
   }
 };
 
+const handleContentChanged = () => {
+  emit('content-changed');
+};
+
 // ============ 外部数据更新监听 ============
 watch(
   () => props.modelValue,
@@ -793,7 +802,7 @@ function insertOption(opt) {
   7.0500001999999995L7.45009735625,10.8000002C7.50009725625,10.850000399999999,7.60009765625,10.9000006,7.65009685625,10.9499998C7.65009685625,
   10.9499998,7.70009705625,11,7.70009705625,11C7.95009705625,11.100000399999999,8.30009745625,11.0500002,8.500097256250001,10.850000399999999L12.25009725625,
   7.0500001999999995C12.60009765625,6.69999981,12.55009745625,6.25,12.30009745625,5.94999981Z" fill="var(--primary-default)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>`;
-  placeholder.innerHTML = `{{${opt.metric_name}}}&nbsp;&nbsp;${svg_icon}`;
+  placeholder.innerHTML = `{{${opt.field_name}}}&nbsp;&nbsp;${svg_icon}`;
   const zeroWidth = '\u200B';
   const after = document.createTextNode(zeroWidth);
   range.insertNode(after);
@@ -815,6 +824,7 @@ function insertOption(opt) {
   hideActionMenu();
   immediateSave(); // 👈 接入你的历史机制
   emit('submitHtml', editorRef.value.innerHTML);
+  emit('content-changed', editorRef.value.innerHTML);
 }
 // 当编辑器内容改变时,同步数据
 function syncMentionsData() {
@@ -928,7 +938,8 @@ function handleEditorClick(e) {
     mention.classList.add('mention-highlighted');
     // console.log(option.option.advanced_computing_config);
     // console.log(JSON.parse(option.option.advanced_computing_config));
-    target_action.value = JSON.parse(option.option.advanced_computing_config);
+    console.log(option.option, 999);
+    target_action.value = JSON.parse(option.option.source.advanced_computing_config);
     // 保存当前指标引用,用于后续位置调整
     currentMentionElement.value = mention;
 
@@ -1107,14 +1118,13 @@ const onDropMetric = (event) => {
   if (editorRef.value && document.activeElement !== editorRef.value) {
     editorRef.value.focus({ preventScroll: true });
   }
-
-  const text = event.dataTransfer.getData('text/plain');
-  if (!text?.startsWith('__METRIC__')) return;
-
+  const text = event.dataTransfer.getData('application/x-metric');
+  if (!text) return;
   let item;
   try {
-    item = JSON.parse(text.slice('__METRIC__'.length));
-    if (!item || !item.metric_name) return;
+    item = JSON.parse(text);
+    console.log(item, 9999);
+    if (!item || !item.field_name) return;
   } catch (e) {
     console.warn('Drop parse error:', e);
     return;
@@ -1182,6 +1192,7 @@ function handleAction(action) {
   hideActionMenu();
   immediateSave();
   emit('submitHtml', editorRef.value.innerHTML);
+  emit('content-changed', editorRef.value.innerHTML);
 }
 
 // 处理图表面板下拉菜单点击-插入图表节点
@@ -1348,6 +1359,7 @@ function handleChartPanelDropdownClick(item) {
   // 更新父组件
   emit('submitHtml', editor.innerHTML);
   emit('update:modelValue', editor.innerHTML);
+  emit('content-changed', editor.innerHTML);
 }
 
 const insertData = () => {

+ 120 - 57
src/modules/report-template/editor-model/EditorTitleHeader.vue

@@ -61,7 +61,7 @@ const props = defineProps({
   template_name: String,
   template_save_time: String
 });
-const emit = defineEmits(['save_template']);
+const emit = defineEmits(['save_template', 'content-changed', 'update-draft']);
 
 const title = ref('草稿-' + Date.now());
 const edit_title = ref('');
@@ -73,6 +73,10 @@ const is_loading_template = ref(false);
 
 const show_save_template_model = ref(false);
 
+const is_need_save = computed(() => {
+  return reportEditor.QueryIsNeedSave;
+});
+
 const handleEdit = () => {
   edit_title.value = title.value;
   is_edit.value = true;
@@ -105,6 +109,7 @@ const handleSaveTitle = async () => {
     // 这里是没有生成模板前,编辑标题后,当保存模板时,在更新模板名称
     title.value = edit_title.value;
     is_edit.value = false;
+    emit('content-changed');
   }
 };
 const handleDeleteTitle = () => {
@@ -133,67 +138,89 @@ const formatTime = (updated_at) => {
 const ToSave = async () => {
   // 如果存在id,则保存新的修改到当前模板中
   if (props.template_id) {
-    try {
-      is_loading_template.value = true;
-      const res = await editTemplateItem({
-        id: props.template_id,
+    const res_temp = await editTemplateFunc(props.template_id);
+    if (res_temp.code / 1 === 200) {
+      Message.success('模板保存成功');
+    } else {
+      Message.error('模板保存失败');
+    }
+    return;
+  }
+  // 如果不存在id,则保存草稿
+  if (props.draft_id) {
+    const res_draft = await editTemplateFunc(props.draft_id);
+    if (res_draft.code / 1 === 200) {
+      Message.success('草稿保存成功');
+    } else {
+      Message.error('草稿保存失败');
+    }
+    return;
+  }
+  if (!props.template_id && !props.draft_id) {
+    const res = await addTemplateFunc(2);
+    if (res.data.id) {
+      Message.success('保存草稿成功');
+      emit('update-draft', {
+        id: res.data.id,
         name: title.value,
-        content: reportEditor.target_report_markdown,
-        template_config: JSON.stringify(reportEditor.metrics_list)
+        created_at: res.data.created_at
       });
-      if (res.code / 1 === 200) {
-        Message.success('保存成功');
-      } else {
-        Message.error('保存失败');
-      }
-    } catch (error) {
-      Message.error('保存失败');
-    } finally {
-      is_loading_template.value = false;
+    } else {
+      Message.error('保存草稿失败');
     }
-    return;
   }
-  // if (props.draft_id) {
-  //   try {
-  //     is_loading_template.value = true;
-  //     const res = await editTemplateItem({
-  //       id: props.draft_id,
-  //       name: title.value,
-  //       content: reportEditor.target_report_markdown,
-  //       template_config: JSON.stringify(reportEditor.metrics_list)
-  //     });
-  //     if (res.code / 1 === 200) {
-  //       console.log(res);
-  //       Message.success('保存成功');
-  //     } else {
-  //       Message.error('保存失败');
-  //     }
-  //   } catch (error) {
-  //     Message.error('保存失败');
-  //   } finally {
-  //     is_loading_template.value = false;
-  //   }
-  //   return;
-  // }
-  // if (!props.template_id && !props.draft_id) {
-  //   try {
-  //     const res = await addTemplate({
-  //       name: title.value,
-  //       content: reportEditor.target_report_markdown,
-  //       template_config: JSON.stringify(reportEditor.metrics_list)
-  //     });
-  //     if (res.code / 1 === 200) {
-  //       console.log(res);
-  //       Message.success('保存成功');
-  //     } else {
-  //       Message.error('保存失败');
-  //     }
-  //   } catch (error) {
-  //     Message.error('保存失败');
-  //   }
-  // }
   // 不存在id,则提醒是否保存成模板
-  show_save_Tips.value = true;
+  // show_save_Tips.value = true;
+};
+
+const editTemplateFunc = async (id) => {
+  try {
+    is_loading_template.value = true;
+    const res = await editTemplateItem({
+      id,
+      name: title.value,
+      content: reportEditor.target_report_markdown,
+      template_config: JSON.stringify(reportEditor.metrics_list),
+      common_config: JSON.stringify(reportEditor.common_config)
+    });
+    if (res.code / 1 === 200) {
+      return res;
+    } else {
+      Message.error('保存失败');
+    }
+  } catch (error) {
+    Message.error('保存失败');
+  } finally {
+    is_loading_template.value = false;
+  }
+  return;
+};
+
+const addTemplateFunc = async (type = 1) => {
+  // console.log(reportEditor, reportEditor.common_config);
+  try {
+    is_loading_template.value = true;
+    const res = await addTemplate({
+      name: title.value,
+      content: reportEditor.target_report_markdown,
+      template_config: JSON.stringify(reportEditor.metrics_list),
+      common_config: JSON.stringify(reportEditor.common_config),
+      type: type
+    });
+    if (res.code / 1 === 200) {
+      console.log(res);
+      // Message.success('保存成功');
+      return res;
+    } else {
+      // Message.error('保存失败');
+      return false;
+    }
+  } catch (error) {
+    // Message.error('保存失败');
+    return false;
+  } finally {
+    is_loading_template.value = false;
+  }
 };
 
 watch(
@@ -216,6 +243,42 @@ watch(
   },
   { immediate: true }
 );
+watch(
+  () => is_need_save.value,
+  async (newVal, oldVal) => {
+    console.log(newVal, props.template_id, props.draft_id, 99999999999999);
+    console.log(props.template_id && props.draft_id);
+    if (newVal) {
+      if (!props.template_id && !props.draft_id) {
+        const res = await addTemplateFunc(2);
+        if (res.data.id) {
+          Message.success('保存草稿成功');
+
+          emit('update-draft', {
+            id: res.data.id,
+            name: title.value,
+            created_at: res.data.created_at
+          });
+          reportEditor.setIsNeedSave(false);
+        }
+      }
+      if (!props.template_id && props.draft_id) {
+        console.log(props.draft_id, 99999999999999);
+        const res = await editTemplateFunc(props.draft_id);
+        if (res.code / 1 === 200) {
+          emit('update-draft', {
+            id: res.data.id,
+            name: title.value,
+            created_at: res.data.created_at
+          });
+        }
+        reportEditor.setIsNeedSave(false);
+        // console.log(res);
+      }
+    }
+    reportEditor.setIsNeedSave(false);
+  }
+);
 </script>
 <style scoped lang="css">
 .editor-title-header {

+ 2 - 0
src/modules/report-template/editor-model/SaveTemplateModel.vue

@@ -97,6 +97,7 @@ const form = ref({
   remark: '',
   content: reportEditor.target_report_markdown || '',
   template_config: reportEditor.metrics_list || '',
+  common_config: reportEditor.common_config || '',
   creator: ''
 });
 
@@ -160,6 +161,7 @@ const handleSave = async () => {
       remark: form.value.remark.trim(),
       content: form.value.content,
       template_config: JSON.stringify(form.value.template_config),
+      common_config: JSON.stringify(reportEditor.common_config),
       status: 1,
       creator: form.value.creator
 

+ 10 - 5
src/modules/report-template/editor-model/TipsModel.vue

@@ -3,12 +3,12 @@
     <template #title>
       <div class="title-box">
         <icon-exclamation-circle-fill style="color: #f37500; font-size: 28px" />
-        <span>检测到有未保存的配置,是否保存当前配置?</span>
+        <span>您的配置有未保存的更改,是否将其保存到当前模板中?</span>
       </div>
     </template>
     <template #footer>
-      <a-button type="info" style="border: none" @click="handleCancel">不保存</a-button>
-      <a-button class="update_btn" @click="handleOk">保存</a-button>
+      <a-button type="info" style="border: none" :disabled="loading" @click="handleCancel">不保存,返回</a-button>
+      <a-button class="update_btn" @click="handleOk" :loading="loading">{{ loading ? '保存中' : '保存' }}</a-button>
     </template>
   </a-modal>
 </template>
@@ -17,10 +17,11 @@
 import { toRefs } from 'vue';
 
 const props = defineProps({
-  visible: Boolean
+  visible: Boolean,
+  loading: Boolean
 });
 
-const { visible } = toRefs(props);
+const { visible, loading } = toRefs(props);
 const emit = defineEmits(['ok', 'cancel']);
 
 const handleOk = () => {
@@ -74,4 +75,8 @@ const handleCancel = () => {
   width: 100%;
   margin: auto;
 }
+.update_btn {
+  background-color: var(--primary-default) !important;
+  color: #fff !important;
+}
 </style>

+ 8 - 9
src/modules/report-template/editor-select/MentionDropdown.vue

@@ -7,15 +7,13 @@
     :style="{ top: position.top + 'px', left: position.left + 'px' }"
     @click.stop
   >
-    <button
-      v-for="(opt, index) in options"
-      :key="index"
-      @click="handleSelect(opt)"
-      class="dropdown-option"
-      :class="{ active: index === selectedIndex }"
-    >
-      {{ opt.metric_name }}
-    </button>
+    <template v-for="(opt, index) in options" :key="index">
+      <a-tooltip :content="`数据源:${opt.source.metric_name}`" position="right">
+        <button @click="handleSelect(opt)" class="dropdown-option" :class="{ active: index === selectedIndex }">
+          {{ opt.field_name }}
+        </button>
+      </a-tooltip>
+    </template>
   </div>
 </template>
 
@@ -86,6 +84,7 @@ watch(
   min-width: 120px;
   max-height: 200px;
   overflow-y: auto;
+  padding: 10px;
 }
 
 .dropdown-option {

+ 6 - 2
src/modules/report-template/filter-model/DimensionItem.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="dimension-item" @click="handleItemClick">
-    <img :src="getIconByType(dimension_item.field_type)" alt="" />
+    <img class="icon" :src="getIconByType(dimension_item.field_type)" alt="" />
     <span>{{ dimension_item.field_name }}</span>
     <icon-delete class="item-end-icon" @click.stop="handleClick(dimension_item)" />
   </div>
@@ -51,7 +51,7 @@ const clearTargetDeleteItem = () => {
 .dimension-item {
   position: relative;
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   gap: 5px;
   background: #e0eaff;
   color: #181d1d;
@@ -60,9 +60,13 @@ const clearTargetDeleteItem = () => {
   border-radius: 6px;
   cursor: pointer;
 }
+.icon {
+  margin-top: 2px;
+}
 .item-end-icon {
   position: absolute;
   right: 10px;
+  bottom: 10px;
   color: #275fd4;
   display: none;
 }

+ 37 - 8
src/modules/report-template/filter-model/MentionFilter.vue

@@ -99,11 +99,13 @@ const props = defineProps({
     default: () => {}
   }
 });
+
+const emit = defineEmits(['content-changed']);
 const filter_title = computed(() => {
-  return '指标 — ' + props.filter_option.option.metric_name;
+  return '指标 — ' + props.filter_option.option.field_name;
 });
 const measure_name = computed(() => {
-  return props.filter_option.option.metric_name;
+  return props.filter_option.option.field_name;
 });
 
 const dimension_list = ref([]);
@@ -123,7 +125,10 @@ const filter_id = ref(''); // 添加的每个过滤器对应的唯一ID
 const onDragOver = (event) => {
   event.preventDefault();
   event.stopPropagation();
-  if (event.dataTransfer.types.includes('application/x-dimension')) {
+  if (
+    event.dataTransfer.types.includes('application/x-dimension') ||
+    event.dataTransfer.types.includes('application/x-metric')
+  ) {
     event.dataTransfer.dropEffect = 'copy';
   } else {
     event.dataTransfer.dropEffect = 'none';
@@ -133,13 +138,20 @@ const onDrop = (event) => {
   event.preventDefault();
   event.stopPropagation();
   // 尝试读取度量数据
-  const rawData = event.dataTransfer.getData('application/x-dimension');
+  let rawData = null;
+  if (event.dataTransfer.types.includes('application/x-dimension')) {
+    rawData = event.dataTransfer.getData('application/x-dimension');
+  }
+  console.log('rawData', rawData);
+  // rawData = event.dataTransfer.getData('text/plain');
+
   if (!rawData) return;
   try {
     const item = JSON.parse(rawData);
-    console.log(item);
+    console.log(item, 999);
     if (item.filter_config) {
       const filter_config = JSON.parse(item.filter_config);
+      console.log(filter_config, 888);
       if (!filter_config.is_aggregatable) {
         return Message.error('该维度不支持聚合');
       }
@@ -154,6 +166,7 @@ const onDrop = (event) => {
     dimension_list.value.push(item);
 
     reportEditor.changeDimensionListConfigItem(item);
+    emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
   } catch (e) {
     console.warn('Failed to parse dropped metric:', e);
   }
@@ -162,7 +175,13 @@ const onDropFilter = (event) => {
   event.preventDefault();
   event.stopPropagation();
   // 尝试读取度量数据
-  const rawData = event.dataTransfer.getData('application/x-dimension');
+  let rawData;
+  if (event.dataTransfer.types.includes('application/x-dimension')) {
+    rawData = event.dataTransfer.getData('application/x-dimension');
+  }
+  if (event.dataTransfer.types.includes('application/x-metric')) {
+    rawData = event.dataTransfer.getData('application/x-metric');
+  }
   if (!rawData) return;
 
   try {
@@ -172,6 +191,7 @@ const onDropFilter = (event) => {
     filter_list.value.push(item);
     filter_type.value = item.field_type;
     setFilterConfig(item);
+    emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
   } catch (e) {
     console.warn('Failed to parse dropped metric:', e);
   }
@@ -247,11 +267,13 @@ const setFilterConfig = (item) => {
 const handleDimensionRemove = (dimesion_id) => {
   dimension_list.value = dimension_list.value.filter((item) => item.dimesion_id !== dimesion_id);
   reportEditor.changeDimensionListConfig(dimension_list.value);
+  emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
 };
 
 const handleFilterRemove = (filter_id) => {
   filter_list.value = filter_list.value.filter((item) => item.filter_id !== filter_id);
   reportEditor.changeFilterListConfig(filter_list.value);
+  emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
 };
 // 数字过滤器配置项
 const handleNumberConfirm = (filter_config) => {
@@ -263,6 +285,7 @@ const handleNumberConfirm = (filter_config) => {
     }
   });
   closeModel();
+  emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
 };
 // 字符串过滤器配置项
 const handleStringConfirm = (filter_config) => {
@@ -274,6 +297,7 @@ const handleStringConfirm = (filter_config) => {
     }
   });
   closeModel();
+  emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
 };
 // 日期过滤器配置项
 const handleDateConfirm = (filter_config) => {
@@ -285,6 +309,7 @@ const handleDateConfirm = (filter_config) => {
     }
   });
   closeModel();
+  emit('content-changed', { dimension_list: dimension_list.value, filter_list: filter_list.value });
 };
 
 const handleUpdate = () => {
@@ -306,7 +331,10 @@ const handleUpdate = () => {
 watch(
   [() => props.filter_option, () => reportEditor.QueryTargetDimensionListConfig],
   ([newVal, newTargetDiemensionList]) => {
-    const dimension_query_list = newVal.option.flexible_template_metric_fields;
+    console.log('newVal', newVal);
+    const dimension_query_list = newVal.option.source.flexible_template_metric_fields.filter(
+      (item) => item.column_role === 'dim'
+    );
     const result = dimension_query_list.filter((item) => newTargetDiemensionList.includes(item.field_key));
     // 深拷贝result数组中的每个item,避免直接修改props
     const processedResult = result.map((item) => {
@@ -322,10 +350,11 @@ watch(
     deep: true
   }
 );
+
 watch(
   [() => props.filter_option, () => reportEditor.QueryTargetFilterListConfig],
   ([newVal, newTargetFilterList]) => {
-    const filter_query_list = newVal.option.flexible_template_metric_fields;
+    const filter_query_list = newVal.option.source.flexible_template_metric_fields;
     const result = [];
     console.log(filter_query_list, newTargetFilterList);
     filter_query_list.forEach((item) => {

+ 81 - 17
src/modules/report-template/filter-model/model/DualListSelector.vue

@@ -27,11 +27,11 @@
             <a-list v-else :data-source="filteredItems" :pagination="false" :size="'small'">
               <a-list-item
                 v-for="item in filteredItems"
-                :key="item"
-                :class="{ 'list-item-selected': selectedItems.includes(item) }"
-                @click="toggleSelect(item)"
+                :key="item.value"
+                :class="{ 'list-item-selected': selectedItems.includes(item.value) }"
+                @click="toggleSelect(item.value)"
               >
-                {{ item }}
+                {{ item.display_name }}
               </a-list-item>
             </a-list>
           </a-scrollbar>
@@ -57,12 +57,12 @@
             <!-- 右侧正常列表 -->
             <a-list v-else :data-source="selectedItems" :pagination="false" :size="'small'">
               <a-list-item
-                v-for="item in selectedItems"
-                :key="item"
+                v-for="value in selectedItems"
+                :key="value"
                 :class="{ 'list-item-selected': true }"
-                @click="toggleSelect(item)"
+                @click="toggleSelect(value)"
               >
-                {{ item }}
+                {{ getDisplayNameByValue(value) }}
               </a-list-item>
             </a-list>
           </a-scrollbar>
@@ -84,8 +84,9 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
+import { ref, computed, onMounted, watch } from 'vue';
 import Empty from '../../../../assets/chat-aside/empty_a.svg';
+import { getRegionEnum } from '../../../conversation-dialog/api-template';
 
 const props = defineProps({
   items: {
@@ -100,6 +101,10 @@ const props = defineProps({
     type: String,
     default: 'multiple', // multiple 或 single
     validator: (value) => ['multiple', 'single'].includes(value)
+  },
+  enum_type_id: {
+    type: String,
+    default: ''
   }
 });
 
@@ -108,35 +113,78 @@ const emit = defineEmits(['cancel', 'confirm']);
 const searchQuery = ref('');
 const selectedItems = ref([...props.selected]);
 
+const enumOptions = ref([]);
+
 // 过滤左侧可选列表
 const filteredItems = computed(() => {
+  console.log('filteredItems computed with enumOptions.length:', enumOptions.value.length);
+  console.log('selectedItems:', selectedItems.value);
+
   if (!searchQuery.value) {
-    return props.items.filter((item) => !selectedItems.value.includes(item));
+    return enumOptions.value.filter((item) => !selectedItems.value.includes(item.value));
   }
-  return props.items.filter(
-    (item) => !selectedItems.value.includes(item) && item.toLowerCase().includes(searchQuery.value.toLowerCase())
+  return enumOptions.value.filter(
+    (item) =>
+      !selectedItems.value.includes(item.value) &&
+      item.display_name.toLowerCase().includes(searchQuery.value.toLowerCase())
   );
 });
 
+// 通过value获取display_name
+const getDisplayNameByValue = (value) => {
+  const item = enumOptions.value.find((option) => option.value === value);
+  return item ? item.display_name : value;
+};
+
+const getRegionEnumFun = async () => {
+  console.log('getRegionEnumFun called with enum_type_id:', props.enum_type_id);
+
+  // 即使enum_type_id为空,也尝试使用默认值'1'
+  const enumTypeId = props.enum_type_id || '1';
+  console.log('Using enum_type_id:', enumTypeId);
+
+  try {
+    const res = await getRegionEnum({
+      enum_type_id: enumTypeId
+    });
+    console.log('API response:', res);
+
+    if (res && res.code && res.code / 1 === 200) {
+      if (res.data && res.data.flexible_template_enum_values) {
+        enumOptions.value = res.data.flexible_template_enum_values;
+        console.log('Enum options loaded successfully:', enumOptions.value.length, 'items');
+      } else {
+        console.error('Invalid response data structure:', res.data);
+      }
+    } else {
+      console.error('API request failed with code:', res?.code);
+    }
+  } catch (error) {
+    console.error('Error fetching region enum:', error);
+  }
+};
+
 // 切换选择项
-const toggleSelect = (item) => {
+const toggleSelect = (value) => {
   if (props.mode === 'single') {
     // 单选模式:直接替换选中项
-    selectedItems.value = [item];
+    selectedItems.value = [value];
   } else {
     // 多选模式:切换选中状态
-    const index = selectedItems.value.indexOf(item);
+    const index = selectedItems.value.indexOf(value);
     if (index > -1) {
       selectedItems.value.splice(index, 1);
     } else {
-      selectedItems.value.push(item);
+      selectedItems.value.push(value);
     }
   }
 };
 
 // 添加全部
 const addAll = () => {
-  const newItems = props.items.filter((item) => !selectedItems.value.includes(item));
+  const newItems = enumOptions.value
+    .filter((item) => !selectedItems.value.includes(item.value))
+    .map((item) => item.value);
   selectedItems.value = [...selectedItems.value, ...newItems];
 };
 
@@ -154,6 +202,22 @@ const handleCancel = () => {
 const handleConfirm = () => {
   emit('confirm', selectedItems.value);
 };
+
+onMounted(() => {
+  if (props.enum_type_id) {
+    getRegionEnumFun();
+  }
+});
+
+watch(
+  () => props.enum_type_id,
+  () => {
+    getRegionEnumFun();
+  },
+  {
+    immediate: true
+  }
+);
 </script>
 
 <style scoped>

+ 47 - 22
src/modules/report-template/filter-model/model/StringModel.vue

@@ -163,6 +163,7 @@
     <dual-list-selector
       v-if="showEnumSelector"
       :items="enumOptions"
+      :enum_type_id="enum_type_id"
       :selected="formModel.field_value"
       :mode="formModel.subType === 'single' ? 'single' : 'multiple'"
       @cancel="handleEnumSelectorCancel"
@@ -191,6 +192,28 @@ const visible = computed(() => {
   return props.model_name === 'str';
 });
 
+const enum_type_id = computed(() => {
+  try {
+    // 尝试从template_config中获取enum_type_id
+    if (props.filter.template_config && Array.isArray(props.filter.template_config)) {
+      // 查找type为filter_by_enum的配置
+      const enumConfig = props.filter.template_config.find((config) => config.type === 'filter_by_enum');
+      if (enumConfig && enumConfig.children && Array.isArray(enumConfig.children)) {
+        const firstChild = enumConfig.children[0];
+        if (firstChild && firstChild.enum_type_id) {
+          console.log('Found enum_type_id:', firstChild.enum_type_id);
+          return firstChild.enum_type_id;
+        }
+      }
+    }
+    console.log('enum_type_id not found, using default');
+    return '1'; // 默认值,确保API调用能够执行
+  } catch (error) {
+    console.error('Error getting enum_type_id:', error);
+    return '1'; // 默认值,确保API调用能够执行
+  }
+});
+
 // 1. 先定义空表单
 const formModel = ref({
   ...props.filter,
@@ -211,26 +234,26 @@ const formModel = ref({
 
 // 枚举选项列表(模拟数据,实际项目中可能从API获取)
 const enumOptions = ref([
-  '乐山',
-  '内江',
-  '凉山',
-  '南充',
-  '天府',
-  '宜宾',
-  '巴中',
-  '广元',
-  '广安',
-  '德阳',
-  '成都',
-  '阿坝',
-  '达州',
-  '自贡',
-  '泸州',
-  '遂宁',
-  '眉山',
-  '资阳',
-  '绵阳',
-  '攀枝花'
+  // '乐山',
+  // '内江',
+  // '凉山',
+  // '南充',
+  // '天府',
+  // '宜宾',
+  // '巴中',
+  // '广元',
+  // '广安',
+  // '德阳',
+  // '成都',
+  // '阿坝',
+  // '达州',
+  // '自贡',
+  // '泸州',
+  // '遂宁',
+  // '眉山',
+  // '资阳',
+  // '绵阳',
+  // '攀枝花'
 ]);
 
 // 显示双列表选择器的开关
@@ -366,6 +389,8 @@ const initForm = () => {
   });
 };
 
+// onMounted(() => getRegionEnumFun(props.filter.template_config[0].enum_type_id));
+
 // 4. 监听 props.filter 变化,重新初始化(防抖可选)
 watch(
   () => props.filter,
@@ -513,8 +538,8 @@ const handleBeforeOk = (done) => {
     submitData.filter_by_enum = data.subType;
     submitData.filter_by_condition = null;
     submitData.filter_conditions = null;
-    submitData.field_value = data.filter_conditions.map((item) => item.field_value);
-    data.subType === 'single' ? [data.field_value[0] || ''] : data.field_value.filter((v) => v.trim() !== '');
+    submitData.field_value =
+      data.subType === 'single' ? [data.field_value[0] || ''] : data.field_value.filter((v) => v.trim() !== '');
   }
   submitData.filter_id = props.filter_id;
   console.log('submitData:', submitData);

+ 42 - 8
src/modules/report-template/source-model/DataSource.vue

@@ -5,8 +5,8 @@
       <icon-right />
     </div>
     <div class="select">
-      <a-select v-model="target_source" :style="{ width: '100%' }" placeholder="请选择数据源">
-        <a-option v-for="item in source_data_options" :key="item.value">{{ item.label }}</a-option>
+      <a-select v-model="target_source" @change="handleChange" :style="{ width: '100%' }" placeholder="请选择数据源">
+        <a-option v-for="item in source_data_options" :key="item.value" :value="item.value">{{ item.label }}</a-option>
       </a-select>
     </div>
   </div>
@@ -15,13 +15,47 @@
 <script setup >
 import { ref, computed, watch, onMounted } from 'vue';
 
-const source_data_options = [
-  {
-    value: '供电可靠性明细数据表',
-    label: '供电可靠性明细数据表'
+const props = defineProps({
+  target_source_id: String,
+  data_source_list: Array
+});
+
+const emit = defineEmits(['change-source']);
+
+const source_data_options = computed(() => {
+  return (props.data_source_list || []).map((item) => ({
+    value: item.id,
+    label: item.metric_name
+  }));
+});
+const target_source = ref(props.target_source_id || '');
+
+const handleChange = (value) => {
+  target_source.value = value;
+  emit('change-source', value);
+};
+
+watch(
+  () => props.target_source_id,
+  (newVal, oldVal) => {
+    handleChange(newVal);
   }
-];
-const target_source = ref('供电可靠性明细数据表');
+);
+// 当数据源列表变化时,默认选择第一个
+// watch(
+//   () => props.data_source_list,
+//   (newOptions) => {
+//     console.log(props.data_source_list[0]?.id, 888888888888888);
+//     if (props.data_source_list.length > 0) {
+//       // console.log(props.data_source_list[0]?.id, 888888888888888);
+//       target_source.value = props.target_source_id || '';
+//       handleChange(props.target_source_id);
+//     } else {
+//       target_source.value = '';
+//     }
+//   },
+//   { immediate: true, deep: true }
+// );
 </script>
 <style scoped lang="css">
 .data-source {

+ 9 - 1
src/modules/report-template/source-model/DimensionSource.vue

@@ -4,7 +4,7 @@
       <span>维度</span>
     </div>
     <div class="diemension_list">
-      <template v-for="item in dimension_list" :key="item.id">
+      <template v-for="item in target_dimension_list" :key="item.id">
         <!-- <a-tooltip
           position="lt"
           mini
@@ -46,6 +46,14 @@ const emit = defineEmits(['dimension-click']);
 
 const target_dimension_id = ref('');
 
+const target_dimension_list = computed(() => {
+  return props.dimension_list.map((item) => {
+    return {
+      ...item,
+      is_aggregatable: JSON.parse(item.filter_config)?.is_aggregatable || 0
+    };
+  });
+});
 const getIconByType = (type) => {
   if (type === 'str') return DimensionStr;
   if (type === 'int') return DimensionNum;

+ 9 - 10
src/modules/report-template/source-model/MetricsSource.vue

@@ -11,13 +11,12 @@
         <div
           class="item"
           :class="item.id === target_metrics_id ? 'active_item' : ''"
-          @click="handleClick(item)"
           :draggable="true"
           @dragstart="onDragStart($event, item)"
           @dragend="onDragEnd"
         >
           <img class="icon" :src="MetricsIcon" alt="" />
-          <span>{{ item.metric_name }}</span>
+          <span>{{ item.field_name }}</span>
         </div>
       </template>
     </div>
@@ -37,11 +36,6 @@ const props = defineProps({
 });
 const emit = defineEmits(['metrics-click']);
 const target_metrics_id = ref('');
-const getIconByType = (type) => {
-  if (type === 'string') return DimensionStr;
-  if (type === 'int') return DimensionStr || '';
-  if (type === 'date') return DimensionDate;
-};
 
 const handleClick = (item) => {
   if (item.id === target_metrics_id.value) {
@@ -54,13 +48,18 @@ const handleClick = (item) => {
 };
 const onDragStart = (event, item) => {
   console.log(item);
-  handleClick(item);
-  const payload = JSON.stringify(item);
-  event.dataTransfer.setData('text/plain', `__METRIC__${payload}`);
+  // handleClick(item);
+  event.dataTransfer.setData('application/x-metric', JSON.stringify(item));
+  event.dataTransfer.setData('text/plain', ''); // ←←← 这行是关键!
 
   event.dataTransfer.effectAllowed = 'copy'; // 或 'move'
   console.log(event.dataTransfer);
 
+  // event.dataTransfer.setData('text/plain', `__METRIC__${payload}`);
+
+  // event.dataTransfer.effectAllowed = 'copy'; // 或 'move'
+  // console.log(event.dataTransfer);
+
   // 可选:添加拖拽时的视觉反馈(例如半透明)
   const target = event.target;
   if (target.classList) {

+ 12 - 1
src/modules/report-template/template-detail/EditorModel.vue

@@ -6,6 +6,8 @@
       :template_name="template_name"
       :template_save_time="template_save_time"
       @save_template="handleSaveTemplate"
+      @content-changed="handleContentChanged"
+      @update-draft="handleUpdateDraft"
     ></EditorTitleHeader>
     <EditorDetail
       :modelValue="render_html"
@@ -15,6 +17,7 @@
       @chart-click="handleChartClick"
       @close-filter-model="closeFilterModel"
       @to-save-setting="handleToSaveSetting"
+      @content-changed="handleContentChanged"
       @submitHtml="handleSubmitHtml"
     ></EditorDetail>
     <div class="loading" v-if="is_loading_template_detail">
@@ -45,7 +48,9 @@ const emit = defineEmits([
   'close-filter-model',
   'to-save-setting',
   'submit-html',
-  'save-template'
+  'save-template',
+  'content-changed',
+  'update-draft'
 ]);
 
 const handleClick = () => {
@@ -73,6 +78,12 @@ const handleSubmitHtml = (html) => {
 const handleSaveTemplate = (data) => {
   emit('save-template', data);
 };
+const handleContentChanged = (html) => {
+  emit('content-changed');
+};
+const handleUpdateDraft = (data) => {
+  emit('update-draft', data);
+};
 
 function getFirstH1Content(htmlString) {
   // 创建临时DOM元素用于解析HTML

+ 11 - 1
src/modules/report-template/template-detail/FilterModel.vue

@@ -1,6 +1,11 @@
 <template>
   <div class="filter-model">
-    <MentionFilter ref="mentionFilterRef" v-if="type == 'mention'" :filter_option="filter_option"></MentionFilter>
+    <MentionFilter
+      ref="mentionFilterRef"
+      v-if="type == 'mention'"
+      :filter_option="filter_option"
+      @content-changed="handleContentChanged"
+    ></MentionFilter>
   </div>
 </template>
 
@@ -18,6 +23,7 @@ const props = defineProps({
     default: () => {}
   }
 });
+const emit = defineEmits(['content-changed']);
 const mentionFilterRef = ref(null);
 
 const type = computed(() => {
@@ -28,6 +34,10 @@ const toSaveSetting = () => {
     mentionFilterRef.value.saveDataToStore();
   }
 };
+const handleContentChanged = (content) => {
+  // console.log('内容变了');
+  emit('content-changed');
+};
 defineExpose({
   saveSettingToStore: toSaveSetting
 });

+ 31 - 46
src/modules/report-template/template-detail/SourceModel.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="source-model">
     <!-- 数据源 -->
-    <DataSource></DataSource>
+    <DataSource
+      :target_source_id="target_source_id"
+      :data_source_list="data_source_list"
+      @change-source="handleChangeSource"
+    ></DataSource>
     <FieldSource></FieldSource>
-    <MetricsSource
-      :metrics_list="metrics_list"
-      :is_loading="is_metrics_loading"
-      @metrics-click="handleMetricsClick"
-    ></MetricsSource>
-    <DimensionSource v-if="show_dimension_source" :dimension_list="target_dimension_list"></DimensionSource>
+    <MetricsSource :metrics_list="metrics_list" :is_loading="is_metrics_loading"></MetricsSource>
+    <DimensionSource v-if="show_dimension_source" :dimension_list="dimensions_list"></DimensionSource>
   </div>
 </template>
 
@@ -22,58 +22,43 @@ import { getMetrics } from '../../conversation-dialog/api-template';
 
 const props = defineProps({
   is_metrics_loading: Boolean,
+  target_source_id: String,
+  data_source_list: Array,
   metrics_list: Array,
-  target_filter_option: Object
+  dimensions_list: Array
 });
 
-const emit = defineEmits(['metrics-click']);
+const emit = defineEmits(['metrics-click', 'change-source']);
 
 const show_dimension_source = computed(() => {
-  return target_dimension_list.value.length;
+  // return target_dimension_list.value.length;
+  return props.dimensions_list?.length;
 });
 // 度量数据
 // const metrics_list = ref([]);
 // const is_metrics_loading = ref(false);
 
-const target_dimension_list = ref([]);
-
-const handleMetricsClick = (item) => {
-  if (item) {
-    target_dimension_list.value = [...item.flexible_template_metric_fields];
-    target_dimension_list.value = target_dimension_list.value.map((it) => {
-      return {
-        ...it,
-        is_aggregatable: JSON.parse(it.filter_config)?.is_aggregatable || false
-      };
-    });
-    // target_dimension_list.value.push({
-    //   id: 'xxxsasdas',
-    //   field_name: '测试数字',
-    //   field_type: 'int'
-    // });
-    emit('metrics-click', item);
-  } else {
-    target_dimension_list.value = [];
-  }
+const handleChangeSource = (value) => {
+  emit('change-source', value);
 };
 
 onMounted(() => {});
-watch(
-  () => props.target_filter_option,
-  (new_value) => {
-    if (new_value) {
-      console.log('new_value', new_value, props.metrics_list);
-      props.metrics_list.forEach((item) => {
-        if (item.metric_name === new_value.option.metric_name) {
-          handleMetricsClick(item);
-        }
-      });
-    }
-  },
-  {
-    deep: true
-  }
-);
+// watch(
+//   () => props.target_filter_option,
+//   (new_value) => {
+//     if (new_value) {
+//       console.log('new_value', new_value, props.metrics_list);
+//       props.metrics_list.forEach((item) => {
+//         if (item.metric_name === new_value.option.metric_name) {
+//           handleMetricsClick(item);
+//         }
+//       });
+//     }
+//   },
+//   {
+//     deep: true
+//   }
+// );
 </script>
 <style scoped lang="css">
 .source-model {

+ 16 - 8
src/modules/report/Editor.vue

@@ -223,7 +223,7 @@ const bindPElementEvents = () => {
     pTag.addEventListener('mouseleave', (e) => handleParentMouseLeave(e, pTag));
   });
 };
-// 获取到文档中前几个H1进行居中
+// 获取到文档中前几个H1进行居中,并将第一个h标签作为title传递
 const toAlginCenterByH1 = () => {
   const max_count = 10;
   let count = 0;
@@ -235,17 +235,25 @@ const toAlginCenterByH1 = () => {
     }
     count += 1;
     if (editorRef.value.getHtml()) {
-      // console.log(document.querySelectorAll('.editor_content h1'));
-      const headers = document.querySelectorAll('.editor_content h1');
-      headers.forEach((item, index) => {
+      // 1. 处理h1标签居中
+      const h1Headers = document.querySelectorAll('.editor_content h1');
+      h1Headers.forEach((item, index) => {
         if (index + 1 == item.id.split('-')[1]) {
           item.setAttribute('style', 'text-align: center ');
         }
-        if (item.id.split('-')[1] / 1 === 1) {
-          if (props.report_title) return;
-          emit('renderTitle', item.innerText);
-        }
       });
+
+      // 2. 找到第一个h标签(h1-h6)作为title传递
+      if (!props.report_title) {
+        const allHeaders = document.querySelectorAll(
+          '.editor_content h1, .editor_content h2, .editor_content h3, .editor_content h4, .editor_content h5, .editor_content h6'
+        );
+        if (allHeaders.length > 0) {
+          const firstHeader = allHeaders[0];
+          emit('renderTitle', firstHeader.innerText);
+        }
+      }
+
       editorRef.value.updateView();
       count = 11;
       pre_html_content.value = valueHtml.value;

+ 57 - 5
src/modules/template-manage/template/TemplateDetailBody.vue

@@ -60,10 +60,12 @@
                 :checked-value="1"
                 :unchecked-value="0"
                 :loading="is_loading_status"
+                :disabled="is_loading_status"
                 checked-color="var(--primary-default)"
                 checked-text="启用"
                 unchecked-text="停用"
-                @change="handleStatusChange(record)"
+                :beforeChange="handleChangeIntercept"
+                @change="handleStatusChange(record, $event)"
               >
               </a-switch>
             </div>
@@ -100,7 +102,12 @@
 
 <script setup >
 import { ref, reactive, watch, computed, nextTick, onMounted } from 'vue';
-import { getTemplateListByGroupId, deleteTemplateItem, editTemplateItem } from '../../conversation-dialog/api-template';
+import {
+  getTemplateListByGroupId,
+  deleteTemplateItem,
+  editTemplateItem,
+  checkTemplateDetail
+} from '../../conversation-dialog/api-template';
 import DeleteModal from '../../../components/DeleteModal.vue';
 import { Message } from '@arco-design/web-vue';
 import { IconEdit } from '@arco-design/web-vue/es/icon';
@@ -273,10 +280,31 @@ const handleCancel = () => {
   selectedKeys.value = [];
   is_branch.value = false;
 };
-const handleStatusChange = (record) => {
-  console.log(record);
+
+const handleChangeIntercept = async (value) => {};
+const handleChangeIntercept1 = async (newValue) => {
+  await new Promise((resolve) => setTimeout(resolve, 1000));
+  return true;
+};
+
+const handleChangeIntercept2 = async (newValue) => {
+  await new Promise((resolve) => setTimeout(resolve, 500));
+  return false;
+  return true;
+};
+const handleStatusChange = async (record, val) => {
+  console.log(record, val);
   is_loading_status.value = true;
-  editTemplateItemFunc({ id: record.id, status: record.status });
+  if (val / 1 === 0) {
+    await editTemplateItemFunc({ id: record.id, status: record.status });
+    return;
+  }
+  const hasContent = await checkTemplateDetailFunc(record);
+  if (hasContent) {
+    await editTemplateItemFunc({ id: record.id, status: record.status });
+  } else {
+    is_loading_status.value = false;
+  }
 };
 
 const editTemplateItemFunc = async (item) => {
@@ -298,6 +326,30 @@ const editTemplateItemFunc = async (item) => {
   }
 };
 
+const checkTemplateDetailFunc = async (item) => {
+  try {
+    const res = await checkTemplateDetail({
+      id: item.id
+    });
+    if (res.code / 1 === 200) {
+      if (!res.data.content) {
+        Message.error('模板内容为空,请先配置模板');
+        getTemplateList();
+        return false;
+      }
+      return true;
+    } else {
+      getTemplateList();
+      Message.error('数据异常,请稍后重试');
+      return false;
+    }
+  } catch (error) {
+    getTemplateList();
+    Message.error('数据异常,请稍后重试');
+    return false;
+  }
+};
+
 const handleEditRemark = (record) => {
   record.temp_remark = record.remark;
   record.is_remark_edit = true;

+ 5 - 0
src/views/conversation/ConversationChatContainer.vue

@@ -166,6 +166,8 @@ const show_template_manage = ref(false);
 const target_template_id = ref('');
 const target_outline_md = ref('');
 
+const dialog_input = ref(null);
+
 let isProcessingQueue = ref(false);
 const PROCESS_INTERVAL = 10;
 
@@ -1272,6 +1274,9 @@ const toInitData = () => {
     clientHeight: 0
   };
   // shouldAutoScroll.value = true
+  if (chat_type.value === 'bgsc') {
+    dialog_input.value.initInput();
+  }
 };
 const toNewZnws = () => {
   toInitData();