Sfoglia il codice sorgente

feat: 基本布局

sunxiao 9 mesi fa
parent
commit
bde94c1c81

+ 1 - 1
package-lock.json

@@ -10,7 +10,7 @@
       "dependencies": {
         "axios": "^1.6.8",
         "load-awesome": "^1.1.0",
-        "naive-ui": "^2.38.2",
+        "naive-ui": "^2.24.0",
         "pinia": "^2.1.7",
         "sass": "^1.77.1",
         "sass-loader": "^14.2.1",

+ 7 - 9
src/App.vue

@@ -4,8 +4,6 @@ import { NConfigProvider } from 'naive-ui';
 import type { GlobalThemeOverrides } from 'naive-ui';
 import { SelectProps, InputProps, TableProps } from 'naive-ui'
 
-import 'load-awesome/css/ball-running-dots.min.css';
-
 // type SelectThemeOverrides = NonNullable<SelectProps['themeOverrides']>
 // type SelectThemeOverrides = NonNullable<InputProps['themeOverrides']>
 const primaryColor = '#1A2029';
@@ -59,13 +57,13 @@ const themeOverrides: GlobalThemeOverrides = {
     tabFontWeightActive: 'bold'
   },
   DataTable: {
-    thTextColor: '#1A2029',
-    tdTextColor: primaryColor,
-    borderRadius: '4px',
-    borderColor: '#D7D7D7',
-    thPaddingMedium: '10px',
-    tdPaddingMedium: '10px',
-    // color
+    // thTextColor: '#1A2029',
+    // tdTextColor: primaryColor,
+    // borderRadius: '4px',
+    // borderColor: '#D7D7D7',
+    // thPaddingMedium: '8px 10px',
+    // tdPaddingMedium: '7px 10px',
+    // fontSizeMedium: '11px'
   }
   // Menu: {
   //   itemTextColorHorizontal: '#161616',

BIN
src/assets/images/chat/bg-raido-check.png


BIN
src/assets/images/login/bg-login.png


BIN
src/assets/images/login/bg-title.png


BIN
src/assets/images/login/logo.png


+ 3 - 0
src/assets/svgs/login/warning-tips.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 0C5.84 0 3.84 0.8 2.32 2.32C0.88 3.84 0 5.84 0 7.92C0 12.4 3.52 16 7.92 16C12.4 16 16 12.48 16 8.08C16 3.6 12.48 0 8 0ZM8 14.96C4.08 14.96 0.96 11.84 1.04 7.92C1.04 4.08 4.16 0.96 8 1.04C11.84 1.04 14.96 4.16 14.96 8C14.96 11.92 11.92 14.96 8 14.96ZM7.2 6.64V9.2C7.2 9.76 7.52 9.92 8.08 9.92C8.64 9.92 8.64 9.6 8.64 9.2C8.72 7.68 8.72 6.16 8.8 4.64C8.88 4.08 8.8 3.68 8 3.68C7.2 3.68 7.12 4 7.12 4.64L7.2 6.64ZM9.04 11.92C8.96 12.56 8.56 12.88 8 12.88C7.44 12.88 7.04 12.56 6.96 12C6.88 11.36 7.28 11.04 7.92 11.04C8.56 10.96 8.88 11.36 9.04 11.92Z" fill="#FF7152"/>
+</svg>

+ 11 - 0
src/assets/svgs/tool/voice-close.svg

@@ -0,0 +1,11 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.01" d="M21.5 9H15V15.5H21.5V9Z" fill="#5E5E5E"/>
+<mask id="mask0_1039_722" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="15" y="9" width="7" height="7">
+<path d="M21.5 9H15V15.5H21.5V9Z" fill="#333333"/>
+</mask>
+<g mask="url(#mask0_1039_722)">
+<path d="M20.3674 10.1429L16.1248 14.3856" stroke="#5E5E5E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M16.1248 10.1429L20.3674 14.3856" stroke="#5E5E5E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<path d="M12 3V21C8.5 21 5.89925 16.4195 5.89925 16.4195H3C2.44771 16.4195 2 15.9718 2 15.4195V8.5054C2 7.9531 2.44771 7.5054 3 7.5054H5.89925C5.89925 7.5054 8.5 3 12 3Z" stroke="#5E5E5E" stroke-width="1.3" stroke-linejoin="round"/>
+</svg>

+ 32 - 4
src/components/BaseCard/index.vue

@@ -1,22 +1,44 @@
 <script setup>
+import { ref } from 'vue';
 import { SvgIcon } from '@/components';
 
+import 'load-awesome/css/ball-circus.min.css';
+
+const iconLoading = ref(false);
+
 defineProps({
   toggleVisibleIcons: {
     type: Boolean,
     default: false
+  },
+  loading: {
+    type: Boolean,
+    default: false
   }
 })
+
+setTimeout(() => {
+
+  // loading.value = true;
+
+}, 2000)
+
 </script>
 
 <template>
   <div class="card-container">
     <div class="card-inner">
-      <div class="chat-icon">
-        <SvgIcon name="common-logo" size="30" />
+      <div class="chat-icon relative">
+        <SvgIcon name="common-logo" class="chat-logo" size="30" :style="{ scale: loading ? 0 : 1 }"/>
+        <div style="color: #2454FF" class="la-ball-circus la-dark la-sm" v-show="loading">
+          <div v-for="item in 5" :key="item"></div>
+        </div>
       </div>
-      <div class="flex-1 pt-[6px] ml-[16px] text-[15px] leading-[24px]">
-        <slot />
+      <div class="flex-1 pt-[4px] ml-[16px] text-[15px] leading-[24px]">
+        <p class="font-bold text-[#1A2029]" v-show="loading">内容生成中...</p>
+        <div v-show="!loading">
+          <slot />
+        </div>
       </div>
     </div>
     <ul class="answer-btn-group" v-if="toggleVisibleIcons">
@@ -36,6 +58,11 @@ defineProps({
 </template>
 
 <style scoped lang="scss">
+.chat-logo {
+  position: absolute;
+  transition: all 1s;
+}
+
 .card-container {
   margin-bottom: 20px;
 
@@ -46,6 +73,7 @@ defineProps({
     background: #fff;
 
     .chat-icon{
+      @include flex(x, center, center);
       width: 32px;
       height: 32px;
       border: 1px solid #E5ECF0;

+ 6 - 1
src/components/BaseInput/index.vue

@@ -3,6 +3,8 @@ import { ref } from 'vue';
 import { NSelect, NInput, NSwitch } from 'naive-ui';
 import SvgIcon from '@/components/SvgIcon';
 
+import 'load-awesome/css/ball-running-dots.min.css';
+
 const inputInstRef = ref(null);
 const inputValue = ref(null);
 const isFocusState = ref(false);
@@ -26,7 +28,10 @@ const handleInpFocus = () => {
     </div>
     <div class="submit-btn">
       <button class="btn bg-[#1A2029] hover:bg-[#3C4148]">
-        <SvgIcon name="tool-send-plane" size="22"></SvgIcon>
+        <!-- <SvgIcon name="tool-send-plane" size="22"></SvgIcon> -->
+          <div style="color: #fff" class="la-ball-running-dots la-sm">
+            <div v-for="item in 5" :key="item"></div>
+          </div>
       </button>
     </div>
   </div>

+ 53 - 0
src/components/BaseTable/index.vue

@@ -0,0 +1,53 @@
+<!-- 主要用于单独重置 data-table 样式 -->
+<script setup lang="ts">
+import { DataTableProps, NDataTable } from 'naive-ui'
+
+type DataTableThemeOverrides = NonNullable<DataTableProps['themeOverrides']>
+
+defineProps({
+  columns: {
+    type: Array,
+    default: []
+  },
+  data: {
+    type: Array,
+    default: []
+  }
+})
+
+const primaryColor = '#1A2029';
+const dataTableThemeOverrides: DataTableThemeOverrides = {
+  thTextColor: '#1A2029',
+  tdTextColor: primaryColor,
+  borderRadius: '4px',
+  borderColor: '#D7D7D7',
+  thPaddingMedium: '8px 0px',
+  // tdPaddingMedium: '0px 10px',
+  fontSizeMedium: '11px',
+  emptyPadding: '0px'
+}
+
+const rowClassName = (row) => {
+  return 'custom-row'
+} 
+</script>
+
+<template>
+  <n-data-table bordered align="center" :theme-overrides="dataTableThemeOverrides" :single-line="false"
+    :columns="columns" :data="data" :row-class-name="rowClassName">
+    <template #empty>
+      <span class="leading-[32px]">暂无数据</span>
+    </template>
+
+  </n-data-table>
+
+</template>
+
+<style scoped lang="scss">
+:deep(.custom-row .small) {
+  color: red !important;
+  line-height: 32px;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+</style>

+ 14 - 11
src/components/Layout/TheChatView.vue

@@ -1,22 +1,25 @@
 <script setup>
-import { ref } from 'vue';
+import { ref, unref,computed } from 'vue';
 import { NSelect } from 'naive-ui';
 import SvgIcon from '@/components/SvgIcon';
 import BasePopover from "@/components/BasePopover";
 
-const value = ref();
+const selectValue = ref('water');
+const voiceSwitchStatus = ref(false);
 
 const options = [
   {
-    label: "水厂的名称不要太长",
-    value: 'song0',
-  },
-  {
-    label: '信义污水厂',
-    value: 'song1'
-  },
+    label: "信义污水厂",
+    value: 'water',
+  }
 ]
 
+const voiceName = computed(() => unref(voiceSwitchStatus) ? 'tool-voice-close' : 'tool-voice-open')
+
+const changeVoiceStatus = () => {
+  voiceSwitchStatus.value = !voiceSwitchStatus.value;
+}
+
 </script>
 
 <template>
@@ -30,11 +33,11 @@ const options = [
           </div>
         </BasePopover>
         <!-- 声音开关 -->
-        <SvgIcon name="tool-voice-open" size="24" class="cursor-pointer"></SvgIcon>
+        <SvgIcon :name="voiceName" size="24" class="cursor-pointer" @click="changeVoiceStatus"></SvgIcon>
         <!--分割线 -->
         <div class="h-[24px] border-r-[1px] border-color-[#D3D0E1]"></div>
         <!-- 水厂select -->
-        <NSelect v-model:value="value" placeholder="123" :options="options" class="w-[114px]" size="medium"
+        <NSelect v-model:value="selectValue" placeholder="" :options="options" class="w-[114px]" size="medium"
           :consistent-menu-width="false" />
         <!-- 用户头像 -->
         <div class="flex items-center">

+ 0 - 1
src/components/Layout/TheLogo.vue

@@ -1,7 +1,6 @@
 <script setup>
 import { storeToRefs } from 'pinia';
 import { useAppStore } from '@/stores/modules/app';
-import { NPopover } from 'naive-ui';
 
 import SvgIcon from '@/components/SvgIcon';
 import BasePopover from "@/components/BasePopover"

+ 0 - 1
src/components/Layout/TheSubMenu.vue

@@ -37,7 +37,6 @@ const handleLoad = () => emits('on-load');
     <div class="sub-menu-main w-full h-full">
       <NInfiniteScroll class="h-full" :distance="10" @load="handleLoad">
         <slot></slot>
-        
         <div class="footer-loading w-full h-[50px]">
           加载更多
         </div>

+ 2 - 0
src/components/index.js

@@ -2,6 +2,7 @@ import BaseButton from './BaseButton';
 import BaseInput from './BaseInput';
 import BasePopover from './BasePopover';
 import BaseCard from './BaseCard';
+import BaseTable from './BaseTable';
 
 import TheChatView from './Layout/TheChatView';
 import TheLogo from './Layout/TheLogo';
@@ -19,6 +20,7 @@ export {
   BaseInput,
   BasePopover,
   BaseCard,
+  BaseTable,
   
   TheChatView,
   TheLogo,

+ 5 - 0
src/router/index.js

@@ -3,6 +3,11 @@ import { createRouter, createWebHistory } from 'vue-router'
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
+    {
+      path: '/login',
+      name: 'Login',
+      component: () => import('@/views/login/LoginView.vue')
+    },
     {
       path: '/',
       name: 'theBaseLayout',

+ 174 - 88
src/views/analyse/WaterView.vue

@@ -1,63 +1,77 @@
 <script setup lang="jsx">
 import { ref, h } from 'vue';
-import { NTabs, NTab, NEllipsis, NDataTable } from 'naive-ui';
+import { NTabs, NTab, NEllipsis, NModal, NInput  } from 'naive-ui';
 import {
   BaseButton,
   BaseInput,
   BaseCard,
+  BaseTable,
   ChatWelcome,
   SvgIcon,
   RecodeCardItem,
   TheSubMenu,
   TheChatView,
 } from "@/components";
+import { CustomModal } from "./components"
 
-
+const visible = ref(false);
 
 const columns = [
-    {
-      title: "流量(m³/h)",
-      key: 'name',
-      titleAlign: 'center',
-      align: 'center',
-    },
-    {
-      title: 'COD(mg/L)',
-      key: 'age',
-      titleAlign: 'center',
-      align: 'center',
-    },
-    {
-      title: 'TN(mg/L)',
-      key: 'address',
-      titleAlign: 'center',
-      align: 'center',
-    },
-    {
-      title: 'NH3 -N(mg/L)',
-      key: 'tags',
-      titleAlign: 'center',
-      align: 'center',
-    },
-    {
-      title: '总磷TP(mg/L)',
-      key: 'actions',
-      titleAlign: 'center',
-      align: 'center',
-      render(row) {
-        // TODO: 需要调整,待后续请求参数确定
-        return (<span class={ row.actions > 7 ? 'text-[#F44C49] font-bold': 'text-[1A2029]' }>{row.actions} ↑</span>)
-      }
-    },
-    {
-      title: 'SS(mg/L)',
-      key: 'actions1',
-      titleAlign: 'center',
-      align: 'center',
+  {
+    title: (<span class="text-[11px]">COD</span>),
+    key: 'name',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'small',
+    width: '80px'
+  },
+  {
+    title: 'COD(mg/L)',
+    key: 'small',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'small',
+    width: '80px'
+  },
+  {
+    title: 'TN(mg/L)',
+    key: 'address',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'small',
+    width: '80px'
+  },
+  {
+    title: 'NH3 -N(mg/L)',
+    key: 'tags',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'small',
+    width: '80px'
+  },
+  {
+    title: '总磷TP(mg/L)',
+    key: 'actions',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'small',
+    width: '80px',
+    render(row) {
+      // TODO: 需要调整,待后续请求参数确定
+      return (<span class={row.actions > 7 ? 'text-[#F44C49] font-bold' : 'text-[1A2029]'}>{row.actions} ↑</span>)
     }
-  ]
+  },
+  {
+    title: 'SS(mg/L)',
+    key: 'actions1',
+    titleAlign: 'center',
+    align: 'center',
+    className: 'age',
+    width: '78px'
+  }
+]
 
-const inWaterTableData = ref([{name: 1233, actions: "7.87"}])
+const inWaterTableData = ref([{ name: 1233, actions: "7.87" }])
 
 // 新建对话
 const handleCreateDialog = () => {
@@ -67,13 +81,16 @@ const handleCreateDialog = () => {
 const handleLoad = () => {
   console.log("loading")
 }
+
+const handleModelVisible = () => {
+  visible.value = true
+} 
 </script>
 
 <template>
   <section class="flex items-start h-full">
     <TheSubMenu title="水质报警" @onLoad="handleLoad">
       <template #top>
-        <!-- border-b -->
         <div class="border-[#DAE5ED]">
           <n-tabs type="line" justify-content="space-evenly">
             <n-tab name="oasis" tab="正在报警"></n-tab>
@@ -113,25 +130,22 @@ const handleLoad = () => {
 
     <TheChatView>
 
-      <ChatWelcome
-        title="您好,我是LibraAI工艺管控助手"
-        card-title="常见处理方案:" 
-          :sub-title="[
+      <ChatWelcome title="您好,我是LibraAI工艺管控助手" card-title="常见处理方案:"
+        :sub-title="[
           '报警分析功能具备实时监测与预警机制,检测到异常情况立即触发多种报警方式,推送相关',
           '工作人员确保问题及时处理。报警时间为每小时警报,请大家及时处理。'
-        ]"
+        ]" 
         :card-content="[
           '进水COD超标原因分析及常见的解决方案',
           '进水TP超标原因分析及常见的解决方案',
           '出水TN超标原因分析及常见的解决方案'
-        ]"
-        v-if="false"
+        ]" v-if="false"
       />
 
-      <BaseCard>
+      <BaseCard :loading="true">
         <div class="waring-answer-wrapper">
-          <dl class="warning-inner warning-info_medium mb-[20px]">
-            <dt class="mb-[2px] font-bold text-[#1A2029] leading-[20px]">进水总磷报警</dt>
+          <dl class="message-inner warning-info_medium ">
+            <dt class="mb-[2px] font-bold text-[#1A2029]">进水总磷报警</dt>
             <dd><span>报警时间:2024-4-25 21:00</span></dd>
             <dd><span class="text-[#F44C49]">报警值:7.87mg/L</span></dd>
             <dd><span>标准值:7.1mg/L</span></dd>
@@ -139,43 +153,73 @@ const handleLoad = () => {
             <dd><span>报警次数:33</span></dd>
             <dd><span>状态:报警中</span></dd>
           </dl>
-          <div class="warning-table mb-[16px]">
-            <div class="title">
-              <span>当前进水数据:</span>
-            </div>
-            <div class="main">
-              <n-data-table
-                bordered
-                align="center"
-                :single-line="false"
-                :columns="columns"
-                :data="inWaterTableData"
-              />
-            </div>
-          </div>
-
-          <div class="warning-table">
-            <div class="title">
-              <span>当前出水数据:</span>
+          <div class="table-inner">
+            <div class="warning-table mb-[8px]">
+              <div class="title">
+                <span>当前进水数据:</span>
+              </div>
+              <div class="main">
+                <BaseTable :columns="columns" :data="inWaterTableData"></BaseTable>
+              </div>
             </div>
-            <div class="main">
-              <n-data-table
-                bordered
-                align="center"
-                :single-line="false"
-                :columns="columns"
-                :data="inWaterTableData"
-              />
+            <div class="warning-table">
+              <div class="title">
+                <span>当前出水数据:</span>
+              </div>
+              <div class="main">
+                <BaseTable :columns="columns" :data="[]"></BaseTable>
+              </div>
             </div>
           </div>
         </div>
       </BaseCard>
 
+      <BaseCard>
+        <p class="flex-1 text-[15px] leading-[24px]">
+          COD,即化学需氧量,是衡量水中有机物质含量的重要指标。它反映了水中可氧化有机物的量,通常用来评估水体的污染程度。水中的有机物主要来源于工业废水、生活污水、农药残留等,这些有机物不仅会导致水质变差,还会对生物和人类健康产生负面影响。因此,通过测定COD值,可以了解水中有机污染物的含量,进而评估水体的污染程度。这对于制定环境保护政策、控制污染源、保障水资源安全等方面都具有重要的指导意义
+        </p>
+      </BaseCard>
+
+      <button class="
+        px-[30px] py-[10px] mb-[20px]
+        rounded-[8px] 
+        bg-white text-[13px] 
+        text-[#5E5E5E] hover:text-[#2454FF]"
+        @click="handleModelVisible"
+      >
+        水质预测推演
+      </button>
+
+      <BaseCard>
+        <p class="mb-[15px] font-bold text-[#1A2029]">需要确定以下问题,完成决策方案:</p>
+        <ul class="radio-wrapper space-y-[14px]">
+          <li class="flex items-center ">
+            <p class="mr-[14px]">在线仪表是否正常?</p>
+            <p class="radio-btn-group space-x-[14px]">
+              <span class="radio-btn active">是</span>
+              <span class="radio-btn">否</span>
+            </p>
+          </li>
+          <li class="flex items-center ">
+            <p class="mr-[14px]">在线仪表是否正常?</p>
+            <p class="radio-btn-group space-x-[14px]">
+              <span class="radio-btn">是</span>
+              <span class="radio-btn">否</span>
+            </p>
+          </li>
+        </ul>
+      </BaseCard>
+
     </TheChatView>
   </section>
+
+  <CustomModal v-model:visible="visible"></CustomModal>
+
 </template>
 
 <style scoped lang="scss">
+
+
 .base-card-container {
   margin-bottom: 20px;
 }
@@ -226,21 +270,63 @@ const handleLoad = () => {
 
   &_medium {
     line-height: 26px;
-    font-size: 15px;
+    font-size: 14px;
     color: #1A2029;
   }
 }
 
 // 回答区域卡片
 .waring-answer-wrapper {
-  .warning-table {
-    .title {
-      margin-bottom: 8px;
-      line-height: 24px;
-      font-size: 15px;
-      font-weight: bold;
-      color: #1A2029;
+  @include flex(x, start, between);
+
+  .message-inner {
+    width: 194px;
+    flex-shrink: 0;
+  }
+
+  .table-inner {
+    @include flex(y, end, center);
+    padding-left: 20px;
+    border-left: 1px solid #F1F1F1;
+    .warning-table {
+      .title {
+        margin-bottom: 8px;
+        line-height: 16px;
+        font-size: 12px;
+        font-weight: bold;
+        color: #1A2029;
+      }
     }
   }
 }
+
+.radio-wrapper {
+
+  .radio-btn-group {
+    @include flex(x, center, center);
+    font-size: 14px;
+    text-align: center;
+    color: #5E5E5E;
+
+    .radio-btn {
+      width: 62px;
+      height: 28px;
+      border-radius: 4px;
+      background: #F4F6F8;
+      font-size: 14px;
+      line-height: 26px;
+      cursor: pointer;
+
+      &.active, &:hover {
+        color: #2454FF;
+        background: #E2F1FF;
+      }
+      &.active {
+        background: #E2F1FF url('@/assets/images/chat/bg-raido-check.png') right bottom no-repeat;
+        
+      }
+    }
+  }
+
+}
 </style>

+ 136 - 0
src/views/analyse/components/CustomModal.vue

@@ -0,0 +1,136 @@
+<script setup>
+import { ref, defineModel } from 'vue';
+import { NModal, NInput } from 'naive-ui';
+
+const modelVisible = defineModel('visible');
+const showModal = ref(true);
+
+const handleCancel = () => {
+  modelVisible.value = false;
+}
+</script>
+
+<template>
+  <NModal
+    v-model:show="modelVisible"
+    transform-origin="center"
+    :close-on-esc="false"
+    :maskClosable="false"
+  >
+    <div class="modal-wrapper">
+      <p class="header mb-[16px] font-bold text-[16px] leading-[22px]">水质预测推演 - 出水硝酸盐</p>
+
+      <div class="content-card mb-[8px]">
+        <p class="mb-[10px] font-bold text-[14px] leading-[20px]">可调参数</p>
+        <ul class="inp-group grid grid-cols-2 gap-x-[24px]  gap-y-[8px]">
+          <li>
+            <span>碳源投加量(L/h)</span>
+            <input type="text">
+          </li>
+          <li>
+            <span>碳源投加量(L/h)</span>
+            <input type="text">
+          </li>
+          <li>
+            <span>碳源投加量(L/h)</span>
+            <input type="text">
+          </li>
+          <li>
+            <span>碳源投加量(L/h)</span>
+            <input type="text">
+          </li>
+        </ul>
+      </div>
+
+      <div class="content-card mb-[20px]">
+        <p class="mb-[10px] font-bold text-[14px] leading-[20px]">相关参数</p>
+        <ul class="grid grid-cols-2 gap-x-[24px]  gap-y-[10px]">
+          <li class="space-x-[8px]">
+            <span>好氧池水温</span>
+            <span>11.9</span>
+          </li>
+          <li class="space-x-[8px]">
+            <span>好氧池PH</span>
+            <span>11.9</span>
+          </li>
+          <li class="space-x-[8px]">
+            <span>好氧池硝酸盐</span>
+            <span>11.9</span>
+          </li>
+          <li class="space-x-[8px]">
+            <span>缺氧池氨氮</span>
+            <span>11.9</span>
+          </li>
+          <li class="space-x-[8px]">
+            <span>缺氧池硝酸盐</span>
+            <span>11.9</span>
+          </li>
+        </ul>
+      </div>
+
+      <div class="footer flex items-center justify-between">
+        <p>*红色数字为建议调整数值</p>
+        <div class="btn-group space-x-[16px]">
+          <button class="btn btn_default" @click="handleCancel">取消</button>
+          <button class="btn btn_primary">发起预测</button>
+        </div>
+      </div>
+    </div>
+  </NModal>
+</template>
+
+<style scoped lang="scss">
+.modal-wrapper {
+  width: 432px;
+  padding: 30px 32px 20px 32px;
+  border: 1px solid #fff;
+  border-radius: 8px;
+  background: linear-gradient(180deg, #D6EEFF 0%, #F1F7FB 100%);
+
+  .content-card {
+    border-radius: 6px;
+    padding: 16px 18px;
+    background: #fff;
+
+    span {
+      font-size: 12px;
+      color: #5E5E5E;
+    }
+
+    input {
+      padding: 7px 13px;
+      margin-top: 8px;
+      border: 1px solid #D7D7D7;
+      border-radius: 4px;
+      background: #F8F8FA;
+      font-size: 12px;
+      line-break: 18px;
+      color: #F44C49;
+      outline: none;
+    }
+  }
+
+  .footer {
+    font-size: 11px;
+    color: #9A9A9A;
+
+    .btn {
+      width: 98px;
+      height: 40px;
+      border: 1px solid #2454FF;
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: bold;
+
+      &_default {
+        color: #2454FF;
+      }
+
+      &_primary {
+        color: #fff;
+        background: #2454FF;
+      }
+    }
+  }
+}
+</style>

+ 5 - 0
src/views/analyse/components/index.js

@@ -0,0 +1,5 @@
+import CustomModal from './CustomModal.vue';
+
+export {
+  CustomModal,
+}

+ 99 - 0
src/views/login/LoginView.vue

@@ -0,0 +1,99 @@
+<script setup>
+import { ref, onMounted } from 'vue';
+import { NForm, useMessage } from 'naive-ui';
+import { SvgIcon } from '@/components';
+
+</script>
+
+<template>
+  <div class="login-viewport">
+    <div class="logo absolute w-[106px] h-[28px] top-[14px] left-[20px]">
+      <img src="@/assets/images/login/logo.png" alt="logo" />
+    </div>
+
+    <main class="main flex items-center space-x-[98px]">
+      <div class="text-[#07233C] pb-[90px]">
+        <div class="text-title mb-[8px] text-[36px] font-bold leading-[44px]">
+          <p class="text-[#2454FF]">人工智能运营体</p>
+          <p>全国首家落地水务大模型</p>
+        </div>
+        <p class="w-[540px] text-[18px] text-justify leading-[26px]">
+          人工智能运营体(Libra水务大模型+智能装备)赋能水务行业,为用户提供全参检测、专家问答、报警决策、预测预警、智能投药、精准曝气等多项功能,通过全参数自动化验、运维工能力提升,带动水厂提效变革
+        </p>
+      </div>
+
+      <div class="login-form w-[442px] h-[454px] bg-white rounded-[16px]">
+        <p class="title pl-[2px] mb-[30px] text-[28px] leading-[40px] font-bold">系统登陆</p>
+        <div class="form-inner w-full">
+          <ul class="form-inp-list">
+            <li class="inp-item-inner">
+              <input type="text" placeholder="请输入用户名称">
+            </li>
+            <li class="inp-item-inner">
+              <input type="password" placeholder="请输入登录密码">
+            </li>
+          </ul>
+          <div class="waring-feedback h-[32px] text-[12px] text-[#FF7152]">
+            <p class="flex items-center justify-center h-full space-x-[8px] bg-[#FFF1F0]" v-show="true">
+              <SvgIcon name="login-warning-tips"></SvgIcon>
+              <span>账号或者密码错误</span>
+            </p>
+          </div>
+        </div>
+        <button class="btn w-full h-[54px] rounded-[4px] bg-[#2454FF] text-white test-[18ox]">登 录</button>
+      </div>
+    </main>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.login-viewport {
+  position: relative;
+  @include flex(x, center, center);
+  height: 100vh;
+  background: url("@/assets/images/login/bg-login.png") no-repeat;
+  background-size: 100% 100%;
+  overflow: hidden;
+
+  .text-title {
+    font-family: AlimamaShuHeiTi;
+  }
+
+  .login-form {
+    @include flex(y, start, between);
+    padding: 63px 61px 66px 61px;
+
+    .title {
+      background: url("@/assets/images/login/bg-title.png") left bottom no-repeat;
+    }
+
+    .form-inp-list {
+      width: 100%;
+      margin-bottom: 8px;
+
+      .inp-item-inner {
+        border-radius: 2px;
+        overflow: hidden;
+
+        input {
+          width: 100%;
+          padding: 16px 20px;
+          background: #F2F4F9;
+          font-size: 16px;
+          line-height: 22px;
+          color: #1A2029;
+          outline: none;
+
+          &::placeholder {
+            color: #989EA7;
+          }
+        }
+
+        &:nth-child(1) {
+          margin-bottom: 18px;
+        }
+      }
+    }
+  }
+}
+</style>