Prechádzať zdrojové kódy

feat: 新增统计样本筛选界面

sunxiao 9 mesiacov pred
rodič
commit
39543db12a

+ 14 - 2
deploy/config.js

@@ -8,7 +8,19 @@ export const config = [
     port: "22",
     username: "root",
     password: "hsmysql",
-    path: "/usr/share/nginx/html",//部署路径
-    removepath: "/usr/share/nginx/html", //删除路径
+    path: "/usr/share/nginx/html",
+    removepath: "/usr/share/nginx/html"
+  },
+  {
+    id: 1,
+    nodeEnv: "prod",
+    name: "生产环境",
+    domain: "",
+    host: "192.168.9.54",
+    port: "22",
+    username: "root",
+    password: "admin1,xxh",
+    path: "/usr/share/nginx/html",
+    removepath: "/usr/share/nginx/html"
   },
 ];

+ 1 - 1
deploy/index.js

@@ -25,7 +25,7 @@ const getConfig = () => {
 }
 
 // 部署
-const connectShell = async (params) => {
+const connectShell = async () => {
   const sftp = new Client();
   const item = getConfig();
   let spinner = null;

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "dev": "vite && node ./deploy/index.js",
+    "dev": "vite",
     "build:prod": "vite build",
     "build:test": "vite build --mode test",
     "build-lint": "run-p type-check \"build-only {@}\" --",

BIN
src/assets/images/chat/back-btn-active.png


BIN
src/assets/images/chat/back-btn.png


BIN
src/assets/images/warn-text1.png


+ 9 - 4
src/components/Chat/ChatAnswer.vue

@@ -39,10 +39,14 @@ const props = defineProps({
   isVisibleStopBtn: {
     type: Boolean,
     default: false
+  },
+  isVisibleResetBtn: {
+    type: Boolean,
+    default: false
   }
 })
 
-const emit = defineEmits(['on-click-icon', 'on-click-stop']);
+const emit = defineEmits(['on-click-icon', 'on-click-stop', 'on-click-reset']);
 
 const message = useMessage();
 
@@ -60,9 +64,9 @@ const handlLeToggleLike = async (state) => {
   emit('on-click-icon', params);
 }
 
-const handleChatStop = () => {
-  emit('on-click-stop');
-}
+const handleChatStop = () => emit('on-click-stop');
+
+const handleChatReset = () => emit('on-click-reset');
 
 const handleCopy = () => {
   copy(props.content).then(() => {
@@ -84,6 +88,7 @@ const handleCopy = () => {
       <div class="footer-wrap">
         <div class="chat-stop-btn">
           <span @click="handleChatStop" v-if="!delayLoading && loading && isVisibleStopBtn">停止生成</span>
+          <span @click="handleChatReset" v-if="!delayLoading && !loading && isVisibleResetBtn">重新生成</span>
         </div>
         <ul class="answer-btn-group" v-if="!loading && toggleVisibleIcons">
           <li class="btn" @click="handleCopy">

+ 1 - 3
src/components/Chat/ChatInput.vue

@@ -97,9 +97,7 @@ defineExpose({
     <NSwitch size="small" v-model:value="switchStatus"></NSwitch>
     <span class="text-[12px] text-[#9E9E9E]">使用搜索增强</span>
   </div>
-  <div class="masking-inner text-center text-[#2454FF]">
-    <!-- <span>停止生成</span> -->
-  </div>
+  <div class="masking-inner text-center text-[#2454FF]"></div>
 </template>
 
 <style scoped lang="scss">

+ 21 - 23
src/components/Dialog/editPassword.vue

@@ -84,30 +84,28 @@ const onSubmit = event => {
 </script>
 
 <template>
-  <div>
-    <n-modal v-model:show="userStore.dialogStatus" :mask-closable="false">
-      <div class="edit-passWord">
-        <div class="title">修改密码</div>
-        <div class="main">
-          <n-form ref="formRef" :model="model" :rules="rules" label-placement="left" label-align="left" :label-width="64" :show-require-mark="false">
-            <n-form-item label="旧密码" path="oldPassword" first>
-              <n-input v-model:value="model.oldPassword" class="input" placeholder="请输入登录密码" type="password" />
-            </n-form-item>
-            <n-form-item label="新密码" path="newPassword" first>
-              <n-input v-model:value="model.newPassword" class="input" placeholder="请输入8~16位数字,字母" type="password" />
-            </n-form-item>
-            <n-form-item label="确认密码" path="reenteredPassword" first>
-              <n-input v-model:value="model.reenteredPassword" class="input" placeholder="请再次输入密码" type="password" />
-            </n-form-item>
-          </n-form>
-        </div>
-        <div class="footer">
-          <n-button class="cencel btn" @click="onCancel">取消</n-button>
-          <n-button type="primary" class="ok btn" @click="onSubmit">确定</n-button>
-        </div>
+  <n-modal v-model:show="userStore.dialogStatus" :mask-closable="false">
+    <div class="edit-passWord">
+      <div class="title">修改密码</div>
+      <div class="main">
+        <n-form ref="formRef" :model="model" :rules="rules" label-placement="left" label-align="left" :label-width="64" :show-require-mark="false">
+          <n-form-item label="旧密码" path="oldPassword" first>
+            <n-input v-model:value="model.oldPassword" class="input" placeholder="请输入登录密码" type="password" />
+          </n-form-item>
+          <n-form-item label="新密码" path="newPassword" first>
+            <n-input v-model:value="model.newPassword" class="input" placeholder="请输入8~16位数字,字母" type="password" />
+          </n-form-item>
+          <n-form-item label="确认密码" path="reenteredPassword" first>
+            <n-input v-model:value="model.reenteredPassword" class="input" placeholder="请再次输入密码" type="password" />
+          </n-form-item>
+        </n-form>
       </div>
-    </n-modal>
-  </div>
+      <div class="footer">
+        <n-button class="cencel btn" @click="onCancel">取消</n-button>
+        <n-button type="primary" class="ok btn" @click="onSubmit">确定</n-button>
+      </div>
+    </div>
+  </n-modal>
 </template>
 <style lang="scss" scoped>
 .edit-passWord {

+ 36 - 30
src/components/Layout/TheChatView.vue

@@ -1,58 +1,47 @@
 <script setup>
-import { ref, unref, computed, onMounted } from 'vue';
-import { NSelect, NDropdown, NPopover } from 'naive-ui';
+import { ref, unref, computed } from 'vue';
 
-import userTop from './userTop.vue';
+import UserTop from './userTop.vue';
 
 defineProps({
   isFooter: {
     type: Boolean,
     default: true
+  },
+  isBackBtn: {
+    type: Boolean,
+    default: false
   }
 })
 
-const userInfo = ref({});
+const emit = defineEmits(['onClickBack'])
 
 const targetScrollDom = ref(null);
-const selectValue = ref('water');
 const voiceSwitchStatus = ref(false);
 
-const userMenuOptions = [
-  {
-    label: '个人中心',
-    key: "1"
-  },
-  {
-    label: '修改密码',
-    key: "2"
-  },
-  {
-    label: '退出登录',
-    key: "3"
-  },
-]
-
-const options = [
-  {
-    label: "信义污水厂",
-    value: 'water',
-  }
-]
-
 const voiceName = computed(() => unref(voiceSwitchStatus) ? 'tool-voice-close' : 'tool-voice-open')
 
 const changeVoiceStatus = () => {
   voiceSwitchStatus.value = !voiceSwitchStatus.value;
 }
 
+const handleClickBack = () => {
+  emit("onClickBack");
+}
+
 defineExpose({ targetScrollDom });
 </script>
 
 <template>
   <div class="flex-1 h-full chat-container">
     <div class="chat-wrapper w-full h-full flex flex-col rounded-[20px]">
-      <div class="chat-header flex items-center justify-end py-[24px] pr-[18px] space-x-[16px]">
-        <userTop></userTop>
+      <div class="chat-header flex items-center justify-between py-[24px] px-[18px] ">
+        <div class="left_inner">
+          <span @click="handleClickBack" v-if="isBackBtn" class="back-btn"></span>
+        </div>
+        <div class="right_inner flex items-center space-x-[16px]">
+          <UserTop></UserTop>
+        </div>
       </div>
       <main class="chat-main flex flex-1 flex-col justify-between">
         <div class="chat-scroll" ref="targetScrollDom">
@@ -73,6 +62,24 @@ defineExpose({ targetScrollDom });
   padding: 20px 20px 20px 0;
   overflow: hidden;
 
+  .chat-header {
+    .left_inner {
+      .back-btn {
+        display: inline-block;
+        width: 24px;
+        height: 24px;
+        background: url("@/assets/images/chat/back-btn.png") no-repeat;
+        background-size: cover;
+        cursor: pointer;
+
+        &:hover {
+          background: url("@/assets/images/chat/back-btn-active.png") no-repeat;
+          background-size: cover;
+        }
+      }
+    }
+  }
+
   .chat-wrapper {
     border: 1px solid #fff;
     background: linear-gradient(180deg, rgba(238, 253, 255, 0.5) 0%, rgba(231, 243, 252, 0.5) 100%);
@@ -82,7 +89,6 @@ defineExpose({ targetScrollDom });
       color: #1A2029;
 
       .chat-scroll {
-        // flex: 1;
         overflow-x: hidden;
         overflow-y: auto;
 

+ 3 - 15
src/components/Layout/userTop.vue

@@ -1,6 +1,6 @@
 <script setup>
-import { ref, unref, computed, onMounted } from 'vue';
-import { NSelect, NDropdown, NPopover } from 'naive-ui';
+import { ref, unref, computed } from 'vue';
+import { NSelect } from 'naive-ui';
 import { SvgIcon, BasePopover, editPassword } from '@/components';
 import TheUserAvatar from './TheUserAvatar.vue';
 
@@ -11,14 +11,9 @@ defineProps({
   }
 })
 
-
-
 const selectValue = ref('water');
 const voiceSwitchStatus = ref(false);
 
-
-
-
 const options = [
   {
     label: "信义污水厂",
@@ -31,25 +26,18 @@ const voiceName = computed(() => unref(voiceSwitchStatus) ? 'tool-voice-close' :
 const changeVoiceStatus = () => {
   voiceSwitchStatus.value = !voiceSwitchStatus.value;
 }
-
-
 </script>
 
 <template>
-  <!-- 数字人设置 -->
   <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>
-  <!-- 水厂select -->
-  <NSelect v-model:value="selectValue" placeholder="" :options="options" class="w-[114px]" size="medium"
+  <NSelect v-model:value="selectValue" class="w-[114px]" size="medium" :options="options"
     :consistent-menu-width="false" />
-  <!-- 用户头像 -->
   <TheUserAvatar></TheUserAvatar>
   <editPassword></editPassword>
 </template>

+ 3 - 2
src/views/analyse/ForecastView.vue

@@ -24,7 +24,7 @@ const answerResult = ref("");
 const textDataSources = ref(null);
 const chartTitle = ref("");
 
-let chart = {};
+let chart = null;
 const echartRef = ref({});
 
 // 进出水数据
@@ -60,6 +60,7 @@ const handleOpenContent = async ({ id, reason: title }) => {
 
 // 创建图表
 const createLineChat = (data) => {
+  
   const [xAxisData, yData] = data.reduce((acc, { time, val }) => {
     acc[0].push(time);
     acc[1].push(val);
@@ -79,7 +80,7 @@ const handleWelcomeRecommend = question => {
 }
 
 onMounted(() => {
-  chart = echarts.init(echartRef.value, 'light');
+  chart = echarts.init(echartRef.value, 'light', { width: 660, height: 200 });
 })
 </script>
 

+ 1 - 1
src/views/analyse/WaterView.vue

@@ -524,7 +524,7 @@ const handleWelcomeRecommend = question => {
         :delay-loading="answerLoading"
         :toggleVisibleIcons="false"
         v-show="answerLoading"
-        loadingText="内容生成中,大概需要50秒..."
+        loadingText="数据分析中,由于数据查询需要一些时间,请您耐心等候..."
       ></ChatAnswer>
 
     </TheChatView>

+ 27 - 15
src/views/analyse/WorkOrder.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, unref, computed, onMounted, onUnmounted, h } from 'vue';
+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';
@@ -18,6 +18,7 @@ const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
 const { chatDataSource, addChat, updateChat, clearChat, updateById } = useChat();
 
 let controller = new AbortController();
+let timer = null;
 
 const ANSWER_ID_KEY = '@@id@@';
 
@@ -54,6 +55,20 @@ const resetFormData = () => {
   }
 }
 
+const resetState = () => {
+  tabActive.value = 'daily';
+
+  currenSessionId.value = null;
+
+  recordActive.value = null;
+
+  reportDate.value = null;
+
+  resetFormData();
+
+  clearChat();
+}
+
 // 新建对话
 const handleCreateDialog = async () => {
   message.destroyAll();
@@ -65,16 +80,8 @@ const handleCreateDialog = async () => {
   if (!unref(chatDataSource).length) {
     return message.info('已切换最新会话');
   }
-  
-  tabActive.value = 'daily';
 
-  currenSessionId.value = null;
-
-  recordActive.value = null;
-
-  resetFormData();
-
-  clearChat();
+  resetState();
 }
 
 // 查询对话详情
@@ -110,6 +117,7 @@ const handleChatDetail = async ({ sessionId }) => {
   scrollToBottom();
 }
 
+// 请求
 const onRegenerate = async (question, options) => {
   controller = new AbortController();
 
@@ -202,7 +210,7 @@ const handleSubmit = async (question, params) => {
     innerLoading: true,
   });
 
-  setTimeout(() => {
+  timer = setTimeout(() => {
 
     updateChat({
       ...chatDataSourceItem.value,
@@ -320,6 +328,13 @@ const formatData = (data) => {
   }).filter(Boolean);
 }
 
+// 返回
+const handleback = async () => {
+  clearInterval(timer);
+  await chatApi.getStopChatStream(currenSessionId.value);
+  resetState();
+}
+
 // 删除历史对话
 const handeChatDelete = async (id) => {
   await chatApi.deleteHistory(id);
@@ -350,9 +365,6 @@ const dateEndDisabled = (timestamp) => {
   }
 }
 
-onMounted(() => {
-})
-
 onUnmounted(() => {
   controller.abort();
   Object.keys(chartInstance).forEach(key => chartInstance[key].clear());
@@ -378,7 +390,7 @@ onUnmounted(() => {
       </div>
     </TheSubMenu>
 
-    <TheChatView ref="scrollRef" :is-footer="false">
+    <TheChatView ref="scrollRef" :is-footer="false" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback">
       <ChatWelcome title="您好,我是LibraAI智慧工单助手" :sub-title="[
         '基于大语言模型的智能工单分析助手,可以为您实现数据分析及数据解读',
         '选择日期并为您生成日报分析'

+ 18 - 24
src/views/analyse/config/echartOptions.js

@@ -11,10 +11,8 @@ export const getAreaOptions = ({ xAxisData, seriesList }) => {
       symbolSize: 5,
       smooth: true,
       lineStyle: {
-        normal: {
-          width: 2,
-          color: i ? "rgba(25,163,223,1)" : "rgba(36,175,83,1)", // 线条颜色
-        },
+        width: 2,
+        color: i ? "rgba(25,163,223,1)" : "rgba(36,175,83,1)",
         borderColor: 'rgba(0,0,0,.4)',
       },
       itemStyle: {
@@ -25,19 +23,17 @@ export const getAreaOptions = ({ xAxisData, seriesList }) => {
         shadowBlur: 1
       },
       areaStyle: {
-        normal: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
-            offset: 0,
-            color: i ? "rgba(25,163,223,.3)" : "rgba(48, 209, 136,.3)"
-          },
-          {
-            offset: 1,
-            color: i ? "rgba(25,163,223, 0)" : "rgba(48, 209, 136, 0)"
-          }
-          ], false),
-          shadowColor: 'rgba(25,163,223, 0.5)',
-          shadowBlur: 20
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+          offset: 0,
+          color: i ? "rgba(25,163,223,.3)" : "rgba(48, 209, 136,.3)"
+        },
+        {
+          offset: 1,
+          color: i ? "rgba(25,163,223, 0)" : "rgba(48, 209, 136, 0)"
         }
+        ], false),
+        shadowColor: 'rgba(25,163,223, 0.5)',
+        shadowBlur: 20
       },
       z: i ? 3 : 2,
       data
@@ -79,10 +75,8 @@ export const getAreaOptions = ({ xAxisData, seriesList }) => {
         },
       },
       axisLabel: {
-        textStyle: {
-          color: '#1F2328',
-          fontSize: 12,
-        },
+        color: '#1F2328',
+        fontSize: 12,
         formatter: function (data) {
           return data + ":00"
         }
@@ -120,10 +114,10 @@ export const getAreaOptions = ({ xAxisData, seriesList }) => {
       },
       axisLabel: {
         show: true,
-        textStyle: {
-          color: '#1F2328',
-          padding: 0
-        },
+        color: '#1F2328',
+        padding: 0,
+        // textStyle: {
+        // },
         formatter: function (value) {
           if (value === 0) {
             return value

+ 12 - 4
src/views/answer/AnswerView.vue

@@ -80,6 +80,7 @@ const onRegenerate = async ({ question, realQuestion }) => {
       question: realQuestion || question,
       module: 0,
       isStrong: Number(unref(switchActive)),
+      // TODO: 后续大概率需要删除
       topP: 0.9,
       temperature: 0.7
     },
@@ -87,7 +88,7 @@ const onRegenerate = async ({ question, realQuestion }) => {
     onDownloadProgress: ({ event }) => {
       const xhr = event.target;
       const { responseText } = xhr;
-      const [answer] = responseText.split(ANSWER_ID_KEY);
+      const [ answer ] = responseText.split(ANSWER_ID_KEY);
 
       updateChat({
         sessionId,
@@ -128,7 +129,6 @@ const onRegenerate = async ({ question, realQuestion }) => {
 // 提交问题
 const handleSubmit = async (question, realQuestion = '') => {
   // 用于模拟 - 内容生成前置等待状态
-
   if (unref(isExistInHistory)) {
     const { data: sessionId } = await chatApi.getChatSessionTag();
     currenSessionId.value = sessionId;
@@ -169,6 +169,11 @@ const onStopChatStream  = async ({ sessionId }) => {
   return message.warning('已停止对话生成');
 }
 
+// 重新生成问题
+const onChatResetStream = ({ question }) => {
+  handleSubmit(question);
+}
+
 onMounted(() => {
   const question = chatStore.chatQuestion;
   if (Object.keys(question).length) {
@@ -206,7 +211,7 @@ onUnmounted(() => {
       ]" :card-content="recommendList" v-if="!chatDataSource.length" @on-click="handleWelcomeRecommend" />
 
       <div class="conversation-item" v-if="chatDataSource.length">
-        <template v-for="item in chatDataSource" :key="item.id">
+        <template v-for="item, index in chatDataSource" :key="item.id">
           <ChatAsk :content="item.question" :sessionId="item.sessionId"></ChatAsk>
           <ChatAnswer
             :id="item.id"
@@ -214,9 +219,12 @@ onUnmounted(() => {
             :loading="item.loading"
             :delay-loading="item.delayLoading"
             :isSatisfied="item.isSatisfied"
+            :isVisibleResetBtn="chatDataSource.length - 1 === index"
             isVisibleStopBtn
             @on-click-stop="onStopChatStream(item)"
-            @on-click-icon="params => updateById(params)">
+            @on-click-icon="params => updateById(params)"
+            @on-click-reset="onChatResetStream(item)"
+           >
           </ChatAnswer>
         </template>
       </div>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1158 - 0
src/views/count/all_book.json


+ 70 - 15
src/views/count/index.vue

@@ -1,40 +1,95 @@
 <script setup>
-import { ref } from 'vue';
-import { NButton, NSpace, NDrawer } from 'naive-ui';
-import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome } from '@/components';
-import { ChatAsk, ChatAnswer, ChatInput } from '@/components/Chat';
+import { ref, onMounted, watch } from 'vue';
+import { NButton, NSpace, NDrawer, NDrawerContent, NCard } from 'naive-ui';
+import { ChatAsk, ChatAnswer } from '@/components/Chat';
+import allBookData from "./all_book.json";
 
+const dataSource = ref([]);
+const errIds = ref([]);
 const visible = ref(false);
+const pageNum = ref(0);
+const pageSize = 10;
+
+const totalPages = Math.ceil(allBookData.length / pageSize);
+
+watch(pageNum, (num) => {
+  dataSource.value = allBookData.slice((num - 1) * pageSize, num * pageSize).map(item => ({
+    ...item,
+    isDisable: errIds.value.includes(item.id),
+    history: [
+      ...item.history,
+      [item.instruction, item.output]
+    ]
+  }))
+})
 
 const changeVisible = () => {
   visible.value = !visible.value;
 }
 
+const handleNextPage = () => {
+  if ( pageNum.value === totalPages ) return alert("已经是最后一页了");
+  pageNum.value += 1;
+  localStorage.setItem('pageNum', pageNum.value)
+}
+
+const handleError = item => {
+  item.isDisable = true;
+
+  errIds.value.push(item.id);
+  localStorage.setItem('errIds', JSON.stringify( errIds.value ));
+}
+
+onMounted(() => {
+  const localIds = localStorage.getItem('errIds');
+  const localNum = localStorage.getItem('pageNum');
+  const ids = localIds ? JSON.parse(localIds) : [];
+  const num = localNum ? Number(localNum) : 1;
+
+  errIds.value = ids;
+  pageNum.value = num;
+})
 </script>
 
 <template>
   <section class="page-container">
-    <header class="header">
+    <header class="header flex items-center">
       <NSpace>
-        <n-button>下一页</n-button>
+        <n-button @click="handleNextPage">下一页</n-button>
         <n-button @click="changeVisible">显示错误id</n-button>
       </NSpace>
+      <p class="ml-[30px] space-x-[10px]">
+        <span>{{ pageNum }}</span>
+        <span> / </span>
+        <span>{{ totalPages }}</span>
+      </p>
     </header>
     <div class="main">
-      <div class="answer-block" v-for="item in 100">
-        <ChatAsk content="问题问题问题"></ChatAsk>
-        <ChatAnswer content="回答回答回答回答回答" :toggleVisibleIcons="false"></ChatAnswer>
-        <div class="answer-btns">
+      <NCard :title="item.id" v-for="item in dataSource" :key="item.id" class="mb-[20px]">
+        <div v-for="arr, i in item.history" :key="i + item.id" class="pt-[40px]">
+          <ChatAsk :content="arr[0]"></ChatAsk>
+          <ChatAnswer :content="arr[1]" :toggleVisibleIcons="false"></ChatAnswer>
+          <hr>
+        </div>
+        <div class="answer-btns pt-[20px]">
           <NSpace>
-            <NButton type="success">对</NButton>
-            <NButton type="error">错</NButton>
+            <NButton :type="item.isDisable? '' : 'error'" @click="handleError(item)" :disabled="item.isDisable">
+              {{ item.isDisable ? '已审核' : '错' }}
+            </NButton>
           </NSpace>
         </div>
-      </div>
+      </NCard>
     </div>
   </section>
-
-  <NDrawer placement="right" v-model:show="visible" width="600">123123</NDrawer>
+  <NDrawer placement="right" v-model:show="visible" width="800" title="错误ID">
+    <n-drawer-content :title="'错误ID列表 -- ' + errIds.length + ' 个'">
+      <!-- <div class="py-[30px]">{{ errIds.join(',') }}</div> -->
+    <!-- <hr> -->
+    <ul class="grid grid-cols-5">
+      <li v-for="item in errIds" :key="item">{{ item }}</li>
+    </ul>
+    </n-drawer-content>
+  </NDrawer>
 </template>
 
 <style lang="scss" scoped>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 44 - 0
vite.config.ts.timestamp-1720486813773-2c95633e7e6a2.mjs


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov