Commit 5f3adbdc authored by 水玉婷's avatar 水玉婷
Browse files

feat:微信端项目初始化

parent 78707a69
Pipeline #31768 failed with stages
in 2 seconds
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Dependencies
node_modules/
# Build output
dist/
build/
# Environment variables
.env
.env.local
.env.development.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Vite cache
.vite/
# Local History for Visual Studio Code
.history/
\ No newline at end of file
# ai-wechat # 微信H5项目
基于Vue 3 + Vite构建的微信H5项目,支持静默登录和路由跳转。
## 功能特性
## Getting started - ✅ Vue 3 + Vite 现代化开发环境
- ✅ Vue Router 路由管理
- ✅ 微信静默登录(snsapi_base)
- ✅ 响应式设计,适配移动端
- ✅ 生产环境打包优化
To make it easy for you to get started with GitLab, here's a list of recommended next steps. ## 快速开始
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! ### 安装依赖
```bash
## Add your files npm install
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin http://10.17.65.20/gitlab/shuiyuting/ai-wechat.git
git branch -M main
git push -uf origin main
``` ```
## Integrate with your tools ### 开发环境
```bash
- [ ] [Set up project integrations](http://10.17.65.20/gitlab/shuiyuting/ai-wechat/-/settings/integrations) npm run dev
```
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License ### 生产构建
For open source projects, say how it is licensed. ```bash
npm run build
```
## Project status ## 项目结构
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. \ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>国械小智</title>
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
\ No newline at end of file
{
"name": "ai-wechat",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ai-wechat",
"version": "1.0.0",
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^3.2.0",
"dayjs": "^1.11.0",
"echarts": "^6.0.0",
"event-source-polyfill": "^1.0.31",
"vue": "^3.3.0",
"vue-router": "^4.2.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
"less": "^4.4.2",
"vite": "^4.3.0"
}
},
"node_modules/@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0"
}
},
"node_modules/@ant-design/icons-svg": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
"license": "MIT"
},
"node_modules/@ant-design/icons-vue": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-6.1.0.tgz",
"integrity": "sha512-EX6bYm56V+ZrKN7+3MT/ubDkvJ5rK/O2t380WFRflDcVFgsvl3NLH7Wxeau6R8DbrO5jWR6DSTC3B6gYFp77AA==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.2.1"
},
"peerDependencies": {
"vue": ">=3.0.3"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@simonwep/pickr": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz",
"integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==",
"license": "MIT",
"dependencies": {
"core-js": "^3.15.1",
"nanopop": "^2.1.0"
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
"integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^4.0.0 || ^5.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
"integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.24",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
"integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
"integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.24",
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
"integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/reactivity": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.24",
"@vue/shared": "3.5.24",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24"
},
"peerDependencies": {
"vue": "3.5.24"
}
},
"node_modules/@vue/shared": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"license": "MIT"
},
"node_modules/ant-design-vue": {
"version": "3.2.20",
"resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-3.2.20.tgz",
"integrity": "sha512-YWpMfGaGoRastIXEYfCoJiaRiDHk4chqtYhlKQM5GqPt6NfvrM1Vg2e60yHtjxlZjed91wCMm0rAmyUr7Hwzdg==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@babel/runtime": "^7.10.5",
"@ctrl/tinycolor": "^3.4.0",
"@simonwep/pickr": "~1.8.0",
"array-tree-filter": "^2.1.0",
"async-validator": "^4.0.0",
"dayjs": "^1.10.5",
"dom-align": "^1.12.1",
"dom-scroll-into-view": "^2.0.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.15",
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.25",
"shallow-equal": "^1.0.0",
"vue-types": "^3.0.0",
"warning": "^4.0.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ant-design-vue"
},
"peerDependencies": {
"vue": ">=3.2.0"
}
},
"node_modules/array-tree-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==",
"license": "MIT"
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/compute-scroll-into-view": {
"version": "1.0.20",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
"license": "MIT"
},
"node_modules/copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-what": "^3.14.1"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/core-js": {
"version": "3.47.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
},
"node_modules/dom-align": {
"version": "1.12.4",
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz",
"integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==",
"license": "MIT"
},
"node_modules/dom-scroll-into-view": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
"license": "MIT"
},
"node_modules/echarts": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "6.0.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/event-source-polyfill": {
"version": "1.0.31",
"resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz",
"integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==",
"license": "MIT"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"dev": true,
"license": "MIT",
"optional": true,
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-plain-object": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"dev": true,
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/less": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/less/-/less-4.4.2.tgz",
"integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"license": "MIT",
"optional": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/nanopop": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz",
"integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==",
"license": "MIT"
},
"node_modules/needle": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
"license": "MIT"
},
"node_modules/rollup": {
"version": "3.29.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
"dev": true,
"license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/sax": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"optional": true
},
"node_modules/scroll-into-view-if-needed": {
"version": "2.2.31",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
"license": "MIT",
"dependencies": {
"compute-scroll-into-view": "^1.0.20"
}
},
"node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/shallow-equal": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==",
"license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/vite": {
"version": "4.5.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
"integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-sfc": "3.5.24",
"@vue/runtime-dom": "3.5.24",
"@vue/server-renderer": "3.5.24",
"@vue/shared": "3.5.24"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-router": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
"integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/vue-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
"integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
"license": "MIT",
"dependencies": {
"is-plain-object": "3.0.1"
},
"engines": {
"node": ">=10.15.0"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/zrender": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
}
}
}
{
"name": "ai-wechat",
"version": "1.0.0",
"description": "微信H5项目,支持静默登录",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.0",
"vue-router": "^4.2.0",
"@ant-design/icons-vue": "^6.1.0",
"echarts": "^6.0.0",
"ant-design-vue": "^3.2.0",
"dayjs": "^1.11.0",
"event-source-polyfill": "^1.0.31"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
"vite": "^4.3.0",
"less": "^4.4.2"
}
}
\ No newline at end of file
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif;
background-color: #f5f5f5;
-webkit-font-smoothing: antialiased;
}
#app {
min-height: 100vh;
}
/* 微信浏览器样式优化 */
.wechat-browser {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif;
}
</style>
\ No newline at end of file
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
\ No newline at end of file
import { createRouter, createWebHistory } from 'vue-router'
import wechat from '../utils/wechat'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { requiresAuth: true }
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/:pathMatch(.*)*',
redirect: '/'
}
]
const router = createRouter({
history: createWebHistory('/ai/'), // 添加基础路径
routes
})
// 简化的路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
const status = wechat.checkLoginStatus()
if (!status.isLoggedIn) {
next('/login')
return
}
}
next()
})
export default router
\ No newline at end of file
// 简化的微信静默登录工具类
class WeChatLogin {
constructor() {
this.appId = 'YOUR_WECHAT_APPID' // 替换为你的微信公众号AppID
this.isWeChat = this.checkWeChatBrowser()
this.isConfigured = this.appId !== 'YOUR_WECHAT_APPID'
}
// 检查是否为微信浏览器
checkWeChatBrowser() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
}
// 简化的静默登录
async silentLogin() {
// 如果未配置appId,使用模拟登录
if (!this.isConfigured) {
console.log('使用模拟静默登录')
return this.mockLogin()
}
// 如果是微信浏览器,执行真实静默登录
if (this.isWeChat) {
return this.realWeChatLogin()
}
// 非微信浏览器,使用模拟登录
console.log('非微信浏览器,使用模拟登录')
return this.mockLogin()
}
// 真实微信静默登录
realWeChatLogin() {
return new Promise((resolve, reject) => {
// 检查URL中是否已有授权code
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get('code')
if (code) {
// 已有code,直接获取用户信息
this.getUserInfo(code).then(resolve).catch(reject)
} else {
// 重定向到微信授权页面进行静默授权
this.redirectToWeChat()
}
})
}
// 重定向到微信授权页面
redirectToWeChat() {
const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname)
const scope = 'snsapi_base' // 静默授权,不弹出授权页面
const state = 'STATE_' + Date.now()
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`
window.location.href = authUrl
}
// 获取用户信息(简化版)
async getUserInfo(code) {
try {
// 这里需要调用后端接口来获取用户信息
// 微信不允许前端直接调用获取用户信息的接口
const response = await fetch('/api/wechat/userinfo', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error('获取用户信息失败')
}
const userInfo = await response.json()
// 保存用户信息到本地存储
localStorage.setItem('wechat_user', JSON.stringify(userInfo))
return {
isLoggedIn: true,
userInfo: userInfo,
message: '微信静默登录成功'
}
} catch (error) {
console.error('获取用户信息失败:', error)
throw new Error('登录失败,请重试')
}
}
// 模拟登录(用于开发和测试)
mockLogin() {
return new Promise((resolve) => {
setTimeout(() => {
const mockUser = {
openid: 'mock_openid_' + Date.now(),
nickname: '测试用户',
headimgurl: '',
isMock: true
}
localStorage.setItem('wechat_user', JSON.stringify(mockUser))
resolve({
isLoggedIn: true,
userInfo: mockUser,
message: '模拟登录成功'
})
}, 1000) // 模拟网络延迟
})
}
// 检查登录状态
checkLoginStatus() {
const userData = localStorage.getItem('wechat_user')
if (userData) {
try {
const userInfo = JSON.parse(userData)
return {
isLoggedIn: true,
userInfo: userInfo
}
} catch (error) {
console.error('解析用户数据失败:', error)
}
}
return { isLoggedIn: false }
}
// 退出登录
logout() {
localStorage.removeItem('wechat_user')
}
// 获取当前用户信息
getCurrentUser() {
const status = this.checkLoginStatus()
return status.isLoggedIn ? status.userInfo : null
}
}
export default new WeChatLogin()
\ No newline at end of file
<template>
<AiChat
:params="chatParams"
:dialogSessionId="dialogSessionId"
:detailData="detailData"
:apiBaseUrl="apiBaseUrl"
:token="userToken"
:appCode="appCode"
customClass="chat-demo"
/>
</template>
<script setup lang="ts">
import AiChat from './components/AiChat.vue';
import { ref } from 'vue';
const apiBaseUrl = '/pedapi';
// 获取token
const userToken = '65c76a8b38f350bd1849d41d3185c3eb';
// 添加APP_CODE配置
const appCode = 'ped.pc';
const chatParams = {
appId: '83b2664019a945d0a438abe6339758d8',
stage: 'wechat-demo',
};
// const dialogSessionId = '20251028143404893-00045166';
const dialogSessionId = '';
const detailData = ref({
title: '国械小智',
});
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="login-container">
<div class="login-content">
<div v-if="loading" class="loading">
<div class="spinner"></div>
<p>正在静默登录中...</p>
</div>
<div v-else-if="error" class="error">
<p class="error-text">{{ error }}</p>
<button @click="retryLogin" class="retry-btn">重试登录</button>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import wechat from '../utils/wechat'
export default {
name: 'Login',
setup() {
const router = useRouter()
const loading = ref(true)
const error = ref('')
const handleLogin = async () => {
try {
loading.value = true
error.value = ''
const result = await wechat.silentLogin()
if (result.isLoggedIn) {
// 登录成功,跳转到首页
setTimeout(() => {
router.replace('/')
}, 500)
}
} catch (err) {
console.error('登录失败:', err)
error.value = err.message || '登录失败,请重试'
loading.value = false
}
}
const retryLogin = () => {
handleLogin()
}
onMounted(() => {
// 检查是否已登录
const status = wechat.checkLoginStatus()
if (status.isLoggedIn) {
router.replace('/')
return
}
// 执行静默登录
handleLogin()
})
return {
loading,
error,
retryLogin
}
}
}
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f5f5;
}
.login-content {
text-align: center;
padding: 2rem;
}
.loading p {
margin-top: 1rem;
color: #666;
font-size: 1rem;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #07c160;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
padding: 2rem;
}
.error-text {
color: #e74c3c;
margin-bottom: 1rem;
font-size: 1rem;
}
.retry-btn {
background: #07c160;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
}
.retry-btn:hover {
background: #06ae56;
}
</style>
\ No newline at end of file
<template>
<div class="chat-container" :class="props?.customClass">
<!-- 聊天头部 -->
<div class="chat-header" v-if="props.dialogSessionId || hasStartedConversation">
<div class="header-avatar">
<img :src="props.logoUrl || defaultAvatar" alt="avatar" class="avatar-image" />
</div>
<div class="header-info">
<h2>{{ props.dialogSessionId ? props?.detailData?.title || '继续对话' : '新建对话' }}</h2>
</div>
</div>
<!-- 当没有dialogSessionId且未开始对话时显示介绍页面 -->
<div class="chat-intro-center" v-if="!props.dialogSessionId && !hasStartedConversation">
<div class="intro-content">
<img :src="defaultAvatar" alt="avatar" class="avatar-image" />
<h3>嗨,我是国械小智</h3>
<p>可以问我公司经营情况、制度等相关问题,<br>我还在成长中,会不断强大</p>
</div>
</div>
<!-- 消息区域 -->
<div class="chat-messages" ref="messagesContainer" v-if="props.dialogSessionId || hasStartedConversation">
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.type]">
<div class="avatar-container">
<div class="avatar">
<template v-if="msg.type === 'received'">
<img :src="props.logoUrl || defaultAvatar" alt="avatar" class="avatar-image" />
</template>
<template v-else>
<user-outlined />
</template>
</div>
<div class="message-time">{{ msg.date }}</div>
</div>
<div class="message-content-wrapper">
<div class="message-content">
<template v-for="(item, i) in msg.contentBlocks" :key="i">
<!-- 图表内容块 -->
<div v-if="item.chartData" class="chart-block">
<ChartComponent :chart-data="item.chartData" :chart-type="item.chartType || 3"
:title="item.chartData.title || '图表数据'" />
</div>
<!-- 普通内容块 -->
<div v-else v-html="item.content" class="message-inner-box"></div>
<!-- 思考过程框 -->
<div v-if="item.hasThinkBox" class="think-box-wrapper">
<div class="think-box-toggle" @click="toggleThinkBox(index, i)">{{
item.thinkBoxExpanded ? '▲ 收起思考过程' : '▼ 展开思考过程'
}}</div>
<div v-if="item.thinkBoxExpanded" class="think-box-content"
v-html="contentTemplates.thinking(item.thinkContent || '')"></div>
</div>
</template>
</div>
<!-- 操作按钮 -->
<div class="operation-box" v-if="msg.recordId">
<p>
<span>提示词Token数量:{{ msg.promptTokens }}</span>
<span>答复Token数量:{{ msg.completionTokens }}</span>
<span>总Token数量: {{ msg.totalTokens }}</span>
</p>
</div>
</div>
</div>
</div>
<!-- 输入区域 - 始终显示 -->
<div class="chat-input-container">
<div class="chat-input">
<textarea ref="textarea" v-model="messageText" placeholder="输入消息..." @keypress="handleKeyPress"
@input="adjustTextareaHeight" :disabled="loading"></textarea>
<button @click="sendMessage" :disabled="loading">
<send-outlined />
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ref, nextTick, onMounted, onBeforeUnmount } from 'vue';
import { SendOutlined, UserOutlined } from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import defaultAvatar from '@/assets/logo.png';
import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件
// 定义消息类型 - 更新接口添加图表相关字段
interface Message {
type: 'received' | 'sent';
avatar: string;
recordId: string;
promptTokens: number;
completionTokens: number;
totalTokens: number;
date: string;
customClass?: string;
contentBlocks: {
content: string;
thinkContent?: string;
hasThinkBox: boolean;
thinkBoxExpanded: boolean;
chartData?: any; // 添加图表数据字段
chartType?: number | string; // 添加图表类型字段
}[];
}
interface Token {
value: string;
expire: number;
}
interface SSEData {
message: any;
status: number | string;
}
interface ChatParams {
stage?: string;
appId?: string;
}
// 组件属性
const props = withDefaults(
defineProps<{
params?: ChatParams;
dialogSessionId?: string;
detailData?: {
title?: string;
};
apiBaseUrl?: string;
token?: string;
appCode?: string;
logoUrl?: string;
onMessageSend?: (message: string) => Promise<any>;
onGetChatRecord?: (dialogSessionId: string) => Promise<any>;
customClass?: string;
}>(),
{
params: () => ({
appId: 'app-test',
stage: 'wechat-demo',
}),
apiBaseUrl: '/pedapi',
logoUrl: '',
},
);
// 内容模板生成器
const contentTemplates = {
// 普通文本
text: (content: string) => {
return `<div class="message-text">${content}</div>`;
},
// 思考过程
thinking: (content: string) => {
const formattedContent = content
.split('\n')
.map((line) => `<div class="think-line">${line}</div>`)
.join('');
return `<div class="think-content">${formattedContent}</div>`;
},
// 错误信息
error: (content: string) => {
return `<div class="message-error">${content}</div>`;
},
// 生成表格HTML
table: (tableData: any) => {
// 处理SSE返回的JSON数组数据
// tableData 就是SSE消息中的message字段,直接是你提供的JSON数组
const data = Array.isArray(tableData) ? tableData : [];
if (data.length === 0) {
return `<div class="message-table">
<div class="table-title">数据表格</div>
<div class="table-empty">暂无数据</div>
</div>`;
}
// 动态生成表头 - 使用第一条数据的键名
const headers = Object.keys(data[0]);
// 判断列是否为数字列
const isNumericColumn = (header: string) => {
return data.some(row => {
const value = row[header];
return typeof value === 'number' || (!isNaN(parseFloat(value)) && isFinite(value));
});
};
// 数字格式化函数 - 万/亿格式化,保留两位小数,向上取整
const formatNumber = (value: any) => {
if (value === null || value === undefined || value === '') return '';
const num = parseFloat(value);
if (isNaN(num)) return value;
if (num >= 100000000) {
// 亿级别
const result = Math.ceil((num / 100000000) * 100) / 100;
return result.toFixed(2) + '亿';
} else if (num >= 10000) {
// 万级别
const result = Math.ceil((num / 10000) * 100) / 100;
return result.toFixed(2) + '';
} else {
// 小于万
return Math.ceil(num).toString();
}
};
// 处理单元格内容,特别处理趋势列和数字列
const renderCellContent = (header: string, value: any) => {
// 如果是趋势列,根据up/down值显示箭头图标
if (header.toLowerCase().includes('趋势') || header.toLowerCase().includes('trend')) {
const trendValue = String(value).toLowerCase().trim();
if (trendValue === 'up' || trendValue === '上升' || trendValue === '上涨') {
return `<span class="trend-up">↑</span>`;
} else if (trendValue === 'down' || trendValue === '下降' || trendValue === '下跌') {
return `<span class="trend-down">↓</span>`;
}
}
// 如果是数字列,进行格式化
if (isNumericColumn(header)) {
return formatNumber(value);
}
// 其他列保持原样
return value;
};
// 判断列是否为趋势列
const isTrendColumn = (header: string) => {
return header.toLowerCase().includes('趋势') || header.toLowerCase().includes('trend');
};
// 为不同列类型添加对应的样式类
const getCellClass = (header: string) => {
if (isTrendColumn(header)) {
return 'trend-cell'; // 趋势列居中
} else if (isNumericColumn(header)) {
return 'numeric-cell'; // 数字列右对齐
} else {
return 'text-cell'; // 文字列左对齐
}
};
const tableHTML = `
<div class="message-table">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
${headers.map(header => `<th class="${getCellClass(header)}">${header}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
${headers.map(header => `
<td class="${getCellClass(header)}">
${renderCellContent(header, row[header])}
</td>
`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
<div class="table-footer">共 <span>${data.length}</span> 条记录</div>
</div>
`;
return tableHTML;
},
// 简化的iframe模板 - 移除全屏功能,设置宽高100%固定
iframe: (iframeData: any) => {
const src = iframeData.src;
return `<div class="message-iframe iframe-loading">
<!-- 加载状态 -->
<div class="iframe-loading">
<div class="loading-spinner"></div>
<div class="loading-text">正在加载内容...</div>
<div class="loading-progress">
<div class="progress-bar"></div>
</div>
</div>
<!-- iframe容器 -->
<iframe
src="${src}"
width="100%"
height="100%"
frameborder="0"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
scrolling="no"
style="overflow: hidden;"
onload="this.parentElement.classList.add('iframe-loaded'); this.parentElement.classList.remove('iframe-loading');"
onerror="console.error('iframe加载失败:', this.src)"
></iframe>
</div>`;
}
};
// 响应式数据
const messageText = ref('');
const messages = ref<Message[]>([]);
const messagesContainer = ref<HTMLDivElement>();
const textarea = ref<HTMLTextAreaElement>();
const loading = ref(false);
const currentAIResponse = ref<Message | null>(null);
const isAIResponding = ref(false);
const eventSource = ref<EventSourcePolyfill | null>(null);
const dialogSessionId = ref(props.dialogSessionId || '');
const isInThinkingMode = ref(false);
const currentBlockIndex = ref(-1);
const isReconnecting = ref(false);
const timeArr = ref([]);
const hasStartedConversation = ref(false); // 添加对话开始状态
// 开始对话函数 - 修改为在发送消息时调用
const startConversation = () => {
hasStartedConversation.value = true;
};
// 发送消息
const sendMessage = async () => {
loading.value = true;
const message = messageText.value.trim();
if (message) {
// 开始对话
startConversation();
isAIResponding.value = false;
isInThinkingMode.value = false;
currentAIResponse.value = null;
messages.value.push({
type: 'sent',
avatar: '',
recordId: '',
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
date: dayjs().format('HH:mm'),
contentBlocks: [
{
content: contentTemplates.text(message),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
}
],
});
if (textarea.value) {
textarea.value.style.height = '50px';
}
await nextTick();
scrollToBottom();
try {
messageText.value = '';
// 模拟AI返回iframe数据
setTimeout(() => {
// 创建AI响应消息
const aiMessage = {
type: 'received',
avatar: 'AI',
recordId: '111',
promptTokens: 1110,
completionTokens: 11,
totalTokens: 1110,
date: dayjs().format('HH:mm'),
contentBlocks: [
{
content: contentTemplates.text('这是模拟的AI响应,包含一个嵌入的iframe:涵盖了销售指标的展示'),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
},
{
content: contentTemplates.iframe({
src:'/WeChatOauth2/MobileReport_Monthly/MonthlyReport_index.aspx?postage=384b67414b334f2f693177644246313756704a724d513d3d&company=器械整体&typename=整体指标',
}),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
}
],
};
messages.value.push(aiMessage);
nextTick(() => {
scrollToBottom();
});
loading.value = false;
}, 1000); // 1秒后模拟AI响应
// 调用外部传入的消息发送函数
if (props.onMessageSend) {
console.log('调用外部消息发送函数', message);
await props.onMessageSend(message);
} else {
// 默认的API调用逻辑
console.log('默认API调用逻辑', dialogSessionId);
const response = await fetch(`${props.apiBaseUrl}/aiService/ask/app/${props.params?.appId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Token: props.token || '',
'x-session-id': props.token || '',
'x-app-code': props.appCode || '',
},
body: JSON.stringify({
question: message,
...props.params,
}),
});
const data = await response.json();
if (data.code === 0) {
loading.value = false;
}
}
} catch (e) {
console.error('发送消息失败:', e);
} finally {
loading.value = false;
}
}
};
// 处理SSE消息的核心方法 - 添加图表类型处理
const processSSEMessage = (
data: SSEData,
currentResponse: Message | null,
isThinking: boolean,
currentBlockIndex: number,
isHistoryData = false,
) => {
let messageContent = data.message || '';
const contentType = data.status;
let updatedResponse = currentResponse;
let updatedIsThinking = isThinking;
let updatedBlockIndex = currentBlockIndex;
let recordId = '';
let promptTokens = 0;
let completionTokens = 0;
let totalTokens = 0;
let newDialogSessionId = '';
// 根据是否为历史数据设置默认展开状态
const defaultThinkBoxExpanded = !isHistoryData;
switch (contentType) {
case -1: // 错误信息
if (updatedResponse) {
updatedResponse.contentBlocks.push({
content: contentTemplates.error(messageContent || ''),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
}
break;
case 3: // 图表数据
if (updatedResponse) {
const { rows } = messageContent;
updatedResponse.contentBlocks.push({
content: contentTemplates.table(rows),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
updatedResponse.contentBlocks.push({
content: '', // 图表内容由组件处理,这里留空
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
chartData: messageContent, // 添加图表数据
chartType: 3, // 使用object类型
});
}
break;
case 4: // iframe数据
if (updatedResponse) {
updatedResponse.contentBlocks.push({
content: contentTemplates.iframe(messageContent),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
}
break;
case 10: // 思考开始
updatedIsThinking = true;
if (updatedBlockIndex === -1 && updatedResponse) {
updatedBlockIndex = updatedResponse.contentBlocks.length;
updatedResponse.contentBlocks.push({
content: '',
thinkContent: `${messageContent}`,
hasThinkBox: true,
thinkBoxExpanded: defaultThinkBoxExpanded,
});
} else if (updatedResponse && updatedResponse.contentBlocks[updatedBlockIndex]) {
updatedResponse.contentBlocks[updatedBlockIndex].thinkContent += ``;
updatedResponse.contentBlocks[updatedBlockIndex].hasThinkBox = true;
updatedResponse.contentBlocks[updatedBlockIndex].thinkBoxExpanded =
defaultThinkBoxExpanded;
}
break;
case 11: // 思考结束
if (
updatedResponse &&
updatedBlockIndex !== -1 &&
updatedResponse.contentBlocks[updatedBlockIndex]
) {
updatedResponse.contentBlocks[updatedBlockIndex].thinkContent += ``;
updatedResponse.contentBlocks[updatedBlockIndex].hasThinkBox = true;
updatedResponse.contentBlocks[updatedBlockIndex].thinkBoxExpanded =
defaultThinkBoxExpanded;
}
updatedIsThinking = false;
break;
case 20: // 初始连接回传信息
if (updatedResponse) {
updatedResponse.contentBlocks.push({
content: contentTemplates.text(messageContent?.words || ''),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
}
newDialogSessionId = messageContent?.dialogSessionId || '';
break;
case 21: // 重连成功正回传信息
newDialogSessionId = messageContent?.dialogSessionId || '';
break;
case 29: // 当前会话结束
recordId = messageContent?.recordId || '';
promptTokens = messageContent?.promptTokens || 0;
completionTokens = messageContent?.completionTokens || 0;
totalTokens = messageContent?.totalTokens || 0;
// 只有实时对话才在29时折叠思考框,历史数据不受影响
if (!isHistoryData && updatedResponse && updatedResponse.contentBlocks.length > 0) {
updatedResponse.contentBlocks.forEach((block) => {
if (block.hasThinkBox) {
block.thinkBoxExpanded = false;
}
});
}
updatedIsThinking = false;
updatedBlockIndex = -1;
break;
default: // 普通内容
if (updatedIsThinking && updatedResponse) {
if (updatedBlockIndex !== -1 && updatedResponse.contentBlocks[updatedBlockIndex]) {
updatedResponse.contentBlocks[updatedBlockIndex].thinkContent += `\n${messageContent}`;
updatedResponse.contentBlocks[updatedBlockIndex].hasThinkBox = true;
updatedResponse.contentBlocks[updatedBlockIndex].thinkBoxExpanded =
defaultThinkBoxExpanded;
}
} else if (updatedResponse) {
updatedBlockIndex = updatedResponse.contentBlocks.length;
updatedResponse.contentBlocks.push({
content: contentTemplates.text(messageContent),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
}
break;
}
return {
updatedResponse,
updatedIsThinking,
updatedBlockIndex,
recordId,
promptTokens,
completionTokens,
totalTokens,
newDialogSessionId,
};
};
// 重新连接SSE(添加重连间隔控制)
const reconnectSSE = (newDialogSessionId: string) => {
if (isReconnecting.value) {
console.log('正在重连中,跳过重复重连');
return;
}
isReconnecting.value = true;
console.log('开始重连SSE,新的dialogSessionId:', newDialogSessionId);
closeSSE();
dialogSessionId.value = newDialogSessionId;
// 添加重连间隔控制,避免频繁重连
const reconnectTimeout = setTimeout(() => {
initSSE();
// 重连完成后重置标志
setTimeout(() => {
isReconnecting.value = false;
}, 2000); // 延长重连间隔
}, 500); // 增加重连延迟
timeArr.value.push(reconnectTimeout);
};
// 关闭SSE连接(增强清理逻辑)
const closeSSE = () => {
if (eventSource.value) {
try {
eventSource.value.close();
eventSource.value = null;
} catch (err) {
console.warn('关闭SSE连接时出错:', err);
}
}
// 清理所有定时器
timeArr.value.forEach(timeout => {
clearTimeout(timeout);
});
timeArr.value = [];
};
// 初始化SSE连接(添加错误边界)
const initSSE = () => {
try {
const url = `${props.apiBaseUrl}/aiService/sse/join/${props.params?.stage || ''}?app-id=${props.params?.appId || ''
}&dialog-session-id=${dialogSessionId.value || ''}`;
console.log('初始化SSE连接,dialogSessionId:', dialogSessionId.value);
eventSource.value = new EventSourcePolyfill(url, {
headers: {
Token: props.token || '',
'x-session-id': props.token || '',
'x-app-code': props.appCode || '',
},
withCredentials: true,
connectionTimeout: 30000, // 缩短超时时间
});
eventSource.value.onopen = (event) => {
console.log('SSE连接已建立', event);
};
eventSource.value.addEventListener('message', async (event) => {
try {
console.log('Received message:', event);
const data: SSEData = JSON.parse(event.data);
// 对于type21消息,先检查是否有实际内容再决定是否创建新消息
if (data.status === 21) {
const messageContent = data.message || '';
const hasContent = messageContent?.words && messageContent.words.trim() !== '';
// 只有有实际内容时才创建新消息
if (!isAIResponding.value && hasContent) {
isAIResponding.value = true;
currentAIResponse.value = {
type: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
date: dayjs().format('HH:mm'),
contentBlocks: [],
};
messages.value.push(currentAIResponse.value);
}
} else {
// 其他消息类型保持原有逻辑
if (!isAIResponding.value) {
isAIResponding.value = true;
currentAIResponse.value = {
type: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
date: dayjs().format('HH:mm'),
contentBlocks: [],
};
messages.value.push(currentAIResponse.value);
}
}
// 实时消息处理,isHistoryData设为false,新会话思考框展开
const result = processSSEMessage(
data,
currentAIResponse.value,
isInThinkingMode.value,
currentBlockIndex.value,
false,
);
currentAIResponse.value = result.updatedResponse;
isInThinkingMode.value = result.updatedIsThinking;
currentBlockIndex.value = result.updatedBlockIndex;
// 如果type21没有内容,需要清理可能创建的空白消息
if (data.status === 21 && currentAIResponse.value &&
currentAIResponse.value.contentBlocks.length === 0) {
// 移除空白消息
const lastIndex = messages.value.length - 1;
if (lastIndex >= 0 && messages.value[lastIndex] === currentAIResponse.value) {
messages.value.splice(lastIndex, 1);
currentAIResponse.value = null;
isAIResponding.value = false;
}
}
if (result.recordId && currentAIResponse.value) {
currentAIResponse.value.recordId = result.recordId;
currentAIResponse.value.promptTokens = result.promptTokens;
currentAIResponse.value.completionTokens = result.completionTokens;
currentAIResponse.value.totalTokens = result.totalTokens;
}
if (result.newDialogSessionId) {
console.log('收到新的 dialogSessionId:', result.newDialogSessionId);
dialogSessionId.value = result.newDialogSessionId;
}
await nextTick();
scrollToBottom();
} catch (error) {
console.error('处理SSE消息时出错:', error);
}
});
eventSource.value.onerror = (error) => {
console.error('SSE error:', error);
isAIResponding.value = false;
isInThinkingMode.value = false;
closeSSE();
// 添加错误重连逻辑
if (!isReconnecting.value) {
setTimeout(() => {
if (dialogSessionId.value) {
reconnectSSE(dialogSessionId.value);
}
}, 3000);
}
};
} catch (err) {
console.error('初始化SSE连接失败:', err);
}
};
// 组件卸载时清理所有资源
onBeforeUnmount(() => {
closeSSE();
isAIResponding.value = false;
isInThinkingMode.value = false;
});
// 处理历史记录数据
const processHistoryData = (dataArray: any[]) => {
const result: Message[] = [];
dataArray.forEach((data) => {
let date = dayjs(data.startTime).format('YYYY-MM-DD HH:mm:ss');
if (data.question) {
result.push({
type: 'sent',
avatar: '',
recordId: '',
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
date,
contentBlocks: [
{
content: contentTemplates.text(data.question),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
},
],
});
}
if (data.answerInfoList && Array.isArray(data.answerInfoList)) {
const aiMessage: Message = {
type: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
contentBlocks: [],
date,
};
let currentThinkingMode = false;
let currentBlockIdx = -1;
// 历史数据处理,isHistoryData设为true,思考框折叠
data.answerInfoList.forEach((answer) => {
const sseData: SSEData = {
message: answer.message || '',
status: answer.status || 0,
};
const processResult = processSSEMessage(
sseData,
aiMessage,
currentThinkingMode,
currentBlockIdx,
true,
);
currentThinkingMode = processResult.updatedIsThinking;
currentBlockIdx = processResult.updatedBlockIndex;
aiMessage.recordId = processResult.recordId;
aiMessage.promptTokens = processResult.promptTokens;
aiMessage.completionTokens = processResult.completionTokens;
aiMessage.totalTokens = processResult.totalTokens;
});
// 确保历史记录中的思考框默认折叠
aiMessage.contentBlocks.forEach((block) => {
if (block.hasThinkBox) {
block.thinkBoxExpanded = false;
}
});
if (aiMessage.contentBlocks.length > 0) {
result.push(aiMessage);
}
}
});
return result;
};
// 获取历史会话消息
const getChatRecord = async (dialogSessionId: string) => {
if (props.onGetChatRecord) {
const res = await props.onGetChatRecord(dialogSessionId);
if (res && res.code === 0) {
const recordList = processHistoryData(res.data || []);
messages.value = [...recordList];
}
} else {
const response = await fetch(`${props.apiBaseUrl}/aiService/ask/list/chat/${dialogSessionId}`, {
headers: {
Token: props.token || '',
'x-session-id': props.token || '',
'x-app-code': props.appCode || '',
},
});
const data = await response.json();
if (data.code === 0) {
const recordList = processHistoryData(data.data || []);
messages.value = [...recordList];
} else {
console.error('获取历史记录失败:', data);
}
}
};
// 思考框切换
const toggleThinkBox = (messageIndex: number, blockIndex: number) => {
if (messages.value[messageIndex] && messages.value[messageIndex].contentBlocks[blockIndex]) {
messages.value[messageIndex].contentBlocks[blockIndex].thinkBoxExpanded =
!messages.value[messageIndex].contentBlocks[blockIndex].thinkBoxExpanded;
}
};
// 处理按键事件
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
// 自动调整文本区域高度
const adjustTextareaHeight = () => {
if (textarea.value) {
textarea.value.style.height = 'auto';
textarea.value.style.height = `${Math.min(textarea.value.scrollHeight, 65)}px`;
}
};
// 滚动到底部
const scrollToBottom = () => {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
}
};
// 暴露组件方法给外部使用
defineExpose({
// SSE相关方法
initSSE, // 初始化SSE连接
closeSSE, // 关闭SSE连接
reconnectSSE, // 重新连接SSE
// 消息处理方法
processSSEMessage, // 处理SSE消息
processHistoryData, // 处理历史记录数据
// 其他实用方法
sendMessage, // 发送消息
getChatRecord, // 获取聊天记录
toggleThinkBox, // 切换思考框
// 数据访问
messages, // 消息列表
currentAIResponse, // 当前AI响应
isAIResponding, // 是否正在响应
isInThinkingMode // 是否在思考模式
});
// 生命周期
onMounted(() => {
console.log('组件挂载,初始 dialogSessionId:', props.dialogSessionId);
initSSE();
scrollToBottom();
if (props.dialogSessionId) {
getChatRecord(props.dialogSessionId);
}
});
onBeforeUnmount(() => {
closeSSE();
// 清除重连超时
timeArr.value.forEach((item) => {
clearTimeout(item);
});
});
</script>
<style lang="less" scoped>
@import './style.less';
</style>
\ No newline at end of file
<template>
<div class="message-chart">
<div class="chart-title">{{ title }}</div>
<div v-if="isEmpty" class="chart-empty">
<div class="empty-icon">📊</div>
<div class="empty-text">暂无数据</div>
<div class="empty-desc">当前查询条件下没有找到相关数据</div>
</div>
<div v-else ref="chartContainer" class="chart-container"></div>
<div v-if="error" class="chart-error">{{ error }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
// 定义组件属性
interface Props {
chartData: any;
chartType?: any;
title?: string;
width?: number | string;
height?: number | string;
}
const props = withDefaults(defineProps<Props>(), {
chartType: { type: 'column' },
title: '数据图表',
width: '100%',
height: 'auto' // 设置为auto,完全自适应
});
const chartContainer = ref<HTMLElement>();
const chartInstance = ref<echarts.ECharts | null>(null);
const error = ref<string>('');
const isEmpty = ref<boolean>(false);
// ========== 工具函数抽离 ==========
/**
* 检查数据是否为空
*/
const checkDataEmpty = (data: any): boolean => {
if (!data) return true;
if (!data.rows || !Array.isArray(data.rows)) return true;
if (data.rows.length === 0) return true;
// 检查所有行是否都是空数据
const hasValidData = data.rows.some((row: any) => {
if (data.indexFields && Array.isArray(data.indexFields)) {
return data.indexFields.some((field: string) => {
const value = row[field];
return value !== null && value !== undefined && value !== '' && !isNaN(Number(value));
});
}
return false;
});
return !hasValidData;
};
/**
* 数字格式化工具
*/
const formatNumber = (value: any): string => {
if (value === null || value === undefined || isNaN(value) || value === '') {
return '0';
}
const numValue = Number(value);
if (isNaN(numValue)) return '0';
if (numValue === 0) return '0';
const roundedValue = Math.ceil(numValue * 100) / 100;
if (Math.abs(roundedValue) >= 100000000) {
return `${(roundedValue / 100000000).toFixed(2)}亿`;
} else if (Math.abs(roundedValue) >= 10000) {
return `${(roundedValue / 10000).toFixed(2)}万`;
} else {
return roundedValue.toFixed(2);
}
};
/**
* 获取颜色方案
*/
const getColors = () => ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1', '#fa8c16'];
/**
* 获取tooltip数据值
*/
const getTooltipValue = (param: any): any => {
if (param.data !== undefined && param.data !== null) return param.data;
if (param.value !== undefined && param.value !== null) return param.value;
return null;
};
/**
* 数据格式化工具
*/
const formatData = (data: any) => {
if (data && data.rows && Array.isArray(data.rows) && data.dimFields && data.indexFields) {
const { rows, dimFields, indexFields } = data;
if (rows.length === 0) {
return { isEmpty: true, data: [], chartType: 3 };
}
const chartConfig: any = { data: rows, chartType: 3 };
// 维度字段处理
if (dimFields.length === 1) {
chartConfig.xField = dimFields[0];
chartConfig.isGroup = false;
} else if (dimFields.length >= 2) {
chartConfig.xField = dimFields[0];
chartConfig.groupField = dimFields[1];
chartConfig.isGroup = true;
}
// 指标字段处理
if (indexFields.length === 1) {
chartConfig.yField = indexFields[0];
chartConfig.isMultiY = false;
} else if (indexFields.length >= 2) {
chartConfig.isMultiY = true;
chartConfig.yFields = indexFields;
chartConfig.xField = dimFields[0];
chartConfig.isGroup = dimFields.length >= 2;
}
return chartConfig;
} else {
throw new Error('不支持的数据格式,请使用新的结构化数据格式');
}
};
// ========== 图表配置生成器 ==========
/**
* 基础图表配置
*/
const getBaseChartConfig = () => ({
responsive: true,
animation: true,
animationDuration: 500,
animationEasing: 'cubicOut' as const,
grid: {
left: '3%',
right: '3%',
bottom: '3%',
top: '70px', // 改为固定值,为legend留出空间
containLabel: true
}
});
/**
* 基础tooltip配置
*/
const getBaseTooltipConfig = () => ({
trigger: 'item' as const,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#e8e8e8',
borderWidth: 1,
textStyle: {
color: '#333',
fontSize: 12
}
});
/**
* 基础x轴配置
*/
const getXAxisConfig = (xAxisData: string[]) => ({
type: 'category' as const,
data: xAxisData,
axisLabel: {
interval: 0,
rotate: xAxisData.length > 6 ? 45 : 0,
margin: 8,
fontSize: 12
}
});
/**
* 基础y轴配置
*/
const getYAxisConfig = (name?: string) => ({
type: 'value' as const,
...(name && { name }),
axisLabel: {
formatter: (value: number) => formatNumber(value)
},
splitLine: {
lineStyle: {
type: 'dashed' as const,
color: '#ccc',
width: 1
}
}
});
/**
* 双y轴配置
*/
const getDualYAxisConfig = (yFields: string[]) => [
{
...getYAxisConfig(yFields[0]),
position: 'left' as const,
splitLine: { lineStyle: { type: 'dashed' as const, color: '#ccc', width: 1 } }
},
{
...getYAxisConfig(yFields[1]),
position: 'right' as const,
splitLine: { lineStyle: { type: 'solid' as const, color: '#e8e8e8', width: 1 } }
}
];
/**
* 基础系列配置
*/
const getBaseSeriesConfig = (name: string, data: any[], color: string, yAxisIndex = 0) => ({
name,
type: 'bar' as const,
data,
yAxisIndex,
itemStyle: { color },
barWidth: 'auto' as const,
barGap: '30%',
barCategoryGap: '40%'
});
// ========== 图表类型配置生成器 ==========
/**
* 单指标柱状图配置生成器
*/
const createSingleColumnOption = (chartConfig: any): echarts.EChartsOption => {
const colors = getColors();
const xAxisData = [...new Set(chartConfig.data.map((item: any) => item[chartConfig.xField]))];
if (chartConfig.isGroup && chartConfig.groupField) {
const groups = [...new Set(chartConfig.data.map((item: any) => item[chartConfig.groupField]))];
const series = groups.map((group, index) => {
const groupData = chartConfig.data
.filter((item: any) => item[chartConfig.groupField] === group)
.map((item: any) => item[chartConfig.yField]);
return getBaseSeriesConfig(group, groupData, colors[index % colors.length]);
});
return {
...getBaseChartConfig(),
tooltip: {
...getBaseTooltipConfig(),
formatter: createTooltipFormatter()
},
legend: {
data: groups,
orient: 'horizontal',
left: 'center',
top: '0',
padding: [15, 20, 20, 25], // 增加四周的padding
itemGap: 15,
type: 'scroll',
textStyle: {
fontSize: 12
},
pageTextStyle: {
fontSize: 10
},
pageIconSize: 12,
pageButtonItemGap: 5
},
xAxis: getXAxisConfig(xAxisData),
yAxis: getYAxisConfig(),
series
};
} else {
const seriesData = chartConfig.data.map((item: any) => item[chartConfig.yField]);
return {
...getBaseChartConfig(),
tooltip: {
...getBaseTooltipConfig(),
formatter: createTooltipFormatter()
},
legend: { show: false },
xAxis: getXAxisConfig(xAxisData),
yAxis: getYAxisConfig(),
series: [getBaseSeriesConfig(chartConfig.yField, seriesData, colors[0])]
};
}
};
/**
* 双轴柱状图配置生成器
*/
const createDualColumnOption = (chartConfig: any): echarts.EChartsOption => {
const colors = getColors();
const xAxisData = [...new Set(chartConfig.data.map((item: any) => item[chartConfig.xField]))];
if (chartConfig.isGroup && chartConfig.groupField) {
const groups = [...new Set(chartConfig.data.map((item: any) => item[chartConfig.groupField]))];
const series1 = groups.map((group, index) => {
const groupData = chartConfig.data
.filter((item: any) => item[chartConfig.groupField] === group)
.map((item: any) => item[chartConfig.yFields[0]]);
return getBaseSeriesConfig(`${group} - ${chartConfig.yFields[0]}`, groupData, colors[index % colors.length], 0);
});
const series2 = groups.map((group, index) => {
const groupData = chartConfig.data
.filter((item: any) => item[chartConfig.groupField] === group)
.map((item: any) => item[chartConfig.yFields[1]]);
return getBaseSeriesConfig(`${group} - ${chartConfig.yFields[1]}`, groupData, colors[(index + 3) % colors.length], 1);
});
return {
...getBaseChartConfig(),
tooltip: {
...getBaseTooltipConfig(),
formatter: createTooltipFormatter()
},
legend: {
data: [...series1, ...series2].map(s => s.name),
orient: 'horizontal',
left: 'center',
top: '0',
padding: [15, 20, 20, 25], // 增加四周的padding
itemGap: 15,
type: 'scroll',
textStyle: {
fontSize: 12
},
pageTextStyle: {
fontSize: 10
},
pageIconSize: 12,
pageButtonItemGap: 5
},
xAxis: getXAxisConfig(xAxisData),
yAxis: getDualYAxisConfig(chartConfig.yFields),
series: [...series1, ...series2]
};
} else {
const series1Data = chartConfig.data.map((item: any) => item[chartConfig.yFields[0]]);
const series2Data = chartConfig.data.map((item: any) => item[chartConfig.yFields[1]]);
return {
...getBaseChartConfig(),
tooltip: {
...getBaseTooltipConfig(),
formatter: createTooltipFormatter()
},
legend: { data: [chartConfig.yFields[0], chartConfig.yFields[1]] },
xAxis: getXAxisConfig(xAxisData),
yAxis: getDualYAxisConfig(chartConfig.yFields),
series: [
getBaseSeriesConfig(chartConfig.yFields[0], series1Data, colors[0], 0),
getBaseSeriesConfig(chartConfig.yFields[1], series2Data, colors[1], 1)
]
};
}
};
/**
* 创建tooltip格式化器
*/
const createTooltipFormatter = () => {
return (params: any) => {
// 在item模式下,params是单个对象
const dataValue = getTooltipValue(params);
const seriesName = params.seriesName;
const xAxisValue = params.name || params.axisValue;
if (dataValue === null || dataValue === undefined) return '';
const formattedValue = formatNumber(dataValue);
return `${xAxisValue}<br/>${seriesName}: ${formattedValue}`;
};
};
// ========== 响应式处理逻辑 ==========
/**
* 创建响应式处理函数
*/
const createResizeHandler = (chartConfig: any, chartTypeForLogic: string) => {
let resizeTimer: NodeJS.Timeout;
return () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (chartInstance.value && chartContainer.value) {
const newOption = chartTypeForLogic === 'column'
? createSingleColumnOption(chartConfig)
: createDualColumnOption(chartConfig);
// 完全自适应配置
chartInstance.value.setOption({
...newOption,
animation: false
});
chartInstance.value.resize(); // ECharts会自动适应容器大小
}
}, 100);
};
};
/**
* 初始化图表大小监听
*/
const initChartResizeListener = (chartConfig: any, chartTypeForLogic: string) => {
const handleResize = createResizeHandler(chartConfig, chartTypeForLogic);
// 窗口大小变化监听
window.addEventListener('resize', handleResize);
// ResizeObserver监听
if (typeof ResizeObserver !== 'undefined' && chartContainer.value) {
const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(chartContainer.value);
onUnmounted(() => {
resizeObserver.disconnect();
});
}
// 清理函数
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
};
// ========== 主图表创建函数 ==========
/**
* 创建ECharts图表
*/
const createChart = () => {
try {
if (!chartContainer.value) return;
// 检查数据是否为空
isEmpty.value = checkDataEmpty(props.chartData);
if (isEmpty.value) {
// 清理之前的图表实例
if (chartInstance.value) {
chartInstance.value.dispose();
chartInstance.value = null;
}
error.value = '';
return;
}
const chartConfig = formatData(props.chartData);
// 根据数据特征自动选择图表类型
const chartTypeForLogic = chartConfig.isMultiY && chartConfig.yFields && chartConfig.yFields.length > 1
? 'dualColumn'
: 'column';
// 创建ECharts实例
chartInstance.value = echarts.init(chartContainer.value);
// 生成图表配置
const option = chartTypeForLogic === 'column'
? createSingleColumnOption(chartConfig)
: createDualColumnOption(chartConfig);
// 设置图表选项
chartInstance.value.setOption(option);
error.value = '';
// 初始化响应式监听
initChartResizeListener(chartConfig, chartTypeForLogic);
} catch (err: any) {
error.value = `图表渲染失败: ${err.message}`;
console.error('图表渲染失败:', err);
}
};
// ========== 生命周期和响应式 ==========
// 监听数据变化
watch(() => props.chartData, createChart);
onMounted(() => {
nextTick(createChart);
});
// 组件卸载时清理
onUnmounted(() => {
if (chartInstance.value) {
chartInstance.value.dispose();
}
});
</script>
<style scoped>
.message-chart {
margin: 16px 0;
/* 完全自适应,不设置固定高度 */
/* 确保宽度不超过父容器 */
box-sizing: border-box; /* 包含边框和内边距在宽度计算中 */
}
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
color: #333;
width: 100%; /* 标题宽度限制 */
box-sizing: border-box;
}
.chart-container {
border: 1px solid #e8e8e8;
border-radius: 4px;
/* 正常图表保持宽高比自适应 */
aspect-ratio: 3 / 2; /* 宽高比 3:2 */
min-height: 200px; /* 最小高度确保显示效果 */
width: 100%; /* 确保宽度不超过父容器 */
box-sizing: border-box; /* 包含边框在宽度计算中 */
overflow: hidden; /* 防止内容溢出导致滚动条 */
}
/* 空状态样式 */
.chart-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 350px;
border: 1px solid #e8e8e8;
border-radius: 4px;
background-color: #fafafa;
color: #999;
width: 100%; /* 确保宽度不超过父容器 */
box-sizing: border-box; /* 包含边框在宽度计算中 */
overflow: hidden; /* 防止内容溢出 */
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #e8e8e8;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.empty-text {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
color: #666;
text-align: center; /* 文字居中,避免溢出 */
max-width: 100%; /* 文字宽度限制 */
}
.empty-desc {
font-size: 14px;
color: #999;
text-align: center; /* 文字居中,避免溢出 */
max-width: 100%; /* 文字宽度限制 */
}
</style>
\ No newline at end of file
// 全局滚动条美化 - 悬浮式不占位滚动条
* {
padding: 0;
margin: 0;
box-sizing: border-box;
// Webkit浏览器滚动条样式(Chrome, Safari, Edge)
&::-webkit-scrollbar {
width: 0px; // 垂直滚动条宽度
height: 0px; // 水平滚动条高度
}
}
p,h1,h2,h3,h4,h5,h6,ul,ol,li{
margin:0;
padding:0;
}
.chat-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 8px 30px rgba(91, 138, 254, 0.15); // 修改阴影颜色
overflow: hidden;
width: 100%;
display: flex;
flex-direction: column;
height: 100vh;
// 居中介绍页面样式
.chat-intro-center {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
.intro-content {
text-align: center;
max-width: 400px;
width: 100%;
.avatar-image {
width: 180px;
height: 180px;
border-radius: 50%;
margin-bottom: 20px;
}
h3 {
font-size: 24px;
color: #333;
margin-bottom: 12px;
font-weight: 600;
}
p {
font-size: 16px;
color: #666;
line-height: 1.5;
margin-bottom: 30px;
}
.start-chat-btn {
background: linear-gradient(135deg, #5B8AFE 0%, #7BA6FF 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(91, 138, 254, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(91, 138, 254, 0.4);
}
&:active {
transform: translateY(0);
}
}
}
}
// 聊天头部样式保持不变
.chat-header {
display: flex;
align-items: center;
}
// 消息区域样式保持不变
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 12px;
}
// 输入容器保持在底部
.chat-input-container {
padding: 20px;
border-top: 1px solid #e8f2f1;
background: #FCFCFC;
// 确保输入容器始终在底部
flex-shrink: 0;
}
.header-avatar {
width: 45px;
height: 45px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin-right: 15px;
border: 2px solid rgba(255, 255, 255, 0.3);
img{
width:100%;
height:auto;
}
}
.header-info {
flex: 1;
h2 {
font-size: 18px;
}
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding:12px;
}
.message-time {
color: #88a5d0; // 修改时间颜色为蓝色系
margin-bottom: 6px;
font-size: 14px;
}
.message {
display: flex;
margin-bottom: 20px;
flex-direction: column;
align-items: baseline;
}
.message.sent {
align-items: flex-end;
.message-time {
text-align: right;
}
.avatar-container {
flex-direction: row-reverse;
justify-content: flex-end;
}
}
.avatar-container {
display: flex;
align-items: center;
}
.avatar {
width: 42px;
height: 42px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
background-color: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: white;
border: 2px solid white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
img{
width:100%;
height:auto;
}
}
.message.received .avatar {
margin-right: 12px;
}
.message.sent .avatar {
background: linear-gradient(135deg, #A8C6FF 0%, #C2D6FF 100%); // 修改发送方头像背景为浅蓝色
margin-left: 12px;
}
.message-content-wrapper {
max-width: 100%;
margin-top: 8px;
min-width: 150px;
// 当包含图表、表格或iframe时,宽度为100%
&:has(.message-table),
&:has(.message-chart),
&:has(.message-iframe){
width: 100%;
min-width: 100%;
max-width: 100%;
}
}
.message-content {
padding: 10px;
border-radius: 8px;
line-height: 1.5;
position: relative;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
white-space: pre-wrap;
.message-inner-box {
font-size: 0;
:deep(.message-text) {
font-size: 14px;
}
}
}
.message.received .message-content {
background-color: #f0f5ff; // 修改回复方消息背景为浅蓝色
border-top-left-radius: 5px;
color: #333;
border: 1px solid #e0e8ff; // 修改边框颜色
}
.message.sent .message-content {
background: linear-gradient(135deg, #5B8AFE 0%, #7BA6FF 100%); // 修改发送方消息背景为#5B8AFE
border-top-right-radius: 5px;
color: white; // 修改文字颜色为白色
border: 1px solid #4a7df5; // 修改边框颜色
}
:deep(.message-error) {
line-height: 1.5;
color: #c33;
white-space: pre-wrap;
font-size: 14px;
}
.think-box-wrapper {
margin: 12px 0;
padding: 10px 15px;
background-color: #f5f8f7;
border-radius: 12px;
border: 1px solid #e3ecea;
transition: all 0.3s ease;
}
:deep(.think-box-toggle) {
color: #5B8AFE; // 修改思考框切换按钮颜色
cursor: pointer;
font-size: 12px;
text-decoration: none;
align-items: center;
gap: 4px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.2s ease;
}
:deep(.think-box-content) {
margin-top: 8px;
background-color: #ffffff;
padding: 12px 16px;
border-radius: 8px;
border: 1px solid #e9ecef;
font-size: 0px;
color: #495057;
white-space: pre-wrap;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
animation: fadeIn 0.3s ease-in-out;
.think-content {
font-size: 0;
.think-line {
font-size: 13px;
color: #999;
font-style: italic;
}
}
}
.chat-input-container {
padding: 20px;
border-top: 1px solid #e8f2f1;
background: #FCFCFC;
}
.chat-input {
display: flex;
align-items: flex-end;
position: relative; // 添加相对定位
}
.chat-input textarea {
flex: 1;
padding: 14px 70px 14px 18px; // 增加右侧内边距为按钮留出更多空间
border-radius: 12px;
outline: none;
resize: none;
height: 52px;
font-size: 15px;
transition: border-color 0.3s, box-shadow 0.3s;
background-color: #f8faff;
border: 1px solid #E0E0E0;
overflow: hidden;
position: relative;
/* 投影/大投影 */
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.12);
}
.chat-input button {
position: absolute;
right: 12px;
top: 50%;
color: #5B8AFE;
background: none;
border: none;
width: 40px;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: color 0.2s, border-color 0.2s, background-color 0.2s;
z-index: 10;
transform: translateY(-50%);
}
.chat-input button:hover {
color: #4a7df5; // 悬停时颜色变深
border-color: #4a7df5; // 悬停时边框颜色变深
background-color: rgba(91, 138, 254, 0.05); // 悬停时添加轻微背景色
}
.chat-input button:active {
background-color: rgba(91, 138, 254, 0.1); // 点击时背景色加深
transform: translateY(-50%) scale(0.95); // 保持垂直居中并缩小
}
.chat-input button:disabled {
color: #cccccc; // 禁用状态颜色
border-color: #cccccc; // 禁用状态边框颜色
background-color: transparent; // 禁用状态背景透明
cursor: not-allowed;
transform: translateY(-50%); // 保持垂直居中,不移除transform
}
.chat-input textarea:focus {
border-color: #5B8AFE; // 修改输入框焦点边框颜色
box-shadow: 0 0 0 2px rgba(91, 138, 254, 0.1); // 修改输入框焦点阴影
}
.chat-input textarea:disabled {
background-color: #f5f5f5;
border-color: #e0e0e0;
color: #999;
cursor: not-allowed;
}
.operation-box {
margin-top: 6px;
p {
color: #999;
font-size: 12px;
span {
margin-right: 15px;
}
}
}
}
@media (max-width: 600px) {
.header-avatar {
width: 40px;
height: 40px;
}
}
// 表格消息样式
:deep(.message-table) {
width: 100%;
max-width: 100%;
margin: 8px 0;
// 表格容器,用于包裹表格和滚动条
.table-container {
width: 100%;
overflow-x: auto; // 添加水平滚动条支持
-webkit-overflow-scrolling: touch; // iOS平滑滚动
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
// 滚动条样式
&::-webkit-scrollbar {
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a8a8a8;
}
}
}
.data-table {
width: auto; // 改为自动宽度,允许表格超出容器
min-width: 100%; // 确保表格至少与容器一样宽
border-collapse: collapse;
background-color: white;
table-layout: auto; // 改为自动布局,允许列宽自适应内容
// 文字列样式 - 左对齐
.text-cell {
text-align: left;
padding-left: 12px;
padding-right: 8px;
}
// 数字列样式 - 右对齐
.numeric-cell {
text-align: right;
padding-left: 8px;
padding-right: 12px;
font-family: 'Courier New', monospace; // 使用等宽字体便于数字对齐
font-weight: 500;
}
// 趋势列样式 - 居中
.trend-cell {
text-align: center;
padding-left: 8px;
padding-right: 8px;
}
th {
background: linear-gradient(135deg, #5B8AFE 0%, #4a7df5 100%); // 修改表格表头背景
color: white;
font-weight: 600;
padding: 12px 8px;
font-size: 14px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
height: 40px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
&:last-child {
border-right: none;
}
}
td {
padding: 10px 8px;
font-size: 14px;
border-bottom: 1px solid #f0f0f0;
color: #333;
height: 35px; // 固定单元格高度,替换line-height
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle; // 确保内容垂直居中
min-width: 80px; // 设置最小列宽,与表头保持一致
&:nth-child(odd) {
background-color: #f8faff; // 修改奇数行背景色为浅蓝色
}
&:nth-child(even) {
background-color: white;
}
}
tr:hover td {
background-color: #e8f6f4;
}
tr:last-child td {
border-bottom: none;
}
}
// 趋势箭头样式
.trend-up {
color: #52c41a;
font-weight: bold;
font-size: 16px;
}
.trend-down {
color: #f5222d;
font-weight: bold;
font-size: 16px;
}
.table-footer {
margin-top: 12px;
font-size: 14px;
span {
color: #2eb0a1;
font-weight: bold;
}
}
}
// 响应式表格样式
@media (max-width: 768px) {
:deep(.message-table) {
.data-table {
font-size: 12px;
th, td {
padding: 8px 4px;
height: 30px; // 移动端减小高度
min-width: 60px; // 移动端减小最小列宽
}
}
.table-title {
font-size: 14px;
}
.table-summary {
font-size: 12px;
padding: 8px 10px;
}
}
}
@media (max-width: 480px) {
:deep(.message-table) {
.data-table {
th, td {
min-width: 50px; // 更小屏幕进一步减小最小列宽
height: 28px; // 更小屏幕进一步减小高度
}
}
}
}
// 表格消息样式
:deep(.message-iframe) {
width: 100%;
max-width: 100%;
margin: 8px 0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
iframe {
width: 100%;
height: 100%;
min-height: 1800px;
border: none;
border-radius: 8px;
background-color: #f8f9fa;
transition: height 0.5s ease-in-out, opacity 0.3s ease;
// 加载状态样式
&[src=""] {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
}
// 加载状态
&.iframe-loading {
iframe {
opacity: 0;
pointer-events: none;
min-height: 400px;
}
.iframe-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
z-index: 10;
}
}
// 加载完成状态
&.iframe-loaded {
iframe {
opacity: 1;
}
.iframe-loading {
display: none;
}
}
// 加载动画
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top: 4px solid #5B8AFE;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
.loading-text {
font-size: 16px;
color: #666;
margin-bottom: 12px;
font-weight: 500;
}
.loading-progress {
width: 200px;
height: 4px;
background: #e0e0e0;
border-radius: 2px;
overflow: hidden;
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #5B8AFE, #7BA6FF);
width: 30%;
animation: progress 2s ease-in-out infinite;
}
}
}
// 加载动画
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes progress {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(200%);
}
100% {
transform: translateX(200%);
}
}
\ No newline at end of file
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';
export default defineConfig({
base: '/ai/', // 添加基础路径前缀
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/pedapi': {
target: 'http://peddev.cmic.com.cn',
changeOrigin: true, // 解决跨域问题
secure: false, // 允许不安全的SSL连接
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('代理错误:', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('发送请求到:', options.target);
});
}
},
'/WeChatOauth2': {
target: 'http://peddev.cmic.com.cn',
changeOrigin: true, // 解决跨域问题
secure: false, // 允许不安全的SSL连接
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('代理错误:', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('发送请求到:', options.target);
});
}
}
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'echarts': 'echarts/dist/echarts.esm.js' // 添加这一行
}
},
})
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment