zhongshujie
2025-08-08 401ed176c9f1bdd97ccdf827d9454b11a3891f79
8.8 初始版本
23个文件已修改
2个文件已添加
1个文件已删除
1062 ■■■■■ 已修改文件
.gitignore 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/ai/userImg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/details/img-2D.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/config.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/middleGround/tool.js 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/crumbPage.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/footerPage.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/headerPage.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/pageLayout.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/zh.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugin/axios/index.ts 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/aiDetails.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/details.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/home.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/searchlist.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/aiDetails.vue 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/detailsPage/index.vue 220 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/index.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/searchList/index.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.app.json 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -21,3 +21,8 @@
*.i*86
*.x86_64
*.hex
.DS_Store
node_modules
/dist
/dist.zip
index.html
@@ -5,7 +5,7 @@
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>小分子数据库</title>
  <title>天然产物电荷图谱库</title>
</head>
<body>
package-lock.json
@@ -19,7 +19,7 @@
        "speak-tts": "^2.0.8",
        "swiper": "^11.0.5",
        "vue": "^3.3.4",
        "vue-i18n": "^12.0.0-alpha.3",
        "vue-i18n": "^11.1.7",
        "vue-router": "^4.2.5"
      },
      "devDependencies": {
@@ -1040,12 +1040,12 @@
      "license": "BSD-3-Clause"
    },
    "node_modules/@intlify/core-base": {
      "version": "12.0.0-alpha.3",
      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-12.0.0-alpha.3.tgz",
      "integrity": "sha512-LEvBHBUbiOOtIBkp4IIQENVC5Fg2YHsvdXN1+WRIxQ8hzHbHSBiqZ2l68B/yg8sE1a4S7dqhkaAedunShWPH+Q==",
      "version": "11.1.11",
      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.11.tgz",
      "integrity": "sha512-1Z0N8jTfkcD2Luq9HNZt+GmjpFe4/4PpZF3AOzoO1u5PTtSuXZcfhwBatywbfE2ieB/B5QHIoOFmCXY2jqVKEQ==",
      "dependencies": {
        "@intlify/message-compiler": "12.0.0-alpha.3",
        "@intlify/shared": "12.0.0-alpha.3"
        "@intlify/message-compiler": "11.1.11",
        "@intlify/shared": "11.1.11"
      },
      "engines": {
        "node": ">= 16"
@@ -1055,11 +1055,11 @@
      }
    },
    "node_modules/@intlify/message-compiler": {
      "version": "12.0.0-alpha.3",
      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-12.0.0-alpha.3.tgz",
      "integrity": "sha512-mDDTN3gfYOHhBnpnlby19UHyvMaOnzdlpsIrxUfs44R/vCATfn8pMOkE8PXD2t410xkocEj3FpDcC9XC/0v4Dg==",
      "version": "11.1.11",
      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.11.tgz",
      "integrity": "sha512-7PC6neomoc/z7a8JRjPBbu0T2TzR2MQuY5kn2e049MP7+o32Ve7O8husylkA7K9fQRe4iNXZWTPnDJ6vZdtS1Q==",
      "dependencies": {
        "@intlify/shared": "12.0.0-alpha.3",
        "@intlify/shared": "11.1.11",
        "source-map-js": "^1.0.2"
      },
      "engines": {
@@ -1070,30 +1070,14 @@
      }
    },
    "node_modules/@intlify/shared": {
      "version": "12.0.0-alpha.3",
      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-12.0.0-alpha.3.tgz",
      "integrity": "sha512-ryaNYBvxQjyJUmVuBBg+HHUsmGnfxcEUPR0NCeG4/K9N2qtyFE35C80S15IN6iYFE2MGWLN7HfOSyg0MXZIc9w==",
      "version": "11.1.11",
      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.11.tgz",
      "integrity": "sha512-RIBFTIqxZSsxUqlcyoR7iiC632bq7kkOwYvZlvcVObHfrF4NhuKc4FKvu8iPCrEO+e3XsY7/UVpfgzg+M7ETzA==",
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://github.com/sponsors/kazupon"
      }
    },
    "node_modules/@intlify/vue-i18n-core": {
      "version": "12.0.0-alpha.3",
      "resolved": "https://registry.npmmirror.com/@intlify/vue-i18n-core/-/vue-i18n-core-12.0.0-alpha.3.tgz",
      "integrity": "sha512-YwAfTQILHN+VoK0P/Yv47GbKnEf1lhfbliyVyW3knAL1EmT8m0m3rwffXJnwyQhYw8Jpx85CpL49WkSgyi6d/g==",
      "dependencies": {
        "@intlify/core-base": "12.0.0-alpha.3",
        "@intlify/shared": "12.0.0-alpha.3",
        "@vue/devtools-api": "^6.5.0"
      },
      "engines": {
        "node": ">= 16"
      },
      "peerDependencies": {
        "vue": "^3.0.0"
      }
    },
    "node_modules/@jridgewell/gen-mapping": {
@@ -6052,13 +6036,12 @@
      }
    },
    "node_modules/vue-i18n": {
      "version": "12.0.0-alpha.3",
      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-12.0.0-alpha.3.tgz",
      "integrity": "sha512-+KQgD9LJoHfGCdJh3gaLdVS/Sps1n860+6wsjyeNLWJeEofjdVH7KPjz4rAeBlTAUaIDlIjHoXQY0Lk+8B6S9w==",
      "version": "11.1.11",
      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.11.tgz",
      "integrity": "sha512-LvyteQoXeQiuILbzqv13LbyBna/TEv2Ha+4ZWK2AwGHUzZ8+IBaZS0TJkCgn5izSPLcgZwXy9yyTrewCb2u/MA==",
      "dependencies": {
        "@intlify/core-base": "12.0.0-alpha.3",
        "@intlify/shared": "12.0.0-alpha.3",
        "@intlify/vue-i18n-core": "12.0.0-alpha.3",
        "@intlify/core-base": "11.1.11",
        "@intlify/shared": "11.1.11",
        "@vue/devtools-api": "^6.5.0"
      },
      "engines": {
package.json
@@ -23,7 +23,7 @@
    "speak-tts": "^2.0.8",
    "swiper": "^11.0.5",
    "vue": "^3.3.4",
    "vue-i18n": "^12.0.0-alpha.3",
    "vue-i18n": "^11.1.7",
    "vue-router": "^4.2.5"
  },
  "devDependencies": {
@@ -46,4 +46,4 @@
    "vite": "^4.4.11",
    "vue-tsc": "^1.8.19"
  }
}
}
src/assets/images/ai/userImg.png
src/assets/images/details/img-2D.png
src/assets/js/config.ts
@@ -1,4 +1,5 @@
export const requestCtx = 'http://182.92.203.7:3001' // 请求地址
const aiCtx = 'http://192.168.1.14:8100/'
export const appId = 42
export const requestTimeOut = 300000 // 请求超时时间
export const tokenKey = 'caccrd-token'
@@ -29,6 +30,7 @@
}
const config = {
  requestCtx,
  aiCtx,
  requestTimeOut,
  tokenKey,
  userInfoKey,
src/assets/js/middleGround/tool.js
@@ -1,5 +1,5 @@
import { requestCtx, appId } from '@/assets/js/config'
import bookCover from '@/assets/images/default/red-book.png'
// import bookCover from '@/assets/images/default/red-book.png'
import moment from 'moment'
// 处理列表查询结果
export function handleQueryResourceListData({
@@ -397,22 +397,22 @@
// 获取不受保护的图片
export function getPublicImage(md5, width, height, storeInfo) {
  let src = null
  if (md5) {
    src = requestCtx + `/file/GetPreViewImage?md5=${md5}`
  } else {
    if (storeInfo == 'jsek_bookFair') {
      // return defaultBookFair;
      return
    } else if (storeInfo == `defaultGoodsStore${appId}`) {
      return bookCover
    } else if (storeInfo == `defaultPublicStore${appId}`) {
      return bookCover
    } else {
      return ''
    }
  }
  if (width) src += `&width=${width}`
  if (height) src += `&height=${height}`
  return src
  // let src = null
  // if (md5) {
  //   src = requestCtx + `/file/GetPreViewImage?md5=${md5}`
  // } else {
  //   if (storeInfo == 'jsek_bookFair') {
  //     // return defaultBookFair;
  //     return
  //   } else if (storeInfo == `defaultGoodsStore${appId}`) {
  //     return bookCover
  //   } else if (storeInfo == `defaultPublicStore${appId}`) {
  //     return bookCover
  //   } else {
  //     return ''
  //   }
  // }
  // if (width) src += `&width=${width}`
  // if (height) src += `&height=${height}`
  // return src
}
src/layout/components/crumbPage.vue
File was deleted
src/layout/components/footerPage.vue
@@ -61,6 +61,7 @@
}
.main-left {
  width: 7%;
  margin-right: 8%;
  li {
@@ -74,20 +75,13 @@
}
.main-center {
  width: 45%;
  li {
    text-wrap: nowrap;
    margin-bottom: 10px;
    margin-right: 10px;
    cursor: pointer;
  }
}
.main-list {
  display: flex;
  li {
    text-wrap: nowrap;
  }
}
src/layout/components/headerPage.vue
@@ -2,7 +2,7 @@
  <div class="pageHeader">
    <div class="header-box">
      <ul class="page-title">
        <img class="zylogo" src="../../assets/images/home/zylogo.png" alt="">
        <img @click="goHome()" class="zylogo" src="../../assets/images/home/zylogo.png" alt="">
        <li class="separator"></li>
        <img class="sylogo" src="../../assets/images/home/sylogo.svg" alt="">
        <li class="title-text">{{ t('message.logoTitle') }}</li>
@@ -40,6 +40,10 @@
  },
});
const goHome = () => {
  router.push({ path: '/' });
};
</script>
@@ -51,7 +55,7 @@
  justify-content: center;
  width: 100%;
  background-color: #ecf9f6;
  height: 78px;
}
.header-box {
@@ -71,6 +75,7 @@
.zylogo {
  width: 21%;
  cursor: pointer;
}
.separator {
@@ -120,6 +125,7 @@
}
.title-text {
  width: 450px;
  padding-left: 14px;
  font-weight: bold;
  font-size: 20px;
src/layout/pageLayout.vue
@@ -31,8 +31,6 @@
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: url('../assets/images/default/BG.jpg') no-repeat;
  background-size: cover;
  .layoutContentBox {
    flex: 1;
src/locales/zh.json
@@ -8,9 +8,9 @@
        "description": "这是一个国际化演示页面。",
        "hello": "你好",
        "about": "关于我们",
        "logoTitle": "生物电荷图天然产物库",
        "homeTitle": "智能搜索小分子",
        "aiTitle": "智能搜索小分子",
        "logoTitle": "天然产物电荷图谱库",
        "homeTitle": "小分子智能搜索",
        "aiTitle": "小分子智能搜索",
        "aiText": "探索化学结构、生物活性等——由人工智能驱动。",
        "searchResult": "搜索结果",
        "synonyms": "同义词",
src/plugin/axios/index.ts
@@ -2,9 +2,10 @@
import myConfig from '@/assets/js/config'
import toolClass from '@/assets/js/toolClass.js'
import router from '@/router'
// 创建 axios 实例
const service = axios.create({
  baseURL: myConfig.requestCtx,
  baseURL: myConfig.aiCtx,
  timeout: myConfig.requestTimeOut, // 请求超时时间
})
@@ -21,63 +22,80 @@
  },
)
// 响应拦截器
service.interceptors.response.use(
  (response) => {
    // dataAxios 是 axios 返回数据中的 data
    const dataAxios = response.data
    if (typeof dataAxios.data === 'boolean') {
      return dataAxios.data
    }
    if (response.config.responseType == 'blob') {
      return dataAxios
    }
    const { success } = dataAxios
    if (dataAxios.currentDate) {
      sessionStorage.currentDate = new Date(dataAxios.currentDate).getTime()
    }
    // 根据 code 进行判断
    if (success) {
      return dataAxios.data ? dataAxios.data : dataAxios
    } else {
      // 提示错误
    }
// 请求拦截器
// app-6MPX5Fr97eK6BWHpy8g7vddd
// app-u5c1CREHe0IWw3vC0wW8j5Ys //  中药饮片
// app-pLuWNhXPmI1uZSLmWw74Q05F // 小分子库
service.interceptors.request.use(
  (config) => {
    const token = 'app-pLuWNhXPmI1uZSLmWw74Q05F'
    if (token) config.headers['Authorization'] = `Bearer ${token}`
    config.headers['Content-Type'] = 'application/json;charset=utf-8'
    return config
  },
  (error) => {
    if ((error.response && error.response.status == 401) || error.code == 'ERR_NETWORK') {
      localStorage.removeItem(myConfig.tokenKey)
      localStorage.removeItem('jesk-userInfo')
      localStorage.removeItem('alreadyElectronicBook')
      localStorage.removeItem('alreadyPaperBook')
      localStorage.removeItem('electronicBookList')
      localStorage.removeItem('paperBookList')
      sessionStorage.removeItem('cartNumber')
      // router.replace({
      //   path: '/home',
      //   query: {
      //     showLogin: '1'
      //   }
      // })
      const url = window.location.hash.slice(1)
      console.log(url)
      if (url.includes('showLogin=1')) {
        router.push(url)
      } else {
        if (url.includes('?')) {
          router.push(url + '&showLogin=1')
        } else {
          router.push(url + '?showLogin=1')
        }
      }
    } else {
      if (error.response && error.response.data && error.response.data.error) {
        console.error(error.response.data.error.msg)
      } else {
        console.error('请求发生错误')
      }
    }
    return Promise.reject(error)
    // 发送失败
    Promise.reject(error)
  },
)
// 响应拦截器
// service.interceptors.response.use(
//   (response) => {
//     // dataAxios 是 axios 返回数据中的 data
//     const dataAxios = response.data
//     if (typeof dataAxios.data === 'boolean') {
//       return dataAxios.data
//     }
//     if (response.config.responseType == 'blob') {
//       return dataAxios
//     }
//     const { success } = dataAxios
//     if (dataAxios.currentDate) {
//       sessionStorage.currentDate = new Date(dataAxios.currentDate).getTime()
//     }
//     // 根据 code 进行判断
//     if (success) {
//       return dataAxios.data ? dataAxios.data : dataAxios
//     } else {
//       // 提示错误
//     }
//   },
//   (error) => {
//     if ((error.response && error.response.status == 401) || error.code == 'ERR_NETWORK') {
//       localStorage.removeItem(myConfig.tokenKey)
//       localStorage.removeItem('jesk-userInfo')
//       localStorage.removeItem('alreadyElectronicBook')
//       localStorage.removeItem('alreadyPaperBook')
//       localStorage.removeItem('electronicBookList')
//       localStorage.removeItem('paperBookList')
//       sessionStorage.removeItem('cartNumber')
//       // router.replace({
//       //   path: '/home',
//       //   query: {
//       //     showLogin: '1'
//       //   }
//       // })
//       const url = window.location.hash.slice(1)
//       console.log(url)
//       if (url.includes('showLogin=1')) {
//         router.push(url)
//       } else {
//         if (url.includes('?')) {
//           router.push(url + '&showLogin=1')
//         } else {
//           router.push(url + '?showLogin=1')
//         }
//       }
//     } else {
//       if (error.response && error.response.data && error.response.data.error) {
//         console.error(error.response.data.error.msg)
//       } else {
//         console.error('请求发生错误')
//       }
//     }
//     return Promise.reject(error)
//   },
// )
export default service
src/router/index.ts
@@ -1,9 +1,9 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import HomePage from '../views/home/index.vue'
import PageLayout from '../layout/pageLayout.vue'
import { pathData } from '../assets/js/config'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
src/types/aiDetails.d.ts
@@ -1,5 +1,3 @@
export type aiDetails = {
  historylist: Array<any>
  inputValue: string
  message: Object
}
src/types/details.d.ts
@@ -1,3 +1,4 @@
export type details = {
  list: Array<any>
  loading: boolean
}
src/types/home.d.ts
@@ -1,3 +1,4 @@
export type home = {
  searchText: string
  example: string
}
src/types/searchlist.d.ts
@@ -1,4 +1,5 @@
export type searchList = {
  searchText: any
  list: Array<any>
  loading: boolean
}
src/views/ai/aiDetails.vue
@@ -29,7 +29,7 @@
                    </div>
                </div>
                <!-- 消息列表 -->
                <div v-for="(item, index) in pageData.message" :key="index" class="chat-box-content-item">
                <div v-for="(item, index) in message" :key="index" class="chat-box-content-item">
                    <div v-if="item.type === 'user'" class="chat-date">{{ item.time }}</div>
                    <div :class="['message', item.type === 'user' ? 'user-message' : 'assistant-message']">
                        <!-- 助手头像 -->
@@ -43,18 +43,13 @@
                            <div :class="['message-text', 'requestText' + index].join(' ')"
                                v-if="item.type != 'user' && item.txtType == 'txt'">
                                <p class="message-text-title">
                                    AI智能助手为您找到以下{{ pageData.inputValue }}的信息内容如下:
                                    AI智能助手为您找到以下的信息内容如下:
                                </p>
                                <iframe class="message-text-iframe" ref="iframeRef" v-if="item.iframeSrc != ''"
                                    :src="item.iframeSrc" frameborder="0"></iframe>
                                <div v-html="item.content"></div>
                                <p class="message-text-titleOne">以上内容由AI生成,仅供参考。</p>
                            </div>
                            <!-- AI 文本消息 -->
                            <!-- <div :class="['Ai-message-text', 'requestText' + index].join(' ')"
              v-if="item.type != 'user' && item.txtType == 'txt'">
              <div v-html="item.content"></div>
            </div> -->
                            <!-- 消息底部时间 -->
                            <div class="message-footer" v-if="item.type != 'user'">
                                <span class="message-time">{{ item.time }}</span>
@@ -78,8 +73,8 @@
            <div class="chat-footer">
                <!-- 输入框 -->
                <div class="chat-text">
                    <el-input type="textarea" :autosize="{ minRows: 3, maxRows: 3 }" placeholder=""
                        v-model="pageData.inputValue" @keyup.enter="handleEnter($event)" />
                    <el-input type="textarea" :autosize="{ minRows: 3, maxRows: 3 }" placeholder="" v-model="inputValue"
                        @keyup.enter="handleEnter($event)" />
                </div>
                <!-- 功能选择区域 -->
                <div class="select">
@@ -100,12 +95,18 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { ref, reactive, nextTick, computed, onMounted } from 'vue'
import type { aiDetails } from '@/types/aiDetails'
import current from '@/assets/images/ai/current.png'
import noCurrent from '@/assets/images/ai/noCurrent.png'
import dayjs from 'dayjs'
import Header from '@/layout/components/headerPage.vue'
import defaultImg from '../../assets/images/home/AI_logo.png'
import userIcon from '../../assets/images/ai/userImg.png'
import request from '@/plugin/axios/index'
const screenWidth = ref(window.innerWidth)
const assistantAvatar = defaultImg
const userProfilePicture = userIcon
const pageData = reactive<aiDetails>({
    historylist: [
        {
@@ -121,20 +122,170 @@
            content: "What are the quarterly strata fees?",
        },
    ],
    inputValue: "",
    message: "",
})
const inputValue = ref('')
const message = ref<any>([])
const loading = ref(false)
const autoScroll = ref(true)
const messagesContainer = ref<HTMLElement | null>(null)
const currentTime = ref(dayjs(new Date()).format('MM/DD HH:mm:ss'))
const currentImage = computed(() => {
    return pageData.inputValue.trim() === '' ? noCurrent : current;
    return inputValue.value.trim() === '' ? noCurrent : current;
});
// 从本地存储加载消息
onMounted(() => {
    // getCommonQyestuion()
    // 删除本地缓存
    localStorage.removeItem('chatMessages')
    const savedMessages = localStorage.getItem('chatMessages')
    if (savedMessages) {
        message.value = JSON.parse(savedMessages)
    }
    scrollToBottom()
    screenWidth.value = window.innerWidth
const goAi = (e: any) => {
    pageData.inputValue = e.content;
};
    window.addEventListener('message', function (event) {
        console.log(event, "event");
        if (event.data.type == 'toggleFullScreen') {
            window.open(event.data.data)
        } else if (event.data.type == 'download') {
            const link = document.createElement('a')
            link.href = event.data.data
            link.download = event.data.data
            document.body.appendChild(link)
            link.click()
            document.body.removeChild(link)
        }
    })
})
const scrollToBottom = async () => {
    if (!autoScroll.value) return
    await nextTick()
    if (messagesContainer.value) {
        const dom1 = document.querySelector('.dot-loading')
        const dom = document.querySelector('.requestText' + (message.value.length - 1))
        dom1?.scrollIntoView({ behavior: 'smooth', block: 'center' })
        dom?.scrollIntoView({ behavior: 'smooth', block: 'start' })
    }
}
interface ChatMessage {
    content: string
    type: 'user' | 'assistant'
    time: string
    txtType: 'txt' | 'List'
    currentContentIndex: 0
    isEnd: boolean
    iframeSrc: string
}
const handleEnter = (event: any) => {
    event.preventDefault()
    inputValue.value = inputValue.value.replace(/\n/g, '')
    // 调用发送消息的方法
    sendMsg()
}
const sendMsg = async () => {
    if (!inputValue.value.trim() || loading.value) return
    const userMessage: ChatMessage = {
        content: inputValue.value,
        type: 'user',
        time: new Date().toLocaleTimeString(),
        txtType: 'txt',
        currentContentIndex: 0,
        isEnd: false,
        iframeSrc: '',
    }
    message.value.push(userMessage)
    const messageToSend = inputValue.value
    inputValue.value = ''
    try {
        await getData(messageToSend) // 使用 await 等待 getData 完成
        console.log(message, "message");
    } catch (error) {
        console.error('发送消息失败', error)
    }
}
// 获取数据
const getData = async (messageToSend: any) => {
    loading.value = true
    try {
        const response = await request({
            url: '/v1/workflows/run',
            method: 'post',
            data: {
                inputs: {
                    question: messageToSend,
                },
                parent_message_id: null,
                response_mode: 'blocking',
                conversation_id: '',
                user: 'abc-123',
                files: [],
            },
        })
        try {
            const answerAgain = JSON.parse((response as any).data.outputs.text)
            answerAgain.msg = answerAgain.msg.replace(/\n/g, '<br>')
            console.log(answerAgain, 'answerAgain');
            answerAgain.iframeSrc = '';
            if (answerAgain.model && answerAgain.model[0].name == 'Conformer3D_COMPOUND_CID_2244') {
                answerAgain.iframeSrc = 'http://182.92.203.7:3007/showModel/?name=' + answerAgain.model[0].name;
            }
            if (answerAgain.code == 1) {
                const assistantMessage: ChatMessage = {
                    content: answerAgain.data,
                    type: 'assistant',
                    time: new Date().toLocaleTimeString(),
                    txtType: 'List',
                    currentContentIndex: 0,
                    isEnd: false,
                    iframeSrc: answerAgain.iframeSrc,
                }
                message.value.push(assistantMessage)
            } else if (answerAgain.code == 2 || answerAgain.code == 3) {
                const assistantMessage: ChatMessage = {
                    content: answerAgain.msg,
                    type: 'assistant',
                    time: new Date().toLocaleTimeString(),
                    txtType: 'txt',
                    currentContentIndex: 0,
                    isEnd: false,
                    iframeSrc: answerAgain.iframeSrc,
                }
                console.log(assistantMessage);
                message.value.push(assistantMessage)
                loading.value = false
                // 保存消息到本地存储
                localStorage.setItem('chatMessages', JSON.stringify(message.value))
            }
            console.log(message, "message");
        } catch (error) {
            console.log(error)
        }
    } catch (error) {
        const assistantMessage: ChatMessage = {
            content: '当前操作遇到一些问题,请稍后再试。',
            type: 'assistant',
            time: new Date().toLocaleTimeString(),
            txtType: 'txt',
            currentContentIndex: 0,
            isEnd: false,
            iframeSrc: "",
        }
        message.value.push(assistantMessage)
        console.error('发送消息失败', error)
    } finally {
        loading.value = false
    }
}
@@ -187,6 +338,35 @@
    }
}
.message {
    display: flex;
    margin-bottom: 24px;
    align-items: flex-start;
    animation: slideIn 0.3s ease;
    animation-fill-mode: forwards;
    max-width: 85%;
    border-radius: 12px;
    // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    padding: 16px;
}
.assistant-avatar {
    width: 32px;
    height: auto;
}
.user-message {
    flex-direction: row-reverse;
    margin-left: auto;
}
.assistant-message {
    background-color: #f7fffc;
}
.chat-box-right {
    width: 86%;
    margin: 0 auto;
@@ -198,7 +378,8 @@
    .chat-box-content {
        width: 100%;
        width: 62%;
        margin: 0 auto;
        height: calc(100vh - 150px);
        display: flex;
        flex-direction: column;
@@ -282,6 +463,7 @@
                box-sizing: border-box;
                background-color: #f7fffc;
                border-radius: 20px;
                line-height: 1.5em;
            }
        }
@@ -353,8 +535,74 @@
    }
    .message-content {
        display: inline-block;
        max-width: 80%;
        background: #DEDEDE;
        margin-left: 10px;
        border-radius: 10px 0px 10px 10px;
        padding: 14px 12px;
        margin-top: 5px;
        margin-right: 10px;
    }
    .user-message-text {
        font-weight: 400;
        font-size: 14px;
        color: #000000;
        line-height: 1.5em;
    }
    .message-text {
        min-width: 600px;
        padding: 0px 40px;
        border-radius: 12px;
        word-wrap: break-word;
        line-height: 1.6;
        font-size: 0.95rem;
        transition: all 0.3s ease;
        white-space: pre-wrap;
        color: #333;
        .message-text-title {
            font-size: 16px;
            color: #999;
            line-height: 35px;
            padding: 0px 0 10px 0;
        }
        .message-text-iframe {
            width: 100%;
            height: 400px;
            margin-bottom: 10px;
        }
        .message-text-titleOne {
            text-align: right;
            color: #fe7313;
            font-size: 10px;
            padding-bottom: 5px;
            border-bottom: 1px solid #e9d8bf;
        }
        .message-text-changeOne {
            display: flex;
            align-items: center;
            justify-content: flex-end;
            padding: 10px 0 0 0;
            span {
                font-size: 14px;
                font-family:
                    Microsoft YaHei,
                    Microsoft YaHei-Regular;
                font-weight: 400;
                text-align: left;
                color: #d2a25b;
                line-height: 35px;
            }
        }
    }
    .chat-footer {
        width: 62%;
@@ -364,6 +612,7 @@
        padding: 9px 13px;
        box-sizing: border-box;
        margin-bottom: 10px;
        margin-top: 14px;
    }
    ::v-deep(.chat-text) {
src/views/ai/index.vue
@@ -78,6 +78,12 @@
    pageData.inputValue = e.content;
};
const handleEnter = (event: any) => {
    event.preventDefault()
    pageData.inputValue = pageData.inputValue.replace(/\n/g, '')
    // 调用发送消息的方法
    sendMsg()
}
const sendMsg = () => {
    const query = { searchText: pageData.inputValue };
    router.push({ path: pathData.aiDetails, query })
src/views/detailsPage/index.vue
@@ -11,11 +11,11 @@
                        <div class="details-box-left">
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.ID') }}</div>
                                <div class="left-item-content">{{ item.ID }}</div>
                                <div class="left-item-content">{{ item.id }}</div>
                            </div>
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.MolecularFormula') }}</div>
                                <div class="left-item-content">{{ item.MolecularFormula }}</div>
                                <div class="left-item-content" v-html="item.MolecularFormula"></div>
                            </div>
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.MolecularWeight') }}</div>
@@ -23,7 +23,7 @@
                            </div>
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.Smiles') }}</div>
                                <div class="left-item-content">{{ item.Smiles }}</div>
                                <div class="left-item-content">{{ item.SMILES }}</div>
                            </div>
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.XLogP3') }}</div>
@@ -41,28 +41,28 @@
                                <div class="left-item-title">{{ t('message.FormalCharge') }}</div>
                                <div class="left-item-content">{{ item.FormalCharge }}</div>
                            </div>
                            <div class="left-item">
                            <!-- <div class="left-item">
                                <div class="left-item-title">{{ t('message.PubmedID') }}</div>
                                <div class="left-item-content">{{ item.PubmedID }}</div>
                            </div>
                                <div class="left-item-content">{{ item.PubChemCID }}</div>
                            </div> -->
                            <div class="left-item">
                                <div class="left-item-title">{{ t('message.Pubmedlink') }}</div>
                                <div class="left-item-content">{{ item.Pubmedlink }}</div>
                                <div class="left-item-content">{{ item.PubmedLink }}</div>
                            </div>
                        </div>
                        <div class="details-box-right">
                            <div class="right-img2D">
                            <div class="right-img2D" v-if="item.img2D">
                                <img :src="item.img2D" alt="">
                                <div>2D</div>
                            </div>
                            <div class="right-img3D">
                                <img :src="item.img3D" alt="">
                            <div class="right-img3D" v-if="item.img2D">
                                <iframe class="iframe" :src="item.iframeSrc" frameborder="0"></iframe>
                                <div>3D</div>
                            </div>
                        </div>
                    </div>
                    <div class="details-others">
                        <div v-for="(citem, dindex) in item.content" :key="dindex">
                        <div v-for="(citem, cindex) in item.content" :key="cindex">
                            <div class="others-box-title">
                                <div class="title-icon"></div>
                                <div class="title-text">{{ citem.name }}</div>
@@ -72,13 +72,32 @@
                                    <img v-if="citem.type == 'Conformer'" src="../../assets/images/details/getImage.png"
                                        alt="">
                                    <div v-if="citem.type == 'Conformer'" class="content-text">{{ t('message.GetImage')
                                    }}</div>
                                        }}</div>
                                    <img src="../../assets/images/details/download.png" alt="">
                                    <div class="content-text">{{ t('message.download') }}</div>
                                </div>
                                <div class="content-main">
                                    <iframe class="message-text-iframe" ref="iframeRef" :src="citem.iframeSrc"
                                        frameborder="0"></iframe>
                                    <!-- 3D模型 -->
                                    <iframe v-if="citem.name == '3D Conformer'" class="message-text-iframe"
                                        ref="iframeRef" :src="citem.content" frameborder="0"></iframe>
                                    <!-- 2D模型 -->
                                    <div v-if="citem.name == '2D Structure'" class="message-text-img">
                                        <div>Chemical Structure Depiction</div>
                                        <iframe class="message-img-iframe" ref="iframeRef" :src="citem.content"
                                            frameborder="0"></iframe>
                                    </div>
                                    <!-- Charge Distribution -->
                                    <div v-if="citem.name == 'Charge Distribution'"
                                        v-for="(ditem, dindex) in displayContent" :key="dindex">
                                        <div>{{ ditem }}</div>
                                    </div>
                                    <!-- Charge Distribution -->
                                    <div class="ViewMore"
                                        v-if="citem.name == 'Charge Distribution' && citem.content.length > 7"
                                        @click="toggleExpand">
                                        {{ isExpanded ? 'Collapse' : 'View More...' }}
                                    </div>
                                </div>
                            </div>
                        </div>
@@ -90,55 +109,131 @@
</template>
<script setup lang="ts">
import { inject, reactive } from 'vue'
import { ref, inject, reactive, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n';
const { locale, t } = useI18n();
import type { details } from '@/types/details'
import img4D from '../../assets/images/details/img-2D.png'
import img2D from '../../assets/images/serachList/2D.png'
import img3D from '../../assets/images/serachList/3D.png'
import MG from '@/assets/js/middleGround/WebMiddleGroundApi.js'
import router from '@/router'
const pageData = reactive<details>({
    list: [
        {
            name: 'Aspirin',
            ID: "224456FRET",
            XLogP3: "1.2",
            FormalCharge: "123",
            img2D: img2D,
            img3D: img3D,
            HydrogenBondDonorCount: "1",
            HydrogenBondAcceptorCount: "4",
            PubmedID: "0",
            Pubmedlink: "https://pubchem.ncbi.nlm.nih.gov/compound/#CID#",
            content: [
                {
                    name: "Charge Distribution",
                    type: "text",
                },
                {
                    name: "Herbs Containing This Ingredient",
                    type: "text",
                },
                {
                    name: "2D Structure",
                    type: "Conformer",
                },
                {
                    name: "3D Conformer",
                    type: "Conformer",
                    iframeSrc: "11",
                },
            ]
        },
    ]
    ],
    loading: false,
})
onMounted(() => {
    const id = router.currentRoute.value.query.id
    getList(id)
})
const getList = (id: any) => {
    pageData.list = []
    pageData.loading = true
    MG.resource.getItem({
        path: "*",
        itemId: id,
        queryType: '*',
        paging: { start: 0, size: 999999999 },
        searchList: [
            {
                compareType: '',
                keywords: "",
                field: 'string',
                subSearches: ['string'],
            },
        ],
        sort: {
            LinkOrder: 'Desc',
        },
        fields: {
            Synonyms: [],
            MolecularWeight: [],
            SMILES: [],
            XLogP3: [],
            HydrogenBondDonorCount: [],
            HydrogenBondAcceptorCount: [],
            FormalCharge: [],
            ChargeDistribution: [],
            PubChemCID: [],
            PubmedLink: [],
            '2DStructure': [],
            '3DConformer': [],
            MolecularFormula: [],
            SynonymsLine: [],
            "3DStructureSDF": [],
            HerbsContainingThisIngredient: []
        }
    }).then((res: any) => {
        if (res.datas.length > 0) {
            res.datas.forEach((item: any) => {
                if (item.id == 874053) {
                    item.img2D = img2D
                    item.iframeSrc = "http://182.92.203.7:3007/showModel/?name=" + 'Conformer3D_COMPOUND_CID_2244'
                }
                item.content = [
                    {
                        name: "Charge Distribution",
                        content: item.ChargeDistribution ? item.ChargeDistribution.split('\n') : [],
                        type: "text",
                    },
                    // {
                    //     name: "Herbs Containing This Ingredient",
                    //     content: item.HerbsContainingThisIngredient,
                    //     type: "text",
                    // },
                    {
                        name: "2D Structure",
                        content: img4D,
                        type: "Conformer",
                    },
                    {
                        name: "3D Conformer",
                        content: "http://182.92.203.7:3007/showModel/?name=" + 'Conformer3D_COMPOUND_CID_2244',
                        type: "Conformer",
                    }
                ]
            })
            pageData.list = res.datas
        }
        pageData.loading = false
    })
}
const isExpanded = ref(false);
const displayContent = computed(() => {
    // 假设你只对第一个数据项做展开/收起控制
    const item = pageData.list[0];
    if (!item) return [];
    const chargeDistItem = item.content.find((c: any) => c.name === 'Charge Distribution');
    if (!chargeDistItem) return [];
    return isExpanded.value
        ? chargeDistItem.content
        : chargeDistItem.content.slice(0, 8);
});
const toggleExpand = () => {
    isExpanded.value = !isExpanded.value;
};
</script>
<style lang="less" scoped>
.detailsPage {
    height: 100%;
    border-top: 1px solid #D9D9D9;
    background-color: #ecf9f6;
}
.iframe {
    margin-bottom: 10px;
}
.detailsPage-box {
@@ -290,11 +385,40 @@
            .content-main {
                width: 100%;
                height: 282px;
                min-height: 282px;
                border: 1px solid #EBEBEB;
                border-radius: 0px 0px 5px 5px;
                margin-bottom: 20px;
                padding: 22px 36px;
                overflow: auto;
                line-height: 2em;
                .message-text-iframe {
                    height: 250px;
                    width: 100%;
                }
                .message-text-img {
                    height: 300px;
                    display: flex;
                    justify-content: space-around;
                }
                .message-img-iframe {}
                .ViewMore {
                    font-weight: 400;
                    font-size: 12px;
                    color: #006CB6;
                    cursor: pointer;
                }
            }
        }
src/views/home/index.vue
@@ -20,7 +20,7 @@
                </div>
                <div class="page-search">
                    <div class="search-data">
                        <div class="left-See">{{ t('message.SeeMore') }} ></div>
                        <div class="left-See" @click="gotoPage()">{{ t('message.SeeMore') }} ></div>
                        <div class="left-data">121M</div>
                        <div class="left-text">{{ t('message.Compounds') }}</div>
                    </div>
@@ -28,13 +28,16 @@
                        <div class="searchTop">
                            <div class="searchInputBox">
                                <input v-model="pageData.searchText" type="text" placeholder="" />
                                <el-button color="#16569C" :icon="Search" class="search-btn" round
                                <el-icon v-if="pageData.searchText != ''" class="clear-icon" @click="clearInput">
                                    <CloseBold />
                                </el-icon>
                                <el-button color="#01644c" :icon="Search" class="search-btn" round
                                    @click="gotoPage()"></el-button>
                            </div>
                            <ul class="searchExample">
                                <li class="example">{{ t('message.Example') }}:</li>
                                <li class="example-item">aspirin</li>
                                <li class="example-item">aspirin</li>
                                <li class="example-item" @click="gotoList(pageData.example)">{{ pageData.example }}
                                </li>
                            </ul>
                        </div>
                        <div class="searchBottom">
@@ -71,8 +74,13 @@
];
const pageData = reactive<home>({
    searchText: ""
    searchText: "",
    example: "aspirin"
})
const clearInput = () => {
    pageData.searchText = ""
}
const currentLocale = computed({
    get: () => locale.value,
@@ -86,6 +94,11 @@
const gotoPage = () => {
    const query = { searchText: pageData.searchText };
    router.push({ path: pathData.searchList, query })
}
const gotoList = (txt: any) => {
    pageData.searchText = txt
    gotoPage()
}
const goAi = () => {
@@ -276,6 +289,7 @@
        height: 100%;
        width: 8.1%;
        border-radius: 60px;
        font-size: 28px !important;
    }
@@ -289,6 +303,14 @@
        border: none;
        outline: none
    }
    .clear-icon {
        color: #212121;
        font-size: 18px !important;
        margin-right: 5px;
        cursor: pointer;
    }
    .searchExample {
        width: 91%;
@@ -312,6 +334,7 @@
            margin-right: 15px;
            margin-left: 15px;
            padding: 2px 5px;
            cursor: pointer;
        }
    }
src/views/searchList/index.vue
@@ -4,23 +4,26 @@
            <div class="search-input">
                <div class="searchInputBox">
                    <input v-model="pageData.searchText" type="text" placeholder="" />
                    <el-icon v-if="pageData.searchText != ''" class="clear-icon" @click="clearInput">
                        <CloseBold />
                    </el-icon>
                    <el-button color="#16569C" :icon="Search" class="search-btn" round
                        @click="getList(pageData.searchText)" style=""></el-button>
                </div>
            </div>
            <div class="search-list">
            <div class="search-list" v-loading="pageData.loading">
                <div class="search-result">
                    {{ t('message.searchResult') }} ({{ pageData.list.length }})
                </div>
                <div class="search-item" v-for="(item, index) in pageData.list" :key="index" @click="goPage(item)">
                    <div class="search-item-name">{{ item.name }}</div>
                    <div class="search-item-synonyms">
                        <span>{{ t('message.synonyms') }}:</span>{{ item.synonyms }}
                        <span>{{ t('message.synonyms') }}:</span>{{ item.Synonyms }}
                    </div>
                    <div class="search-item-box">
                        <div class="box-left">
                            <div class="box-left-item">
                                <span>{{ t('message.ID') }}:</span>{{ item.ID }}
                                <span>{{ t('message.ID') }}:</span>{{ item.id }}
                            </div>
                            <div class="box-left-MolecularFormula">
                                <span>{{ t('message.MolecularFormula') }}:</span>
@@ -29,8 +32,9 @@
                            <div class="box-left-item">
                                <span>{{ t('message.MolecularWeight') }}:</span>{{ item.MolecularWeight }}
                            </div>
                            <div class="box-left-item">
                                <span>{{ t('message.Smiles') }}:</span>{{ item.Smiles }}
                            <div class="box-left-MolecularFormula">
                                <span>{{ t('message.Smiles') }}:</span>
                                <div v-html="item.SMILES"></div>
                            </div>
                            <div class="box-left-item">
                                <span>{{ t('message.XLogP3') }}:</span>{{ item.XLogP3 }}
@@ -47,14 +51,15 @@
                            </div>
                        </div>
                        <div class="box-right">
                            <div>
                            <div v-if="item.img2D">
                                <img :src="item.img2D" alt="" />
                                <div>2D</div>
                            </div>
                            <div>
                                <img :src="item.img3D" alt="" />
                            <div v-if="item.iframeSrc">
                                <iframe class="iframe" :src="item.iframeSrc" frameborder="0"></iframe>
                                <div>3D</div>
                            </div>
                            <el-empty class="empty" image-size="100" v-if="!item.img2D || !item.iframeSrc"></el-empty>
                        </div>
                    </div>
                </div>
@@ -64,7 +69,7 @@
</template>
<script setup lang="ts">
import { inject, reactive, onMounted } from 'vue'
import { ref, inject, reactive, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import type { searchList } from '@/types/searchlist'
import { useI18n } from 'vue-i18n'
@@ -74,30 +79,34 @@
import img2D from '../../assets/images/serachList/2D.png'
import img3D from '../../assets/images/serachList/3D.png'
import MG from '@/assets/js/middleGround/WebMiddleGroundApi.js'
import type { id } from 'element-plus/es/locales.mjs'
const config: any = inject('config')
const pageData = reactive<searchList>({
    searchText: '',
    list: [
        {
            name: 'Aspirin',
            FormalCharge:
                '9999999999999999999999999999999999999999999999999999999999999999999999989999999999999999999999999999999999999999999',
            img2D: img2D,
            img3D: img3D,
        },
    ],
    list: [],
    loading: false
})
const isInitialized = ref(false);
onMounted(() => {
    pageData.searchText = router.currentRoute.value.query.searchText
    if (!isInitialized.value && router.currentRoute.value.query.searchText) {
        pageData.searchText = router.currentRoute.value.query.searchText;
        isInitialized.value = true;
    }
    getList(pageData.searchText)
})
const clearInput = () => {
    pageData.searchText = ''
}
const getList = (txt: any) => {
    pageData.list = []
    const searchData = {
        'Name*': pageData.searchText
    }
    console.log(searchData);
    pageData.loading = true
    MG.resource.getItem({
        path: config.refCodes.BioChargeMap,
        queryType: '*',
@@ -114,10 +123,9 @@
            LinkOrder: 'Desc',
        },
        fields: {
            ID: '',
            Synonyms: [],
            MolecularWeight: [],
            SMILE: [],
            SMILES: [],
            XLogP3: [],
            HydrogenBondDonorCount: [],
            HydrogenBondAcceptorCount: [],
@@ -133,16 +141,25 @@
        }
    }).then((res: any) => {
        if (res.datas.length > 0) {
            res.datas.forEach((item: any) => {
                // item.SynonymsLine = item.Synonyms.join(';')
                if (item.id == 874053) {
                    item.img2D = img2D
                    item.iframeSrc = "http://182.92.203.7:3007/showModel/?name=" + 'Conformer3D_COMPOUND_CID_2244'
                }
            })
            pageData.list = res.datas
            pageData.list = res.datas.reverse()
        }
        pageData.loading = false
    })
}
const goPage = (item: any) => {
    router.push(pathData.detailsPage)
    const query = { id: item.id }
    router.push({ path: pathData.detailsPage, query })
}
</script>
@@ -184,10 +201,18 @@
        outline: none;
    }
    .clear-icon {
        color: #212121;
        font-size: 18px !important;
        margin-right: 5px;
        cursor: pointer;
    }
    .search-btn {
        height: 100%;
        width: 7.1%;
        border-radius: 60px;
        font-size: 28px !important;
    }
}
@@ -208,12 +233,11 @@
    border-radius: 10px 10px 10px 10px;
    padding: 17px 27px;
    margin-bottom: 24px;
    border: 1px solid #ebebeb;
    &:hover {
        cursor: pointer;
        border: 2px solid;
        border-image: linear-gradient(90deg, rgba(0, 108, 182, 1), rgba(1, 100, 76, 1)) 2 2;
        border-radius: 10px 10px 10px 10px;
        border: 1px solid rgba(1, 100, 76, 1);
    }
    .search-item-name {
@@ -259,8 +283,10 @@
        .box-left-MolecularFormula {
            display: flex;
            padding-bottom: 12px;
            div {
                color: #000;
                font-size: 13px;
            }
        }
@@ -294,9 +320,9 @@
    .box-right {
        width: 35%;
        padding: 30px 48px;
        padding: 30px 10px;
        display: flex;
        align-items: center;
        align-items: end;
        justify-content: space-between;
        img {
@@ -311,6 +337,17 @@
    }
    .empty {
        width: 30%;
        margin: 0 auto;
        --el-empty-padding: 0px 0 !important;
    }
}
.iframe {
    width: 220px;
    margin-bottom: 20px;
}
.fl {
tsconfig.app.json
@@ -1,12 +1,20 @@
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "exclude": ["src/**/__tests__/*"],
  "include": [
    "env.d.ts",
    "src/**/*",
    "src/**/*.vue"
  ],
  "exclude": [
    "src/**/__tests__/*"
  ],
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "paths": {
      "@/*": ["./src/*"]
      "@/*": [
        "./src/*"
      ]
    }
  }
}
}
tsconfig.json
@@ -1,4 +1,8 @@
{
  "compilerOptions": {
    "allowJs": true,
    "moduleResolution": "node",
  },
  "files": [],
  "references": [
    {
@@ -8,4 +12,4 @@
      "path": "./tsconfig.app.json"
    }
  ]
}
}