Explorar el Código

feat: 工单以及所有问答板块增加echart图表显示

sunxiao hace 1 semana
padre
commit
82a3edea8b

+ 108 - 25
src/components/Chat/ChatText.vue

@@ -1,12 +1,12 @@
-<script setup>
-import { computed } from 'vue';
+<script setup lang="jsx">
+import { computed, ref, watchEffect } from 'vue';
 import MarkdownIt from 'markdown-it';
 import hljs from 'highlight.js';
 import mila from 'markdown-it-link-attributes';
-// import markdownItMath from 'markdown-it-math';
-// import mdKatex from '@iktakahiro/markdown-it-katex';
 import markdownItTexmath from 'markdown-it-texmath';
-import katex from 'katex'
+import katex from 'katex';
+import * as echarts from 'echarts';
+
 const props = defineProps({
   content: {
     type: String,
@@ -14,17 +14,21 @@ const props = defineProps({
   }
 })
 
+const echartOptions = {};
+const echartInstance = {};
+
 const highlightBlock = (str, lang) => {
   return `
     <code class="hljs code-block-body ${lang}">${str}</code>
   `
 }
+
 const mdi = new MarkdownIt({
-  // html: true,
-  // linkify: true,
-  // breaks: true,
-  // typographer: true,
-  highlight (code, language) {
+  html: true,
+  linkify: true,
+  breaks: true,
+  typographer: true,
+  highlight(code, language) {
     const validLang = !!(language && hljs.getLanguage(language))
     if (validLang) {
       const lang = language ?? ''
@@ -36,7 +40,6 @@ const mdi = new MarkdownIt({
 
 mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
 
-
 mdi.use(markdownItTexmath, {
   engine: katex,
   delimiters: ['dollars', 'brackets', 'doxygen', 'gitlab', 'julia', 'kramdown', 'beg_end'],
@@ -45,6 +48,98 @@ mdi.use(markdownItTexmath, {
 mdi.renderer.rules.table_open = function (tokens, idx, options, env, self) { return '<div class="custom-table-wrapper"><table>'; };
 mdi.renderer.rules.table_close = function () { return '</table></div>'; };
 
+mdi.renderer.rules.echart_block = (tokens, idx, options, env, self) => {
+  const token = tokens[idx];
+  const optionsString = token.content.trim();
+  try {
+    const optionsObj = JSON.parse(optionsString);
+
+    echartOptions[`chart-${idx}`] = { id: idx, code: optionsString, option: optionsObj, isExecute: false }
+
+    return `<div id="chart-${idx}"></div>`;
+  } catch (e) {
+    console.error('Invalid ECharts options:', optionsString);
+    return '';
+  }
+};
+
+mdi.block.ruler.after('fence', 'echart_block', function (state, startLine, endLine, silent) {
+  const startStr = '@@@echarts_b';
+  const endStr = '@@@echarts_e';
+
+  if (startLine + 1 < state.lineMax && state.src.slice(state.bMarks[startLine] + state.tShift[startLine], state.eMarks[startLine]) === startStr) {
+
+    let pos = state.bMarks[startLine++] + state.tShift[startLine];
+    let endPos = state.bMarks[startLine] + state.blkIndent;
+
+    let nextLine = startLine;
+    for (; nextLine < endLine; nextLine++) {
+      endPos = state.bMarks[nextLine] + state.tShift[nextLine];
+      if (state.src.slice(endPos, endPos + endStr.length) === endStr) {
+        break;
+      }
+    }
+
+    if (nextLine < endLine) {
+      const str = state.getLines(startLine, nextLine, state.tShift[startLine], false);
+      const token = state.push('echart_block', 'div', 0);
+      token.content = str;
+      token.map = [startLine, nextLine + 1];
+
+      state.line = nextLine + 1;
+
+      return true;
+    }
+  }
+
+  return false;
+});
+
+const updateCharts = () => {
+  Object.keys(echartOptions).forEach((key) => {
+    if (echartInstance[key]) return;
+    setTimeout(() => {
+      echartInstance[key] = echarts.init(document.getElementById(key), null, { width: 800, height: 400 });
+      echartInstance[key].setOption(echartOptions[key].option);
+    }, 100)
+  });
+}
+
+const markdownText = ref([]);
+
+function splitStringByChartDiv(str) {
+  const regex = /<div id="chart-\d+"><\/div>/g;
+
+  let result = [];
+  let lastIndex = 0;
+
+  let match;
+  while ((match = regex.exec(str)) !== null) {
+    if (lastIndex !== regex.lastIndex - match[0].length) {
+      result.push(str.slice(lastIndex, regex.lastIndex - match[0].length));
+    }
+    result.push(match[0]);
+    lastIndex = regex.lastIndex;
+  }
+
+  if (lastIndex < str.length) {
+    result.push(str.slice(lastIndex));
+  }
+
+  return result;
+}
+
+watchEffect(() => {
+  const value = mdi.render(props.content);
+
+  const result = splitStringByChartDiv(value)
+
+  markdownText.value = result
+
+  updateCharts();
+})
+
+// 原本的逻辑,暂时不用
 const text = computed(() => {
   const value = props.content ?? ""
   if (!props.asRawText)
@@ -56,11 +151,11 @@ const text = computed(() => {
 
 <template>
   <div class="markdown-body text-[15px] break-all" v-if="content">
-    <div v-html="text"></div>
+    <div v-for="item, index in markdownText" :key="index" v-html="item"></div>
   </div>
 </template>
 
-<style>
+<style lang="scss">
 .markdown-body p:last-child {
   margin-bottom: 0;
 }
@@ -92,17 +187,5 @@ const text = computed(() => {
     margin-bottom: 0;
   }
 
-  /* img {
-    margin-top: 20px;
-  }
-
-  table {
-
-    thead {
-      strong {
-        font-size: 12px;
-      }
-    }
-  } */
 }
 </style>

+ 10 - 10
src/components/Layout/TheMenu.vue

@@ -78,16 +78,16 @@ const menuOptions = [
         icon: renderChildrenIcon({ name: 'menu-cost-drug' }),
         key: 'medicinal',
       },
-      {
-        label: '精准曝气',
-        icon: renderChildrenIcon({ name: 'menu-cost-accurate' }),
-        key: 'normal-2',
-      },
-      {
-        label: '微生物镜检',
-        icon: renderChildrenIcon({ name: 'menu-cost-microorganism' }),
-        key: 'normal-3',
-      },
+      // {
+      //   label: '精准曝气',
+      //   icon: renderChildrenIcon({ name: 'menu-cost-accurate' }),
+      //   key: 'normal-2',
+      // },
+      // {
+      //   label: '微生物镜检',
+      //   icon: renderChildrenIcon({ name: 'menu-cost-microorganism' }),
+      //   key: 'normal-3',
+      // },
       {
         label: '碳排放智能体',
         icon: renderChildrenIcon({ name: 'menu-carbon-emission' }),

+ 7 - 4
src/components/Layout/userTop.vue

@@ -29,13 +29,16 @@ const changeVoiceStatus = () => {
 </script>
 
 <template>
-  <BasePopover placement="bottom" content="数字人设置">
+  <!-- 客服icon -->
+  <!-- <BasePopover placement="bottom" content="数字人设置">
     <div class="avatar rounded-full w-[24px] h-[24px]">
       <img src="@/assets/images/chat/img-avatar.png" alt="" class="cursor-pointer">
     </div>
-  </BasePopover>
-  <SvgIcon :name="voiceName" size="24" class="cursor-pointer" @click="changeVoiceStatus"></SvgIcon>
-  <div class="h-[24px] border-r-[1px] border-color-[#D3D0E1]"></div>
+  </BasePopover> -->
+  <!-- 声音icon -->
+  <!-- <SvgIcon :name="voiceName" size="24" class="cursor-pointer" @click="changeVoiceStatus"></SvgIcon> -->
+   <!-- 分割线 -->
+  <!-- <div class="h-[24px] border-r-[1px] border-color-[#D3D0E1]"></div> -->
   <NSelect v-model:value="selectValue" class="w-[114px]" size="medium" :options="options"
     :consistent-menu-width="false" />
   <TheUserAvatar></TheUserAvatar>

+ 647 - 0
src/views/analyse/WorkOrder-back.vue

@@ -0,0 +1,647 @@
+<script setup>
+import { ref, unref, computed, onUnmounted, h } from 'vue';
+import { useMessage, NDatePicker, NTabs, NTab, NRadioGroup, NRadio, NCheckboxGroup, NCheckbox, NDataTable } from 'naive-ui';
+import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
+import { ChatAsk, ChatAnswer } from '@/components/Chat';
+import { orderApi } from "@/api/order";
+import { chatApi } from '@/api/chat';
+import { formatEchart, isNumberComprehensive } from '@/utils/format';
+import { ORDER_OPTION_ENUM } from '@/utils/enum';
+import { getOrderAreaOptions } from './config/echartOptions'
+import * as echarts from 'echarts';
+import dayjs from 'dayjs';
+
+import { useInfinite, useScroll, useChat } from '@/composables';
+
+const { recordList, isFetching, onScrolltolower, onReset } = useInfinite('/front/bigModel/qa/pageList', { module: 1 });
+const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
+const { chatDataSource, addChat, updateChat, clearChat, updateById } = useChat();
+
+let controller = new AbortController();
+let timer = null;
+
+const ANSWER_ID_KEY = '@@id@@';
+
+const chartInstance = {};
+
+const message = useMessage();
+const reportDate = ref();
+
+const workOrderParams = ref({
+  timeBegin: null,
+  timeEnd: null,
+  whichWay: 1,
+  checkGroup: [],
+})
+
+const switchActive = ref(false);
+const tabActive = ref("daily");
+
+const isLoading = ref(false);
+const recordActive = ref(null);
+
+const currenSessionId = ref(null);
+
+const isExistInHistory = computed(() => (recordList.value.findIndex(({ sessionId: sId }) => sId === unref(currenSessionId)) === -1));
+const isChart = computed(() => tabActive.value == 'customDaily');
+const chatDataSourceItem = computed(() => unref(chatDataSource)[chatDataSource.value.length - 1]);
+
+const resetFormData = () => {
+  workOrderParams.value = {
+    timeBegin: null,
+    timeEnd: null,
+    whichWay: 1,
+    checkGroup: [],
+  }
+}
+
+const resetState = () => {
+  tabActive.value = 'daily';
+
+  currenSessionId.value = null;
+
+  recordActive.value = null;
+
+  reportDate.value = null;
+
+  resetFormData();
+
+  clearChat();
+}
+
+// 新建对话
+const handleCreateDialog = async () => {
+  message.destroyAll();
+
+  if (unref(isLoading)) {
+    return message.warning('当前对话生成中');
+  }
+
+  if (!unref(chatDataSource).length) {
+    return message.info('已切换最新会话');
+  }
+
+  resetState();
+}
+
+// 查询对话详情
+const handleChatDetail = async ({ sessionId }) => {
+  isLoading.value = false;
+
+  controller.abort();
+
+  if(currenSessionId.value === sessionId) return;
+
+  recordActive.value = sessionId;
+
+  const { data } = await chatApi.getAnswerHistoryDetail({ sessionId });
+
+  let echartData = [];
+  let whichWay = null;
+  chatDataSource.value = data.map(item => {
+    if ( item.remark ) {
+      const remark = JSON.parse(item.remark);
+      whichWay = remark.whichWay;
+      echartData = item.echartWithTableData = formatData(remark);
+    }
+
+    return { ...item, loading: false };
+  })
+ 
+  echartData.length && createEchart(echartData, whichWay);
+
+  currenSessionId.value = sessionId;
+
+  resetFormData();
+
+  scrollToBottom();
+}
+
+// 请求
+const onRegenerate = async (question, options) => {
+  controller = new AbortController();
+
+  const sessionId = unref(currenSessionId);
+  const params = {
+    data: {
+      sessionId,
+      showVal: question,
+      question,
+      module: 1,
+      isStrong: Number(unref(switchActive)),
+      reportDate: reportDate.value,
+      ...options,
+    },
+    signal: controller.signal,
+    onDownloadProgress: ({ event }) => {
+      const xhr = event.target;
+      const { responseText } = xhr;
+      const [answer] = responseText.split(ANSWER_ID_KEY);
+
+      updateChat({
+        ...chatDataSourceItem.value,
+        sessionId,
+        showVal: question,
+        answer,
+        loading: true,
+        delayLoading: false,
+        innerLoading: false
+      })
+
+      scrollToBottomIfAtBottom();
+    }
+  }
+
+  try {
+    const { data } = await chatApi.getChatStream(params);
+
+    const [answer, id] = data.split(ANSWER_ID_KEY);
+
+    updateChat({
+      ...chatDataSourceItem.value,
+      id,
+      sessionId,
+      showVal: question,
+      answer,
+      loading: false,
+      delayLoading: false,
+      innerLoading: false
+    })
+
+    scrollToBottomIfAtBottom();
+  }
+  catch (error) {
+    console.log("取消了请求 - catch", error);
+  }
+  finally {
+    isLoading.value = false;
+    onReset();
+  }
+}
+
+// 提交问题
+const handleSubmit = async (question, params) => {
+
+  if (unref(isExistInHistory)) {
+    const { data: sessionId } = await chatApi.getChatSessionTag();
+    currenSessionId.value = sessionId;
+  }
+
+  isLoading.value = true;
+
+  const option = {
+    sessionId: unref(currenSessionId),
+    showVal: question,
+    answer: '',
+    loading: true,
+    delayLoading: true,
+    echartWithTableData: []
+  }
+
+  if (isChart.value) {
+    const { data } = await orderApi.postOrderChart(params);
+    const reuslt = formatData({...data, whichWay: workOrderParams.value.whichWay});
+
+    option.echartWithTableData = reuslt;
+  }
+
+  addChat({
+    ...option,
+    innerLoading: true,
+  });
+
+  timer = setTimeout(() => {
+
+    updateChat({
+      ...chatDataSourceItem.value,
+      delayLoading: true,
+      innerLoading: false,
+    })
+
+    createEchart(option.echartWithTableData, workOrderParams.value.whichWay);
+      
+    scrollToBottom();
+
+    onRegenerate(question, params);
+    
+  }, 2 * 1000);
+}
+
+// 处理推荐问题
+const handleCreateOrder = async () => {
+  const { timeBegin, timeEnd, whichWay, checkGroup } = unref(workOrderParams);
+  const startDateTime = dayjs(timeBegin);
+  const endDateTime = dayjs(timeEnd);
+  let params = {};
+
+  let question = `${reportDate.value}智慧工单分析报告`;
+
+  if (tabActive.value === 'customDaily') {
+    const thirtyDaysBeforeEndTime = endDateTime.subtract(31, 'day');
+    const isStartAfterThirtyDaysBeforeEnd = startDateTime.isAfter(thirtyDaysBeforeEndTime);
+
+    if (!timeBegin) return message.warning('请选择开始日期');
+    if (!timeEnd) return message.warning('请选择结束日期');
+    if (!isStartAfterThirtyDaysBeforeEnd) return message.warning('只可生成一个月区间的工单报告');
+    if (!checkGroup.length) return message.warning('请至少选择一项指标项');
+    
+    params = {
+      timeBegin,
+      timeEnd,
+      whichWay
+    }
+    checkGroup.forEach(key => params[key] = true);
+
+    question = `${startDateTime.format("MM月DD日")}-${endDateTime.format("MM月DD日")}的在线仪表的日报工单`
+
+  } else {
+
+    if (!reportDate.value) return message.warning('请选择时间');
+
+  }
+
+  handleSubmit(question, params)
+}
+
+// 创建echart图形
+const createEchart = (echartData, whichWay) => {
+  setTimeout(() => {
+    echartData.forEach(({ key, xAxisData, yAxisData,  }) => {
+      const dom = document.getElementById(key);
+      chartInstance[key] = echarts.init(dom, null, { width: 680, height: 300 });
+    
+      const option = getOrderAreaOptions({ xAxisData, yAxisData, whichWay });
+    
+      chartInstance[key].setOption(option);
+    })
+  })
+}
+
+// 格式化 echart 和 table 数据
+const formatData = (data) => {
+  const titleEnum = {
+    jsGroup: '进水指标',
+    csGroup: '出水指标',
+    hyGroup: '化验指标'
+  }
+  const { whichWay, jsGroup, csGroup, hyGroup } = data;
+  return Object.entries({jsGroup, csGroup, hyGroup}).map(([key, value]) => {
+    if ( value.length ) {
+      const [ xAxisData, yAxisData ] = formatEchart(value);
+      const [ item ] = value; 
+      const columns = Object.keys(item).map(k => {
+        const wihteKeyList = ['jsSlq', 'csSlqc'];
+        let unit = '';
+
+        if(wihteKeyList.includes(k)) {
+          unit = whichWay === 1? '(m³/h)' : '(m³/d)';
+        } else if (k !== 'time') {
+          unit= '(mg/L)';
+        }
+
+        return {
+          title: ORDER_OPTION_ENUM[k] + unit,
+          key: k,
+          width: '150px',
+          align: 'center'
+        }
+      })
+      const data = value.map(item => {
+        Object.entries(item).forEach(([k, v]) => {
+          !v && v!=0 && (item[k] = '-');
+          if (isNumberComprehensive(v)) {
+            item[k] = v ? Number(v.toFixed(2)) : 0
+          }
+        })
+        return item;
+      })
+
+      return {
+        key: key,
+        title: titleEnum[key],
+        xAxisData,
+        yAxisData,
+        columns,
+        data
+      }
+    }
+  }).filter(Boolean);
+}
+
+// 返回
+const handleback = async () => {
+  clearInterval(timer);
+  await chatApi.getStopChatStream(currenSessionId.value);
+  resetState();
+}
+
+// 删除历史对话
+const handeChatDelete = async (id) => {
+  await chatApi.deleteHistory(id);
+  onReset();
+  clearChat();
+  message.success('删除成功');
+}
+
+const handleTabChange = (val) => {
+  tabActive.value = val;
+}
+
+const dateStartDisabled = (timestamp) => {
+  const { timeEnd } = workOrderParams.value;
+  if (timeEnd) {
+    const currentDate = dayjs(timestamp);
+    const endDateValue = dayjs(timeEnd);
+    return !currentDate.isBefore(endDateValue);
+  }
+}
+
+const dateEndDisabled = (timestamp) => {
+  const { timeBegin } = workOrderParams.value;
+  if (timeBegin) {
+    const currentDate = dayjs(timestamp);
+    const endDateValue = dayjs(timeBegin);
+    return !currentDate.isAfter(endDateValue);
+  }
+}
+
+onUnmounted(() => {
+  controller.abort();
+  Object.keys(chartInstance).forEach(key => chartInstance[key].clear());
+})
+</script>
+
+<template>
+  <section class="flex items-start h-full">
+
+    <TheSubMenu title="智能工单" @scrollToLower="onScrolltolower" :loading="isFetching">
+      <template #top>
+        <div class="create-btn px-[11px] pb-[22px]">
+          <BaseButton @click="handleCreateDialog" icon-name="tool-add-circle">新建工单</BaseButton>
+        </div>
+      </template>
+
+      <div class="pr-[4px] text-[#5e5e5e]">
+        <p v-show="!recordList.length" class="pt-[30px] text-[12px] text-[#999] text-center">暂无工单数据</p>
+        <RecodeCardItem v-for="item, index in recordList" :key="item.sessionId + index" :title="item.showVal"
+          :time="item.createTime" :data-item="item"
+          :class="{ 'recode-card-item_active': recordActive === item.sessionId }" @on-click="handleChatDetail"
+          @on-delete="handeChatDelete" />
+      </div>
+    </TheSubMenu>
+
+    <TheChatView ref="scrollRef" :is-footer="false" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback">
+      <ChatWelcome title="您好,我是LibraAI智慧工单助手" :sub-title="[
+        '基于大语言模型的智能工单分析助手,可以为您实现数据分析及数据解读',
+        '选择日期并为您生成日报分析'
+      ]" v-if="!chatDataSource.length" />
+
+      <div class="conversation-item" v-show="chatDataSource.length">
+        <div v-for="item in chatDataSource" :key="item.sessionId">
+          <ChatAsk :content="item.showVal" :sessionId="item.sessionId"></ChatAsk>
+          <ChatAnswer
+            :id="item.id"
+            :content="item.answer"
+            :loading="item.loading"
+            :delay-loading="item.delayLoading"
+            :isSatisfied="item.isSatisfied"
+            :toggleVisibleIcons="false"
+            loadingText="数据分析中,由于数据查询需要一些时间,请您耐心等候..."
+            @on-click-icon="params => updateById(params)"
+          >
+            <main v-show="!item.innerLoading">
+              <div 
+                class="area_inner"
+                v-for="(item, index) in item.echartWithTableData"
+                :key="index"
+              >
+                <div class="echart-warpper" >
+                  <span class="mb-[10px] #1A2029 font-bold">{{ item.title }}</span>
+                  <div :id="item.key" class="w-[680px] h-[300px]"></div>
+                </div>
+                
+                <div class="w-[680px]" style="max-width: 680px; overflow-x: auto;">
+                  <NDataTable
+                    bordered
+                    size="small"
+                    :scroll-x="item.columns.length > 4 ? 1200: 670"
+                    :max-height="250"
+                    :single-line="false"
+                    :columns="item.columns"
+                    :data="item.data"
+                  >
+                    <template #empty>
+                      <span class="leading-[32px]">暂无数据</span>
+                    </template>
+                  </NDataTable>
+                </div>
+              </div>
+            </main>
+          </ChatAnswer>
+        </div>
+      </div>
+
+      <div class="order-container px-[60px] py-[30px] mt-[36px] rounded-[10px] bg-[#fff]" v-if="!chatDataSource.length">
+        <div
+          class="flex items-center justify-start space-x-[24px] pb-[20px] border-b-[1px] border-solid border-[#F1F1F1]">
+          <n-tabs type="segment" animated style="width: 188px;" size="large" :on-update:value="handleTabChange">
+            <n-tab name="daily">日报工单</n-tab>
+            <n-tab name="customDaily">自定义工单</n-tab>
+          </n-tabs>
+          <span class="text-[12px] text-[#8F959C]">
+            {{ tabActive === 'daily' ? '选择日期后为您生成日报工单' : '选择内容后为您生成工单' }}
+          </span>
+        </div>
+
+        <main class="order pt-[20px] text-[#1A2029]" v-show="tabActive === 'daily'">
+          <div class="flex items-center justify-start text-[16px] space-x-[16px]">
+            <span class="font-bold">选择时间</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="选择日期" :readonly="true" v-model:formatted-value="reportDate"
+                value-format="yyyy-MM-dd">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+          </div>
+
+          <dl class="pt-[20px] text-[#1A2029] space-y-[16px]">
+            <dt class="text-[16px] font-bold leading-[22px]">报告内容</dt>
+            <dd>1、水质数据、生化数据情况</dd>
+            <dd>2、各项指标数据分析</dd>
+            <dd>3、对于工艺调整方面、数据趋势方面的措施与建议</dd>
+          </dl>
+        </main>
+
+        <main class="order pt-[20px] text-[#1A2029]" v-show="tabActive === 'customDaily'">
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">选择时间</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="开始日期" :readonly="true" v-model:formatted-value="workOrderParams.timeBegin"
+                value-format="yyyy-MM-dd" :is-date-disabled="dateStartDisabled">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+            <span class="text-[16px] text-[#8F959C]">至</span>
+            <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
+              <NDatePicker placeholder="结束日期" :readonly="true" v-model:formatted-value="workOrderParams.timeEnd"
+                value-format="yyyy-MM-dd" :is-date-disabled="dateEndDisabled">
+                <template #date-icon>
+                  <SvgIcon name="tool-arrow-bottom"></SvgIcon>
+                </template>
+              </NDatePicker>
+            </div>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">统计方式</span>
+            <n-radio-group v-model:value="workOrderParams.whichWay" name="radiogroup2">
+              <n-radio :value="1">在线仪表</n-radio>
+              <n-radio :value="0">日报</n-radio>
+            </n-radio-group>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">进水指标</span>
+            <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+              <n-checkbox value="jsSlq">进水水量</n-checkbox>
+              <n-checkbox value="jsCod">COD</n-checkbox>
+              <n-checkbox value="jsTn">总氮</n-checkbox>
+              <n-checkbox value="jsTp">总磷</n-checkbox>
+              <n-checkbox value="jsNh3">氨氮</n-checkbox>
+              <n-checkbox value="jsSs">SS</n-checkbox>
+            </n-checkbox-group>
+          </div>
+
+          <div class="flex items-center justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">出水指标</span>
+            <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+              <n-checkbox value="csSlqc">出水水量</n-checkbox>
+              <n-checkbox value="csCod">COD</n-checkbox>
+              <n-checkbox value="csTn">总氮</n-checkbox>
+              <n-checkbox value="csTp">总磷</n-checkbox>
+              <n-checkbox value="csNh3">氨氮</n-checkbox>
+              <n-checkbox value="csSs">SS</n-checkbox>
+            </n-checkbox-group>
+          </div>
+
+          <div class="flex items-start justify-start mb-[20px] text-[16px] space-x-[16px]">
+            <span class="font-bold">化验指标</span>
+            <div class="w-[500px]">
+              <n-checkbox-group v-model:value="workOrderParams.checkGroup" name="radiogroup2">
+                <n-checkbox value="no3Hlj1Jqr">1#好氧池硝酸盐</n-checkbox>
+                <n-checkbox value="no3Hlj2Jqr">2#好氧池硝酸盐</n-checkbox>
+                <n-checkbox value="nh31Jqr">1#缺氧氨氮</n-checkbox>
+                <n-checkbox value="nh32Jqr">2#缺氧氨氮</n-checkbox>
+                <n-checkbox value="no3Qyc1Jqr">1#缺氧池硝酸盐</n-checkbox>
+                <n-checkbox value="no3Qyc2Jqr">2#缺氧池硝酸盐</n-checkbox>
+                <n-checkbox value="tpRccJqr">二沉池正磷酸盐</n-checkbox>
+              </n-checkbox-group>
+            </div>
+          </div>
+
+          <dl class="text-[#1A2029] space-y-[16px]">
+            <dt class="text-[16px] font-bold leading-[22px]">报告内容</dt>
+            <dd>1、数据情况</dd>
+            <dd>2、各项指标数据分析及措施与建议</dd>
+          </dl>
+        </main>
+
+        <footer class="pt-[24px]">
+          <button class="btn-primary" @click="handleCreateOrder">立即生成</button>
+        </footer>
+      </div>
+
+    </TheChatView>
+  </section>
+</template>
+
+<style scoped lang="scss">
+.n-input__input-el {
+  font-size: 12px !important;
+}
+
+.area_inner {
+  display: flex;
+  align-items: center;
+  flex-flow: column;
+  padding-bottom: 20px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  background: #eff8fc;
+}
+
+.list-item {
+  margin-bottom: 16px;
+
+  .title {
+    font-size: 14px;
+    font-weight: bold;
+    line-height: 20px;
+  }
+
+  .item-top {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 10px;
+
+    .num {
+      font-size: 12px;
+      font-weight: 400;
+      color: #B0B7C0;
+    }
+  }
+}
+
+
+.btn-primary {
+  width: 88px;
+  height: 32px;
+  border-radius: 6px;
+  background-color: #2454FF;
+  font-size: 14px;
+  line-height: 32px;
+  color: #fff;
+
+  &:hover {
+    background: #1D43CC;
+  }
+}
+
+.echart-warpper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-flow: column;
+  width: 100%;
+  padding: 20px 0;
+  border-radius: 8px;
+}
+</style>
+
+<style lang="scss">
+.order {
+
+  .n-input__input-el,
+  .n-input__placeholder {
+    font-size: 14px;
+  }
+}
+
+.order-container {
+  .n-tabs-tab__label {
+    font-size: 14px !important;
+    font-weight: bold;
+    color: #5E5E5E;
+  }
+
+  .n-tabs-tab--active {
+    .n-tabs-tab__label {
+      color: #1A2029;
+    }
+  }
+}
+</style>