WaterView.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <script setup lang="jsx">
  2. import { ref, unref, watch } from 'vue';
  3. import { NTabs, NTab } from 'naive-ui';
  4. import { BaseCard, BaseTable, ChatWelcome, SvgIcon, RecodeSquareCardItem, TheSubMenu, TheChatView } from "@/components";
  5. import { ChatAsk, ChatBaseCard, ChatAnswer } from '@/components/Chat';
  6. import { format } from "@/utils/format";
  7. import { waterApi } from '@/api/water';
  8. import { CustomModal } from "./components";
  9. import { useInfinite } from '@/composables/useInfinite';
  10. import { useRecommend } from '@/composables/useRecommend';
  11. import { useFetchStream } from '@/composables/useFetchStream';
  12. import { useScroll } from '@/composables/useScroll';
  13. const { recommendList } = useRecommend({type: 1});
  14. const { scrollRef, scrollToTop, scrollToBottomIfAtBottom } = useScroll();
  15. const { streamData, refetch } = useFetchStream("/grpc/decisionStream", { methdos: 'POST' }, false);
  16. const { recordList, isFetching, onScrolltolower, onRestore, addHistoryRecord } = useInfinite('/front/bigModel/warning/pageList', { type: 0, warningStatus: 0 });
  17. let controller = new AbortController();
  18. // 获取最终回答流数据参数
  19. const flowParams = {
  20. feedback: '',
  21. category: '',
  22. warningId: ''
  23. };
  24. const reportAnswer = ref('');
  25. const alertAnswer = ref([]);
  26. const checkedAlertData = ref([]);
  27. const textDataSources = ref(null);
  28. const answerAlertDataSources = ref([]);
  29. // 进出水数据
  30. const jsTableData = ref([]);
  31. const csTableData = ref([]);
  32. const visible = ref(false);
  33. const renderRowDom = ({ row, key }) => {
  34. const { exceed, value } = row[key] || {};
  35. const cls = exceed ? 'text-[#F44C49] font-bold' : 'text-[1A2029]'
  36. return (<span class={ cls }>{value} {exceed && <i>↑</i>}</span>)
  37. }
  38. const columns = [
  39. {
  40. title: '流量(m³/h)',
  41. key: 'name',
  42. titleAlign: 'center',
  43. align: 'center',
  44. className: 'small',
  45. width: '80px',
  46. render: (row) => renderRowDom({ row, key: '流量' })
  47. },
  48. {
  49. title: 'COD(mg/L)',
  50. key: 'small',
  51. titleAlign: 'center',
  52. align: 'center',
  53. className: 'small',
  54. width: '80px',
  55. render: (row) => renderRowDom({ row, key: 'COD' })
  56. },
  57. {
  58. title: 'TN(mg/L)',
  59. key: 'address',
  60. titleAlign: 'center',
  61. align: 'center',
  62. className: 'small',
  63. width: '80px',
  64. render: (row) => renderRowDom({ row, key: 'TN' })
  65. },
  66. {
  67. title: 'NH3-N(mg/L)',
  68. key: 'tags',
  69. titleAlign: 'center',
  70. align: 'center',
  71. className: 'small',
  72. width: '80px',
  73. render: (row) => renderRowDom({ row, key: 'NH3-N' })
  74. },
  75. {
  76. title: '总磷TP(mg/L)',
  77. key: 'COD',
  78. titleAlign: 'center',
  79. align: 'center',
  80. className: 'small',
  81. width: '80px',
  82. render: (row) => renderRowDom({ row, key: 'TP' })
  83. },
  84. {
  85. title: 'SS(mg/L)',
  86. key: '流量',
  87. titleAlign: 'center',
  88. align: 'center',
  89. className: 'age',
  90. width: '78px',
  91. render: (row) => renderRowDom({ row, key: 'SS' })
  92. }
  93. ]
  94. const inWaterTableData = ref([{ name: 1233, actions: "7.87" }]);
  95. // 新建对话
  96. const handleCreateDialog = () => {
  97. console.log("handleCreateDialog");
  98. }
  99. const handleLoad = () => {
  100. console.log("loading")
  101. }
  102. const handleModelVisible = () => {
  103. visible.value = true
  104. }
  105. /**
  106. * 报警详情
  107. */
  108. const handleOpenContent = async ({id, category}) => {
  109. const { data } = await waterApi.getWaringDetails(id);
  110. const showVal = JSON.parse(data.showVal);
  111. const { basic, jsData, csData } = showVal;
  112. const answer = JSON.parse(data.answer);
  113. const [ answerStrItem ] = answer;
  114. const answerObjItem = JSON.parse( answerStrItem );
  115. console.log( answerObjItem.biz );
  116. const textWhiteList = [
  117. { label: '报警时间', value: '', isWarning: false },
  118. { label: '报警值', value: 'mg/L', isWarning: true },
  119. { label: '管控值', value: 'mg/L', isWarning: false },
  120. { label: '标准值', value: 'mg/L', isWarning: false },
  121. { label: '报警级别', value: '', isWarning: false },
  122. { label: '报警次数', value: '', isWarning: false },
  123. { label: '状态', value: '', isWarning: false }
  124. ]
  125. if ( answerObjItem.biz === "DECISION_REPORT" ) {
  126. alertAnswer.value = [];
  127. reportAnswer.value = answer.map(item => {
  128. const itemParse = JSON.parse(item);
  129. return itemParse.message;
  130. }).join();
  131. } else {
  132. reportAnswer.value = '';
  133. const [ parseAnswer ] = answer.map(item => {
  134. const result = JSON.parse( item );
  135. result.message = Object.keys(result.message).map(key => ({ ...result.message[key], isActive: null }));
  136. return result;
  137. })
  138. console.log( parseAnswer?.message );
  139. alertAnswer.value = parseAnswer?.message;
  140. }
  141. textDataSources.value = format.textSorting(basic, textWhiteList);
  142. jsTableData.value = [jsData];
  143. csTableData.value = [csData];
  144. flowParams.category = category;
  145. flowParams.warningId = id;
  146. scrollToTop();
  147. }
  148. const onChangeTabs = warningStatus => {
  149. onRestore({ warningStatus })
  150. }
  151. const onRegenerate = async () => {
  152. let counter = 0;
  153. let str = 0;
  154. // const timer = setInterval(item => {
  155. // if( timer === 10 ) {
  156. // console.log("str", str);
  157. // clearInterval(timer);
  158. // }
  159. // counter++;
  160. // const data = {biz: "DECISION_REPORT", message: counter};
  161. // str += data.message + "||"
  162. // reportAnswer.value = str;
  163. // }, 1000)
  164. try {
  165. const obj = {"biz": "DECISION_ALERT", "message": {"2_30": {"id": "2_30", "mainType": "alert", "mainContent": "设备与电气类是否有故障发生", "options": ["否", "是"], "next": "", "checked": false}}}
  166. const result = Object.keys(obj.message).map(key => ({ ...obj.message[key], isActive: null }));
  167. console.log( "result", result );
  168. // refetch({
  169. // body: JSON.stringify(flowParams),
  170. // successHandler: data => {
  171. // const item = JSON.parse(data);
  172. // str += item.message;
  173. // reportAnswer.value = str;
  174. // scrollToBottomIfAtBottom()
  175. // },
  176. // doneHandler: () => {
  177. // alert("结束了")
  178. // }
  179. // })
  180. }
  181. catch(error) {
  182. console.log("exist error .....", error);
  183. }
  184. }
  185. // 回答选项点击
  186. const handlerAlertOptions = (item, index) => {
  187. const isExists = checkedAlertData.value.find(d => d.id === item.id);
  188. item.isActive = index;
  189. isExists ?? checkedAlertData.value.push( item );
  190. if ( unref(checkedAlertData).length === unref(alertAnswer).length ) {
  191. const tempArr = alertAnswer.value.map(({ id, options, isActive }) => ({ [id]: options[isActive] }));
  192. const tempArrToStr = JSON.stringify(tempArr);
  193. flowParams.feedback = JSON.stringify(tempArr).substring(1, tempArrToStr.length - 1);
  194. onRegenerate();
  195. }
  196. }
  197. </script>
  198. <template>
  199. <section class="flex items-start h-full">
  200. <TheSubMenu title="水质报警" @scrollToLower="onScrolltolower" :loading="isFetching">
  201. <template #top>
  202. <div class="border-[#DAE5ED]">
  203. <n-tabs type="line" justify-content="space-evenly">
  204. <n-tab name="oasis" tab="正在报警" @click="onChangeTabs(0)"></n-tab>
  205. <n-tab name="thebeatles" tab="历史报警" @click="onChangeTabs(1)"></n-tab>
  206. </n-tabs>
  207. </div>
  208. </template>
  209. <div class="px-[12px] py-[14px] text-[#5e5e5e]">
  210. <div class="grid grid-cols-1 gap-[12px]">
  211. <RecodeSquareCardItem
  212. v-for="item in recordList"
  213. :key="item.id"
  214. :item="item"
  215. @on-click="handleOpenContent"
  216. />
  217. </div>
  218. </div>
  219. </TheSubMenu>
  220. <TheChatView ref="scrollRef" :is-footer="false">
  221. <ChatWelcome title="您好,我是LibraAI工艺管控助手" card-title="常见处理方案:"
  222. :sub-title="[
  223. '报警分析功能具备实时监测与预警机制,检测到异常情况立即触发多种报警方式,推送相关',
  224. '工作人员确保问题及时处理。报警时间为每小时警报,请大家及时处理。'
  225. ]"
  226. :card-content="recommendList"
  227. v-if="!textDataSources"
  228. />
  229. <ChatBaseCard v-if="textDataSources">
  230. <div class="waring-answer-wrapper">
  231. <dl class="message-inner warning-info_medium ">
  232. <dt class="mb-[2px] font-bold text-[#1A2029]">{{ textDataSources?.value }}</dt>
  233. <dd v-for="item, index in textDataSources?.list" :key="index"><span :class="{'text-[#F44C49]': item.isWarning}">{{ item.label }}: {{ item.value }}</span></dd>
  234. </dl>
  235. <div class="table-inner">
  236. <div class="warning-table mb-[8px]">
  237. <div class="title">
  238. <span>当前进水数据:</span>
  239. </div>
  240. <div class="main">
  241. <BaseTable :columns="columns" :data="jsTableData"></BaseTable>
  242. </div>
  243. </div>
  244. <div class="warning-table">
  245. <div class="title">
  246. <span>当前出水数据:</span>
  247. </div>
  248. <div class="main">
  249. <BaseTable :columns="columns" :data="csTableData"></BaseTable>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. </ChatBaseCard>
  255. <!-- report -->
  256. <ChatAnswer
  257. :loading="true"
  258. :toggleVisibleIcons="false"
  259. :content="reportAnswer"
  260. v-show="reportAnswer"
  261. ></ChatAnswer>
  262. <!-- alert -->
  263. <ChatBaseCard v-show="alertAnswer.length">
  264. <p class="mb-[15px] font-bold text-[#1A2029]">需要确定以下问题,完成决策方案:</p>
  265. <ul class="radio-wrapper space-y-[14px]">
  266. <li class="flex items-center" v-for="item in alertAnswer" :key="item.id">
  267. <p class="mr-[14px]">{{ item.mainContent }}</p>
  268. <p class="radio-btn-group space-x-[14px]">
  269. <span
  270. :key="index"
  271. :class="['radio-btn', { active: item.isActive === index }]"
  272. v-for="val,index in item.options"
  273. @click="handlerAlertOptions(item, index)"
  274. >{{ val }}</span>
  275. </p>
  276. </li>
  277. </ul>
  278. </ChatBaseCard>
  279. <!-- <BaseCard :loading="true">
  280. <div class="waring-answer-wrapper">
  281. <dl class="message-inner warning-info_medium ">
  282. <dt class="mb-[2px] font-bold text-[#1A2029]">{{ textDataSources?.title }}</dt>
  283. <dd><span>报警时间:2024-4-25 21:00</span></dd>
  284. <dd><span class="text-[#F44C49]">报警值:7.87mg/L</span></dd>
  285. <dd><span>标准值:7.1mg/L</span></dd>
  286. <dd><span>报警级别:二级</span></dd>
  287. <dd><span>报警次数:33</span></dd>
  288. <dd><span>状态:报警中</span></dd>
  289. </dl>
  290. <div class="table-inner">
  291. <div class="warning-table mb-[8px]">
  292. <div class="title">
  293. <span>当前进水数据:</span>
  294. </div>
  295. <div class="main">
  296. </div>
  297. </div>
  298. <div class="warning-table">
  299. <div class="title">
  300. <span>当前出水数据:</span>
  301. </div>
  302. <div class="main">
  303. </div>
  304. </div>
  305. </div>
  306. </div>
  307. </BaseCard> -->
  308. <!-- <BaseCard>
  309. <p class="flex-1 text-[15px] leading-[24px]">
  310. COD,即化学需氧量,是衡量水中有机物质含量的重要指标。它反映了水中可氧化有机物的量,通常用来评估水体的污染程度。水中的有机物主要来源于工业废水、生活污水、农药残留等,这些有机物不仅会导致水质变差,还会对生物和人类健康产生负面影响。因此,通过测定COD值,可以了解水中有机污染物的含量,进而评估水体的污染程度。这对于制定环境保护政策、控制污染源、保障水资源安全等方面都具有重要的指导意义
  311. </p>
  312. </BaseCard>
  313. <button class="
  314. px-[30px] py-[10px] mb-[20px]
  315. rounded-[8px]
  316. bg-white text-[13px]
  317. text-[#5E5E5E] hover:text-[#2454FF]"
  318. @click="handleModelVisible"
  319. >
  320. 水质预测推演
  321. </button> -->
  322. </TheChatView>
  323. </section>
  324. <CustomModal v-model:visible="visible"></CustomModal>
  325. </template>
  326. <style scoped lang="scss">
  327. .base-card-container {
  328. margin-bottom: 20px;
  329. }
  330. .warning-item-inner {
  331. position: relative;
  332. padding: 20px 8px 8px 8px;
  333. border-radius: 4px;
  334. background: #DDE5EF;
  335. .tips {
  336. position: absolute;
  337. width: 36px;
  338. height: 14px;
  339. top: 0;
  340. right: 0px;
  341. border-radius: 0px 4px 0px 4px;
  342. font-size: 8px;
  343. text-align: center;
  344. line-height: 14px;
  345. &_warning,
  346. &_being {
  347. color: #F44C49;
  348. background: #FFF0ED;
  349. }
  350. &_success {
  351. color: #51BF8E;
  352. background: #E9FAF2;
  353. }
  354. &_close {
  355. color: #999999;
  356. background: #D5D5D5;
  357. }
  358. }
  359. }
  360. .warning-info {
  361. line-height: 16px;
  362. font-size: 11px;
  363. color: #5E5E5E;
  364. dd {
  365. margin-top: 4px;
  366. }
  367. &_medium {
  368. line-height: 26px;
  369. font-size: 14px;
  370. color: #1A2029;
  371. }
  372. }
  373. // 回答区域卡片
  374. .waring-answer-wrapper {
  375. @include flex(x, start, between);
  376. .message-inner {
  377. width: 194px;
  378. flex-shrink: 0;
  379. }
  380. .table-inner {
  381. @include flex(y, end, center);
  382. padding-left: 20px;
  383. border-left: 1px solid #F1F1F1;
  384. .warning-table {
  385. .title {
  386. margin-bottom: 8px;
  387. line-height: 16px;
  388. font-size: 12px;
  389. font-weight: bold;
  390. color: #1A2029;
  391. }
  392. }
  393. }
  394. }
  395. .radio-wrapper {
  396. .radio-btn-group {
  397. @include flex(x, center, center);
  398. font-size: 14px;
  399. text-align: center;
  400. color: #5E5E5E;
  401. .radio-btn {
  402. width: 62px;
  403. height: 28px;
  404. border-radius: 4px;
  405. background: #F4F6F8;
  406. font-size: 14px;
  407. line-height: 26px;
  408. cursor: pointer;
  409. &.active, &:hover {
  410. color: #2454FF;
  411. background: #E2F1FF;
  412. }
  413. &.active {
  414. background: #E2F1FF url('@/assets/images/chat/bg-raido-check.png') right bottom no-repeat;
  415. }
  416. }
  417. }
  418. }
  419. </style>