chore: 初始开源发布

iotlcm-admin-ui:管理前端(基于 Vben Admin 5.5.9 二次开发)。

- 天地图 API Key → YOUR_TIANDITU_KEY
- OAuth 客户端 ID → your-client-id,store 加密密钥 → 提示自行替换
- Makefile 移除明文 registry 登录口令,镜像地址改
- appIcon 指向内网图床的链接 → /favicon.ico
- 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位)
- 真实邮箱 → example.com(署名头内的上游作者邮箱保留)
- 移除 CI 触发器
- 补 MIT LICENSE(含 Vben Admin 上游声明)+ README
- 统一 LF 行尾并加 .gitattributes

上游 Vben Admin 的版权与许可声明完整保留(各源文件 @Author/@Copyright 头 + LICENSE)。
This commit is contained in:
MiFaZhan
2026-10-26 13:46:34 +08:00
commit 3106d96ac7
1607 changed files with 185856 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
.git
.gitignore
README.md
.env.local
*.zip
+14
View File
@@ -0,0 +1,14 @@
# 统一使用 LF,避免跨平台协作时产生整文件 diff
* text=auto eol=lf
# 二进制文件不做行尾转换
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.ttf binary
*.pdf binary
*.exe binary
*.xlsx binary
+66
View File
@@ -0,0 +1,66 @@
node_modules
.DS_Store
dist
dist-ssr
dist.zip
dist.tar
dist.war
.nitro
.output
*-dist.zip
*-dist.tar
*-dist.war
coverage
*.local
**/.vitepress/cache
.cache
.turbo
.temp
dev-dist
.stylelintcache
yarn.lock
package-lock.json
.VSCodeCounter
**/backend-mock/data
.omx
.pnpm-store
# local env files
.env.local
.env.*.local
.eslintcache
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
vite.config.mts.*
vite.config.mjs.*
vite.config.js.*
vite.config.ts.*
# Editor directories and files
.idea
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.history
.cursor
# AI
.agent
.agents
.claude
.codex
skills-lock.json
.atomcode
datalog
# local-only placeholder lefthook config (silences "No config found" popup)
# NOTE: only ignores the GIT-ROOT placeholder; vue/lefthook.yml stays tracked.
/lefthook.yml
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2021-PRESENT Vben Admin (https://github.com/vbenjs/vue-vben-admin)
Copyright (c) 2026 The iotLCM Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
.PHONY: build
build:
docker build --rm -f vue/build/Dockerfile --progress=plain --platform linux/amd64 -t docker_iotlcmadminui:v0 .
.PHONY: vue
vue:
cd vue
cd vue && npm i -g corepack
cd vue && CI=true pnpm install
cd vue && CI=true pnpm build
.PHONY: build-no-build
build-no-build:
docker build --rm -f vue/build/DockerfileNoBuild --progress=plain --platform linux/amd64 -t docker_iotlcmadminui:v0 .
dockerbuild: build-no-build
echo "docker build success"
dockerpush:
docker tag docker_iotlcmadminui:v0 ${REGISTRY}/docker_iotlcmadminui:v0
docker push ${REGISTRY}/$1:$2
+90
View File
@@ -0,0 +1,90 @@
# iotlcm-admin-ui
iotLCM 物联网卡全生命周期管理平台的**管理前端** —— 卡资产、通信管理、资费交易、告警工单、统计分析、账号治理。
配套后端:[iotlcm-admin-svr](https://git.5iot.com/iotlcm-public/iotlcm-admin-svr)。
## 上游与许可
本项目基于 **[Vben Admin](https://github.com/vbenjs/vue-vben-admin) 5.5.9**(MIT License)二次开发。
- 上游版权与许可声明完整保留在 [`LICENSE`](./LICENSE) 与各源文件的 `@Author` / `@Copyright` 头中
- iotLCM 相关的业务页面、API 封装与配置为本项目新增
- 上游仓库:[vbenjs/vue-vben-admin](https://github.com/vbenjs/vue-vben-admin)
- 上游自带的说明文档保留在 [`vue/README.md`](./vue/README.md)
## 技术栈
- Vue 3 + Vite + TypeScript
- Vben Admin 5.5.9(Ant Design Vue 版:`apps/web-antd`)
- Pinia(状态)、Vue Router、TailwindCSS + UnoCSS(原子化样式)
- pnpm workspace + Turborepo(monorepo)
## 快速开始
```bash
cd vue
pnpm install
pnpm dev:antd
```
开发服务器默认监听 `http://127.0.0.1:5666/`。
构建:
```bash
pnpm build:antd
```
产物在 `vue/apps/web-antd/dist/`。
## 配置
环境变量文件在 `vue/apps/web-antd/`:
| 文件 | 用途 |
| --- | --- |
| `.env` | 通用配置(应用标题、命名空间、客户端 ID、store 加密密钥) |
| `.env.development` | 开发环境(端口、API 地址) |
| `.env.production` | 生产环境(压缩策略、路由模式、PWA) |
**公开版里的凭据是占位符**:
| 变量 | 占位 | 说明 |
| --- | --- | --- |
| `VITE_GLOB_CLIENT_ID` | `your-client-id` | OAuth 客户端 ID |
| `VITE_APP_STORE_SECURE_KEY` | `please-replace-me-with-your-own-key` | localStorage 加密密钥 |
| `TIANDITU_CONFIG.API_KEY` | `YOUR_TIANDITU_KEY` | 天地图 API Key(`src/config/tianditu.ts`) |
> ⚠️ `VITE_APP_STORE_SECURE_KEY` 必须是自己的随机值——它用于加密持久化的 store,换设备时 store 数据将无法解密。
## 目录结构
```text
iotlcm-admin-ui/
├── Makefile # 容器构建与推送
└── vue/ # Vben Admin monorepo
├── apps/web-antd/ # iotLCM 管理端应用(主应用)
│ ├── src/api/iotlcm/ # iotLCM 业务 API 封装
│ ├── src/views/iotlcm/ # iotLCM 业务页面
│ ├── src/config/ # 应用配置(含天地图配置)
│ └── .env* # 环境变量
├── packages/ # 内部共享包
└── internal/ # 构建期工具
```
## 容器化构建
```bash
docker build --rm -f vue/build/Dockerfile --platform linux/amd64 -t iotlcm-admin-ui:latest .
```
推送到自己的镜像仓库:
```bash
REGISTRY=<your-registry> make dockerpush
```
## License
[MIT](./LICENSE) —— 含 Vben Admin 上游的 MIT 声明。
+4
View File
@@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11
+1
View File
@@ -0,0 +1 @@
export { default } from '@vben/commitlint-config';
+7
View File
@@ -0,0 +1,7 @@
node_modules
.git
.gitignore
*.md
dist
.turbo
dist.zip
+18
View File
@@ -0,0 +1,18 @@
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
max_line_length = 100
trim_trailing_whitespace = true
quote_type = single
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+6
View File
@@ -0,0 +1,6 @@
echo Start running commit-msg hook...
# Check whether the git commit information is standardized
pnpm exec commitlint --edit "$1"
echo Run commit-msg hook done.
+3
View File
@@ -0,0 +1,3 @@
# 每次 git pull 之后, 安装依赖
pnpm install
+7
View File
@@ -0,0 +1,7 @@
# update `.vscode/vben-admin.code-workspace` file
pnpm vsh code-workspace --auto-commit
# Format and submit code according to lintstagedrc.js configuration
pnpm exec lint-staged
echo Run pre-commit hook done.
+20
View File
@@ -0,0 +1,20 @@
export default {
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
],
'*.{scss,less,styl,html,vue,css}': [
'prettier --cache --ignore-unknown --write',
'stylelint --fix --allow-empty-input',
],
'package.json': ['prettier --cache --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
],
};
+1
View File
@@ -0,0 +1 @@
22.1.0
+13
View File
@@ -0,0 +1,13 @@
registry=https://registry.npmmirror.com
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*
public-hoist-pattern[]=czg
strict-peer-dependencies=false
auto-install-peers=true
dedupe-peer-dependents=true
+18
View File
@@ -0,0 +1,18 @@
dist
dev-dist
.local
.output.js
node_modules
.nvmrc
coverage
CODEOWNERS
.nitro
.output
**/*.svg
**/*.sh
public
.npmrc
*-lock.yaml
+1
View File
@@ -0,0 +1 @@
export { default } from '@vben/prettier-config';
+4
View File
@@ -0,0 +1,4 @@
dist
public
__tests__
coverage
+9
View File
@@ -0,0 +1,9 @@
{
"cSpell.words": [
"iccid",
"imei",
"imsi",
"IOTLCM",
"msisdn"
]
}
+152
View File
@@ -0,0 +1,152 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1>
</div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
## Introduction
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
## Upgrade Notice
This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2).
## Feature
- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite
- **TypeScript**: A language for application-scale JavaScript
- **Themes**: Multiple theme colors available with customizable options
- **Internationalization**: Comprehensive built-in internationalization support
- **Permissions**: Built-in solution for dynamic route-based permission generation
## Preview
- [Vben Admin](https://vben.pro/) - Full version Chinese site
Test Account: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### Use Gitpod
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
## Documentation
[Document](https://doc.vben.pro/)
## Install and use
- Get the project code
```bash
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- Installation dependencies
```bash
cd vue-vben-admin
corepack enable
pnpm install
```
- run
```bash
pnpm dev
```
- build
```bash
pnpm build
```
## Change Log
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## How to contribute
You are very welcome to join![Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) Or submit a Pull Request。
**Pull Request:**
1. Fork code!
2. Create your own branch: `git checkout -b feat/xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat/xxxx`
5. submit`pull request`
## Git Contribution submission specification
- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` Add new features
- `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement
- `refactor` Refactor
- `revert` Undo edit
- `test` Test related
- `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc.
- `ci` Continuous integration
- `types` Type definition file changes
- `wip` In development
## Browser support
The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Maintainer
[@Vben](https://github.com/anncwb)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a>
## Discord
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
## License
[MIT © Vben-2020](./LICENSE)
+152
View File
@@ -0,0 +1,152 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1>
</div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)
## 简介
Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。
## 升级提示
该版本为最新版本`5.0`, 与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
## 特性
- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:提供多套主题色彩,可配置自定义主题
- **国际化**:内置完善的国际化方案
- **权限** 内置完善的动态路由权限生成方案
## 预览
- [Vben Admin](https://vben.pro/) - 完整版中文站点
测试账号: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### 使用 Gitpod
在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
## 文档
[文档地址](https://doc.vben.pro/)
## 安装使用
- 获取项目代码
```bash
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- 安装依赖
```bash
cd vue-vben-admin
corepack enable
pnpm install
```
- 运行
```bash
pnpm dev
```
- 打包
```bash
pnpm build
```
## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feature/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feature/xxxx`
5. 提交`pull request`
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 维护者
[@Vben](https://github.com/anncwb)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a>
## Discord
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
## License
[MIT © Vben-2020](./LICENSE)
+269
View File
@@ -0,0 +1,269 @@
import { h, ref } from 'vue';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getPopupContainer } from '@vben/utils';
import type { DescItem } from '#/components/description';
import { cloneDeep } from 'lodash-es';
import type { VbenFormSchema } from '@vben/common-ui';
import { z } from '@vben/common-ui';
import { renderDict, renderPopoverMemberSumma, type MemberSumma } from '#/utils';
import { getDictOptions } from '#/utils/dict';
import FormOptions from '#/constants/FormOption';
export class State {
public id = 0; // 主键ID
public apiName = ''; // 接口名称
public apiCode = ''; // 接口权限标识
public method = ''; // 请求方法:GET/POST/PUT/DELETE
public path = ''; // 接口路径
public status = 0; // 状态:0启用,1停用
public version = ''; // 接口版本
public remark = ''; // 备注
public tenantId = ''; // 租户ID
public createdDept = 0; // 创建部门
public createdBy = 0; // 创建者
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
public createdAt = ''; // 创建时间
public updatedBy = 0; // 更新者
public updatedBySumma?: null | MemberSumma = null; // 更新者摘要信息
public updatedAt = ''; // 更新时间
constructor(state?: Partial<State>) {
if (state) {
Object.assign(this, state);
}
}
}
export function newState(state: State | Record<string, any> | null): State {
if (state !== null) {
if (state instanceof State) {
return cloneDeep(state);
}
return new State(state);
}
return new State();
}
// 表单验证规则
// 表格搜索表单
export const querySchema: VbenFormSchema[] = [
{
fieldName: 'id',
component: 'InputNumber',
label: '主键ID',
componentProps: {
placeholder: '请输入主键ID',
onUpdateValue: (e: any) => {
console.log(e);
},
}, rules:null,
formItemClass: 'col-span-1',
}, {
fieldName: 'status',
component: 'Select',
label: '状态:0启用,1停用',
defaultValue: null,
componentProps: {
placeholder: '请选择状态:0启用,1停用',
options: getDictOptions('sys_common_status'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'selectRequired',
formItemClass: 'col-span-1',
},
{
fieldName: 'createdAt',
component: 'RangePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
onUpdateValue: (e: any) => {
console.log(e);
},
}, rules:null,
formItemClass: 'col-span-1',
},];
// 表格列
export const columns: VxeGridProps['columns'] = [
{
title: '接口名称',
field: 'apiName',
align: 'left',
width: -1,
},
{
title: '接口权限标识',
field: 'apiCode',
align: 'left',
width: -1,
},
{
title: '请求方法',
field: 'method',
align: 'left',
width: -1,
slots: {
default: ({ row }) => {
return renderDict(row.method, DictEnum.T_METHOD);
}
},
},
{
title: '接口路径',
field: 'path',
align: 'left',
width: -1,
},
{
title: '状态', field: 'status', align: 'left', width: -1,
slots: {
default: ({ row }) => {
return renderDict(row.status, DictEnum.GLOBAL_ENABLED);
}
},
},
{
title: '接口版本',
field: 'version',
align: 'left',
width: -1,
},
{ title: '操作', width: 120, slots: { default: 'action' } },
];
// 表格列接口
export interface RowType {
id: number;
apiName: string;
apiCode: string;
method: string;
path: string;
status: number;
version: string;
remark: string;
tenantId: string;
createdDept: number;
createdBy: number;
createdAt: string;
updatedBy: number;
updatedAt: string;
};
// 查看字段列表
export const viewSchema: DescItem[] = [
{ field: 'apiName', label: '接口名称'},
{ field: 'apiCode', label: '接口权限标识'},
{ field: 'method', label: '请求方法:GET/POST/PUT/DELETE'},
{ field: 'path', label: '接口路径'},
{
field: 'status',
label: '状态',
render: (val) => renderDict(val, 'sys_common_status'),
},
{ field: 'version', label: '接口版本'},
{ field: 'remark', label: '备注'},
];
// 编辑字段列表
export const editSchema: VbenFormSchema[] = [
{
fieldName: 'id',
component: 'Input',
label: '主键ID',
dependencies: { show: () => false, triggerFields: [''], },
componentProps: {
placeholder: '',
onUpdateValue: (e: any) => {
console.log(e);
},
},
formItemClass: 'col-span-1',
}, {
fieldName: 'apiName',
component: 'Input',
label: '接口名称',
componentProps: {
placeholder: '请输入接口名称',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'required',
formItemClass: 'col-span-1',
}, {
fieldName: 'apiCode',
component: 'Input',
label: '接口权限标识',
componentProps: {
placeholder: '请输入接口权限标识',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'required',
formItemClass: 'col-span-1',
}, {
fieldName: 'method',
component: 'Select',
label: '请求方法',
componentProps: {
placeholder: '请选择请求方法',
options: getDictOptions(DictEnum.T_METHOD ),
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'required',
formItemClass: 'col-span-1',
}, {
fieldName: 'path',
component: 'Input',
label: '接口路径',
componentProps: {
placeholder: '请输入接口路径',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'required',
formItemClass: 'col-span-1',
},
{
fieldName: 'status',
component: 'Switch',
label: '状态',
defaultValue: 0,
componentProps: FormOptions.Component.SwitchEnabled(),
},
{
fieldName: 'version',
component: 'Input',
label: '接口版本',
componentProps: {
placeholder: '请输入接口版本',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:'required',
formItemClass: 'col-span-1',
}, {
fieldName: 'remark',
component: 'Input',
label: '备注',
componentProps: {
placeholder: '请输入备注',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules:null,
formItemClass: 'col-span-1',
}, ];
+1
View File
@@ -0,0 +1 @@
backend-mock/nitro.config.ts
+9
View File
@@ -0,0 +1,9 @@
# 应用标题
VITE_APP_TITLE=物联网全生命周期管理平台
VBEN_LOGO_URL=/images/logo.png
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=iotlcm-admin-web
# 客户端ID
VITE_GLOB_CLIENT_ID=your-client-id
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
+7
View File
@@ -0,0 +1,7 @@
# public path
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL=/api
VITE_VISUALIZER=true
+19
View File
@@ -0,0 +1,19 @@
# 端口号
VITE_PORT=5666
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
VITE_NITRO_MOCK=false
# 是否打开 devtools,true 为打开,false 为关闭
VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 客户端ID
VITE_GLOB_CLIENT_ID=your-client-id
+22
View File
@@ -0,0 +1,22 @@
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=none
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=hash
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
# 客户端ID
VITE_GLOB_CLIENT_ID=your-client-id
+48
View File
@@ -0,0 +1,48 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="description" content="A Modern Back-end Management System" />
<meta name="keywords" content="Vben Admin Vue3 Vite" />
<meta name="author" content="Vben" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title></title>
<link rel="icon" href="" />
<script>
(function () {
try {
var cacheText = window.localStorage.getItem('runtime-setting');
var cache = cacheText ? JSON.parse(cacheText) : null;
var setting = cache && cache.setting;
if (!setting) {
return;
}
window.__APP_SETTING__ = setting;
if (setting.appTitle) {
document.title = setting.appTitle;
}
if (setting.appIcon) {
var icon = document.querySelector('link[rel="icon"]');
if (icon) {
icon.href = setting.appIcon;
}
}
} catch (error) {}
})();
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+290
View File
@@ -0,0 +1,290 @@
<style>
html {
/* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */
line-height: 1.15;
}
.loading {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #f4f7f9;
transition: background-color 0.4s ease;
/* transition: all 0.8s ease-out; */
}
.loading.hidden {
pointer-events: none;
visibility: hidden;
opacity: 0;
transition: all 0.8s ease-out;
}
.dark .loading {
background: #0d0d10;
}
.title {
margin-top: 66px;
font-size: 28px;
font-weight: 600;
color: rgb(0 0 0 / 85%);
}
.dark .title {
color: #fff;
}
.loader {
position: relative;
width: 48px;
height: 48px;
margin-bottom: 32px;
}
.loader::before {
position: absolute;
top: 60px;
left: 0;
width: 48px;
height: 5px;
content: '';
background: hsl(var(--primary, 210 100% 50%) / 50%);
border-radius: 50%;
animation: shadow-ani 0.5s linear infinite;
}
.loader::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: '';
background: hsl(var(--primary, 210 100% 50%));
border-radius: 4px;
animation: jump-ani 0.5s linear infinite;
}
.title-wrap {
position: relative;
min-width: 420px;
min-height: 88px;
text-align: center;
}
.title,
.status-text,
.status-subtitle {
position: absolute;
left: 50%;
width: 100%;
transform: translateX(-50%);
transition:
opacity 0.45s ease,
transform 0.45s ease;
}
.title {
top: 0;
margin-top: 0;
font-size: 28px;
font-weight: 600;
line-height: 1.35;
color: rgb(0 0 0 / 85%);
opacity: 0;
transform: translateX(-50%) translateY(10px);
}
.status-text {
top: 0;
font-size: 28px;
font-weight: 600;
line-height: 1.35;
color: rgb(0 0 0 / 85%);
opacity: 1;
}
.status-subtitle {
top: 48px;
font-size: 13px;
letter-spacing: 0.08em;
color: rgb(100 116 139 / 88%);
}
.title-wrap.is-switching .status-text,
.title-wrap.is-switching .status-subtitle {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
.loading.app-title-ready .status-text,
.loading.app-title-ready .status-subtitle {
opacity: 0;
transform: translateX(-50%) translateY(-10px);
}
.loading.app-title-ready .title {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.dark .status-text {
color: #fff;
}
.dark .status-subtitle {
color: rgb(148 163 184 / 78%);
}
@keyframes jump-ani {
15% {
border-bottom-right-radius: 3px;
}
25% {
transform: translateY(9px) rotate(22.5deg);
}
50% {
border-bottom-right-radius: 40px;
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
}
75% {
transform: translateY(9px) rotate(67.5deg);
}
100% {
transform: translateY(0) rotate(90deg);
}
}
@keyframes shadow-ani {
0%,
100% {
transform: scale(1, 1);
}
50% {
transform: scale(1.2, 1);
}
}
</style>
<div class="loading" id="__app-loading__">
<div class="loader" aria-hidden="true"></div>
<div class="title-wrap">
<div class="status-text" id="__app-loading-status__">正在初始化应用...</div>
<div class="status-subtitle" id="__app-loading-subtitle__">
请稍候,系统正在准备运行环境
</div>
<div class="title" id="__app-loading-title__">智联·云</div>
</div>
</div>
<script data-app-loading="runtime-title">
(function () {
var loadingElement = document.getElementById('__app-loading__');
var statusElement = document.getElementById('__app-loading-status__');
var subtitleElement = document.getElementById('__app-loading-subtitle__');
var titleElement = document.getElementById('__app-loading-title__');
var titleWrapElement = titleElement && titleElement.parentElement;
var messageTimer = null;
var messageIndex = 0;
var messages = [
{
status: '正在初始化应用...',
subtitle: '请稍候,系统正在准备运行环境',
},
{
status: '正在连接配置中心...',
subtitle: '正在读取启动所需的运行时配置',
},
{
status: '正在装载页面资源...',
subtitle: '首次加载可能略慢,请稍候片刻',
},
{
status: '正在校准界面状态...',
subtitle: '即将进入系统首页',
},
];
if (
!loadingElement ||
!statusElement ||
!subtitleElement ||
!titleElement ||
!titleWrapElement
) {
return;
}
function applyMessage(message) {
statusElement.textContent = message.status;
subtitleElement.textContent = message.subtitle;
}
function switchMessage(message) {
titleWrapElement.classList.add('is-switching');
window.setTimeout(function () {
applyMessage(message);
titleWrapElement.classList.remove('is-switching');
}, 220);
}
function startMessageLoop() {
statusElement.style.opacity = '';
subtitleElement.style.opacity = '';
applyMessage(messages[0]);
messageTimer = window.setInterval(function () {
messageIndex = (messageIndex + 1) % messages.length;
switchMessage(messages[messageIndex]);
}, 1800);
}
function stopMessageLoop() {
if (messageTimer !== null) {
window.clearInterval(messageTimer);
messageTimer = null;
}
}
function applyTitle(setting) {
var appTitle = setting && setting.appTitle;
if (appTitle) {
stopMessageLoop();
titleElement.textContent = appTitle;
loadingElement.classList.add('app-title-ready');
}
}
// 配置已就绪,直接展示标题
if (window.__APP_SETTING__) {
applyTitle(window.__APP_SETTING__);
return;
}
// 配置未就绪,直接展示默认加载文案
startMessageLoop();
// 等待配置返回后,若已启动文案轮播则停止,并展示标题
if (window.__APP_SETTING_PROMISE__) {
window.__APP_SETTING_PROMISE__
.then(function (setting) {
applyTitle(setting);
})
.catch(function () {});
}
})();
</script>
+60
View File
@@ -0,0 +1,60 @@
{
"name": "@vben/web-antd",
"version": "5.5.9",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "apps/web-antd"
},
"license": "MIT",
"author": {
"name": "vben",
"email": "annvben@example.com",
"url": "https://github.com/anncwb"
},
"type": "module",
"scripts": {
"build": "pnpm vite build --mode production",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
"imports": {
"#/*": "./src/*"
},
"dependencies": {
"@highlightjs/vue-plugin": "^2.1.0",
"@tinymce/tinymce-vue": "^6.1.0",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/constants": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/request": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"cropperjs": "^1.6.2",
"dayjs": "catalog:",
"highlight.js": "^11.11.1",
"js-md5": "^0.8.3",
"lodash-es": "^4.17.21",
"pinia": "catalog:",
"tinymce": "^7.3.0",
"vue": "catalog:",
"vue-router": "catalog:"
},
"devDependencies": {
"@types/lodash-es": "^4.17.12"
}
}
@@ -0,0 +1,427 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { PluginOption } from 'vite';
import { Buffer } from 'node:buffer';
import { constants as fsConstants } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
interface ApiTypeGeneratorOptions {
apiRoot: string;
}
interface DirNode {
children?: DirNode[];
key: string;
title: string;
value: string;
}
interface WriteTypePayload {
autoImport?: boolean;
content?: string;
dir?: string;
fileName?: string;
overwrite?: boolean;
}
interface CheckTypeFilePayload {
items?: CheckTypeFileItem[];
}
interface CheckTypeFileItem {
dir?: string;
fileName?: string;
key?: string;
}
const API_TYPE_GENERATOR_PREFIX = '/__api-type-generator__';
export function apiTypeGeneratorPlugin(
options: ApiTypeGeneratorOptions,
): PluginOption {
const apiRoot = path.resolve(options.apiRoot);
return {
apply: 'serve',
configureServer(server) {
server.middlewares.use(async (request, response, next) => {
const url = request.url?.split('?')[0] ?? '';
if (!url.startsWith(API_TYPE_GENERATOR_PREFIX)) {
next();
return;
}
try {
if (
request.method === 'GET' &&
url === `${API_TYPE_GENERATOR_PREFIX}/dirs`
) {
const data = await scanApiDirs(apiRoot);
sendJson(response, 200, { data, success: true });
return;
}
if (
request.method === 'POST' &&
url === `${API_TYPE_GENERATOR_PREFIX}/exists`
) {
const payload = await readJsonBody<CheckTypeFilePayload>(request);
const data = await checkTypeFiles(apiRoot, payload);
sendJson(response, 200, { data, success: true });
return;
}
if (
request.method === 'POST' &&
url === `${API_TYPE_GENERATOR_PREFIX}/write`
) {
const payload = await readJsonBody<WriteTypePayload>(request);
const data = await writeTypeFile(apiRoot, payload);
sendJson(response, 200, { data, success: true });
return;
}
sendJson(response, 404, {
message: '接口不存在',
success: false,
});
} catch (error) {
const statusCode =
error instanceof ApiTypeGeneratorError ? error.status : 500;
sendJson(response, statusCode, {
code:
error instanceof ApiTypeGeneratorError ? error.code : 'UNKNOWN',
message: error instanceof Error ? error.message : '生成失败',
success: false,
});
}
});
},
name: 'web-antd:api-type-generator',
};
}
class ApiTypeGeneratorError extends Error {
constructor(
message: string,
public status = 400,
public code = 'BAD_REQUEST',
) {
super(message);
}
}
async function accessFile(filePath: string) {
try {
await fs.access(filePath, fsConstants.F_OK);
return true;
} catch {
return false;
}
}
function ensureInApiRoot(apiRoot: string, targetPath: string) {
const relativePath = path.relative(apiRoot, targetPath);
if (
relativePath.startsWith('..') ||
path.isAbsolute(relativePath) ||
relativePath === ''
) {
if (relativePath === '') {
return;
}
throw new ApiTypeGeneratorError('目标路径不在 src/api 目录下');
}
}
function extractTypeNames(content: string) {
const names = new Set<string>();
const regex = /export\s+(?:interface|type)\s+([A-Za-z_$][\w$]*)/g;
const results = content.matchAll(regex);
for (const result of results) {
const typeName = result[1];
if (!/(?:Param|Res)$/u.test(typeName)) {
continue;
}
names.add(typeName);
}
return [...names];
}
function formatTypeImport(typeNames: string[], importPath: string) {
const sortedNames = [...new Set(typeNames)].toSorted();
if (sortedNames.length === 0) {
return '';
}
if (sortedNames.length === 1) {
return `import type { ${sortedNames[0]} } from '${importPath}';`;
}
return `import type {\n ${sortedNames.join(',\n ')},\n} from '${importPath}';`;
}
function getImportPath(fileName: string) {
return `./${fileName.replace(/\.ts$/u, '')}`;
}
function getSafeFileName(fileName?: string) {
const safeFileName = fileName?.trim() || 'type.ts';
if (
!/^[\w.-]+\.ts$/u.test(safeFileName) ||
safeFileName.includes('..') ||
safeFileName.includes('/') ||
safeFileName.includes('\\')
) {
throw new ApiTypeGeneratorError('文件名必须是单个 .ts 文件名');
}
return safeFileName;
}
function getSafeRelativeDir(dir?: string) {
const safeDir = (dir ?? '').trim().replaceAll('\\', '/');
if (!safeDir || safeDir === '.') {
return '';
}
if (path.isAbsolute(safeDir) || safeDir.split('/').includes('..')) {
throw new ApiTypeGeneratorError('目录路径不合法');
}
return safeDir.split('/').filter(Boolean).join('/');
}
function mergeTypeImport(
source: string,
importPath: string,
typeNames: string[],
) {
const importRegex = new RegExp(
String.raw`import\s+type\s+\{([\s\S]*?)\}\s+from\s+['"]${escapeRegExp(
importPath,
)}['"];?`,
'u',
);
const matchedImport = source.match(importRegex);
if (matchedImport) {
const existedNames = matchedImport[1]
.split(',')
.map((item) => item.trim())
.filter(Boolean);
const nextImport = formatTypeImport(
[...existedNames, ...typeNames],
importPath,
);
return source.replace(importRegex, nextImport);
}
const importStatement = formatTypeImport(typeNames, importPath);
if (!importStatement) {
return source;
}
return `${importStatement}\n\n${source}`;
}
function escapeRegExp(value: string) {
return value.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
}
async function readJsonBody<T>(request: IncomingMessage) {
const chunks: Buffer[] = [];
for await (const chunk of request) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
const content = Buffer.concat(chunks).toString('utf8');
if (!content) {
return {} as T;
}
try {
return JSON.parse(content) as T;
} catch {
throw new ApiTypeGeneratorError('请求体必须是合法 JSON');
}
}
async function scanApiDirs(apiRoot: string) {
async function scan(currentDir: string): Promise<DirNode[]> {
const dirents = await fs.readdir(currentDir, { withFileTypes: true });
const nodes = await Promise.all(
dirents
.filter((dirent) => dirent.isDirectory())
.toSorted((prev, next) => prev.name.localeCompare(next.name))
.map(async (dirent) => {
const fullPath = path.join(currentDir, dirent.name);
const relativePath = path
.relative(apiRoot, fullPath)
.replaceAll(path.sep, '/');
const children = await scan(fullPath);
return {
children: children.length > 0 ? children : undefined,
key: relativePath,
title: dirent.name,
value: relativePath,
};
}),
);
return nodes;
}
return [
{
children: await scan(apiRoot),
key: '.',
title: 'api',
value: '.',
},
];
}
async function checkTypeFiles(
apiRoot: string,
payload: CheckTypeFilePayload,
) {
const items = payload.items ?? [];
if (!Array.isArray(items)) {
throw new ApiTypeGeneratorError('检查参数必须是数组');
}
return Promise.all(
items.map(async (item, index) => {
const relativeDir = getSafeRelativeDir(item.dir);
const fileName = getSafeFileName(item.fileName);
const targetDir = path.resolve(apiRoot, relativeDir);
ensureInApiRoot(apiRoot, targetDir);
const targetPath = path.resolve(targetDir, fileName);
ensureInApiRoot(apiRoot, targetPath);
return {
dir: relativeDir || '.',
exists: await accessFile(targetPath),
fileName,
filePath: path.relative(apiRoot, targetPath).replaceAll(path.sep, '/'),
key: item.key ?? String(index),
};
}),
);
}
function sendJson(response: ServerResponse, statusCode: number, data: unknown) {
response.statusCode = statusCode;
response.setHeader('Content-Type', 'application/json;charset=utf-8');
response.end(JSON.stringify(data));
}
async function updateIndexTypeImport(
targetDir: string,
fileName: string,
content: string,
) {
const indexPath = path.join(targetDir, 'index.ts');
const hasIndexFile = await accessFile(indexPath);
if (!hasIndexFile) {
return {
importSkipped: true,
importUpdated: false,
};
}
const typeNames = extractTypeNames(content);
if (typeNames.length === 0) {
return {
importSkipped: true,
importUpdated: false,
};
}
const source = await fs.readFile(indexPath, 'utf8');
const nextSource = mergeTypeImport(
source,
getImportPath(fileName),
typeNames,
);
if (nextSource !== source) {
await fs.writeFile(indexPath, nextSource, 'utf8');
}
return {
importSkipped: false,
importUpdated: nextSource !== source,
};
}
async function writeTypeFile(apiRoot: string, payload: WriteTypePayload) {
const relativeDir = getSafeRelativeDir(payload.dir);
const fileName = getSafeFileName(payload.fileName);
const content = payload.content ?? '';
if (!content.trim()) {
throw new ApiTypeGeneratorError('类型定义内容不能为空');
}
const targetDir = path.resolve(apiRoot, relativeDir);
ensureInApiRoot(apiRoot, targetDir);
const targetPath = path.resolve(targetDir, fileName);
ensureInApiRoot(apiRoot, targetPath);
const exists = await accessFile(targetPath);
if (exists && !payload.overwrite) {
throw new ApiTypeGeneratorError('目标文件已存在', 409, 'FILE_EXISTS');
}
await fs.mkdir(targetDir, { recursive: true });
await fs.writeFile(
targetPath,
content.endsWith('\n') ? content : `${content}\n`,
'utf8',
);
const importResult = payload.autoImport
? await updateIndexTypeImport(targetDir, fileName, content)
: {
importSkipped: false,
importUpdated: false,
};
return {
exists,
filePath: path.relative(apiRoot, targetPath).replaceAll(path.sep, '/'),
...importResult,
};
}
+1
View File
@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config/postcss';
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,18 @@
{
"tDashboard": {
"businessAlert": true,
"businessVolume": true,
"dataSharing": true,
"feeSituation": true,
"lifeCycle": true,
"locationDistribution": true,
"myPanel": true,
"myPanelNew": true,
"myPanelSimActive": true,
"myPanelSimCount": true,
"myPanelUseData": true,
"myPanelUseMsg": true,
"quickAccessBorder": true,
"trafficPool": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

+14
View File
@@ -0,0 +1,14 @@
{
"appTitle": "物联网全生命周期管理平台",
"appIcon": "/logo.png",
"loginPageTitle": "物联网设备上云首选",
"loginPageDescription": "新一代物联网设备统一接入平台",
"loginSloganImage": "",
"copyright": {
"companyName": "示例智联",
"companySiteLink": "https://www.xiujiezhilian.cn",
"date": "2024-2025",
"icp": "鲁ICP备2024117944号-1",
"icpLink": "https://beian.miit.gov.cn/"
}
}
@@ -0,0 +1,102 @@
import { defineAsyncComponent } from 'vue';
/**
* Ant Design Vue 基础组件统一在这里异步引入。
*
* adapter 层只关心组件映射关系,避免把具体 UI 库的 import 细节散落到注册文件中。
*/
export const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
export const Button = defineAsyncComponent(
() => import('ant-design-vue/es/button'),
);
export const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
export const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
export const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
export const Divider = defineAsyncComponent(
() => import('ant-design-vue/es/divider'),
);
export const Image = defineAsyncComponent(
() => import('ant-design-vue/es/image'),
);
export const Input = defineAsyncComponent(
() => import('ant-design-vue/es/input'),
);
export const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
export const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
export const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
export const PreviewGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),
);
export const Radio = defineAsyncComponent(
() => import('ant-design-vue/es/radio'),
);
export const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
export const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
export const Rate = defineAsyncComponent(
() => import('ant-design-vue/es/rate'),
);
export const Select = defineAsyncComponent(
() => import('ant-design-vue/es/select'),
);
export const Space = defineAsyncComponent(
() => import('ant-design-vue/es/space'),
);
export const Switch = defineAsyncComponent(
() => import('ant-design-vue/es/switch'),
);
export const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
export const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
export const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
export const Cascader = defineAsyncComponent(
() => import('ant-design-vue/es/cascader'),
);
export const Upload = defineAsyncComponent(
() => import('ant-design-vue/es/upload'),
);
@@ -0,0 +1,20 @@
import { globalShareState } from '@vben/common-ui';
import { notification } from 'ant-design-vue';
/**
* 框架共享消息适配。
*
* common-ui 内部只声明消息场景,这里绑定到当前 UI 库的 notification 实现。
*/
export function defineComponentAdapterMessage() {
globalShareState.defineMessage({
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
@@ -0,0 +1,125 @@
import type { UploadFile, UploadProps } from 'ant-design-vue';
import type { Ref } from 'vue';
import { h, render, unref } from 'vue';
import { Image, PreviewGroup } from '../antd';
/**
* UploadFile 没有稳定的图片标识字段时,通过 url/name/type 组合判断是否走图片预览。
*/
function isImageFile(file: UploadFile): boolean {
const imageExtensions = new Set(['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp']);
if (file.url) {
const ext = file.url?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
if (!file.type) {
const ext = file.name?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
return file.type.startsWith('image/');
}
/**
* 本地待上传图片没有 url 时,需要转成 base64 才能进入 ImagePreviewGroup。
*/
function getBase64(file: File) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result));
reader.addEventListener('error', (error) => reject(error));
});
}
/**
* 基于 Ant Design Vue ImagePreviewGroup 创建一次性预览容器。
*
* 预览关闭后会卸载并移除临时 DOM,避免多次预览后残留节点。
*/
export const previewImage = async (
file: UploadFile,
visible: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>,
) => {
// 非图片沿用文件打开行为,不进入图片预览组
if (!isImageFile(file)) {
if (file.url) {
window.open(file.url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
} else {
console.warn('无法打开文件,没有可用的URL或预览地址');
}
return;
}
// Image 与 PreviewGroup 只有预览时才加载,保持基础表单渲染轻量
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
Image,
PreviewGroup,
]);
const imageFiles = (unref(fileList) || []).filter((element) =>
isImageFile(element),
);
// 同一上传列表中的图片都加入预览组,支持左右切换
for (const imgFile of imageFiles) {
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
}
}
const container: HTMLElement | null = document.createElement('div');
document.body.append(container);
// 避免关闭动画期间重复触发卸载
let isUnmounted = false;
const PreviewWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
return h(
PreviewGroupComponent,
{
class: 'hidden',
preview: {
visible: visible.value,
current: imageFiles.findIndex((f) => f.uid === file.uid),
onVisibleChange: (value: boolean) => {
visible.value = value;
if (!value) {
// 等待预览关闭动画结束后再卸载临时容器
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
}
},
},
},
() =>
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
src: imgFile.url || imgFile.preview,
}),
),
);
};
},
};
render(h(PreviewWrapper), container);
};
@@ -0,0 +1,46 @@
import type { Component } from 'vue';
import type { Recordable } from '@vben/types';
import { defineComponent, h, ref } from 'vue';
import { $t } from '@vben/locales';
/**
* 为输入类/选择类组件补齐统一占位文案,并保留原组件暴露的方法。
*/
export const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
const innerRef = ref();
// 透传内部组件实例方法,避免包装后影响外部通过 ref 调用 focus 等能力
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
@@ -0,0 +1,114 @@
import type {
UploadChangeParam,
UploadFile,
UploadProps,
} from 'ant-design-vue';
import { defineComponent, h, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isEmpty } from '@vben/utils';
import { Button, Upload } from '../antd';
import { previewImage } from './upload-preview';
/**
* Upload 未提供自定义插槽时,补齐项目统一的默认上传按钮。
*/
function createDefaultSlotsWithUpload(listType: string, placeholder: string) {
switch (listType) {
case 'picture-card': {
return {
default: () => placeholder,
};
}
default: {
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
},
() => placeholder,
),
};
}
}
}
/**
* 适配 Ant Design Vue Upload 到 Vben Form:
* - 使用 modelValue 与表单值同步
* - 上传列表为空时回写 undefined
* - 内置图片预览,非图片文件直接打开地址
*/
export const withPreviewUpload = () => {
return defineComponent({
name: Upload.name,
emits: ['change', 'update:modelValue'],
setup: (
props: any,
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
) => {
const previewVisible = ref<boolean>(false);
const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
const fileList = ref<UploadProps['fileList']>(
attrs?.fileList || attrs?.['file-list'] || [],
);
const handleChange = async (event: UploadChangeParam) => {
fileList.value = event.fileList;
emit('change', event);
emit(
'update:modelValue',
event.fileList?.length ? fileList.value : undefined,
);
};
const handlePreview = async (file: UploadFile) => {
previewVisible.value = true;
await previewImage(file, previewVisible, fileList);
};
const renderUploadButton = (): any => {
const isDisabled = attrs.disabled;
// 禁用状态下只展示文件列表,不再渲染上传入口
if (isDisabled) {
return null;
}
return isEmpty(slots)
? createDefaultSlotsWithUpload(listType, placeholder)
: slots;
};
// 监听 formApi.setValues 等外部回填,保持 Upload 内部 fileList 同步
watch(
() => attrs.modelValue,
(res) => {
fileList.value = res;
},
);
return () =>
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton(),
);
},
});
};
@@ -0,0 +1,136 @@
import type { Component } from 'vue';
import type { ComponentType } from './types';
import { h } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { HtcRegisterComponent } from '#/components/HtCustom/FormExpand/register';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
import {
AutoComplete,
Button,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
} from './antd';
import { defineComponentAdapterMessage } from './helpers/message';
import { withDefaultPlaceholder } from './helpers/with-default-placeholder';
import { withPreviewUpload } from './helpers/with-preview-upload';
/**
* 通用组件适配层。
*
* 这里只维护“组件名称 -> 真实组件”的映射关系,供 vben-form、vben-modal、
* vben-drawer 等框架组件通过 globalShareState 复用。
*/
function createComponentAdapterMap(): Partial<
Record<ComponentType, Component>
> {
return {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
component: Cascader,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
// 弹窗、抽屉等框架组件会通过共享状态读取这两个按钮组件
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
FileUpload,
ImageUpload,
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
RichTextarea,
Select: withDefaultPlaceholder(Select, 'select'),
// SelectDivision,
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload: withPreviewUpload(),
...HtcRegisterComponent,
};
}
async function initComponentAdapter() {
// 先注册组件,再初始化 form 适配,确保 setupVbenForm 能读到完整组件表
globalShareState.setComponents(createComponentAdapterMap());
defineComponentAdapterMessage();
}
export { initComponentAdapter };
export type { ComponentType };
@@ -0,0 +1,19 @@
import type { SchemaComponentType } from './types';
import type { HtcComponentPropsMap } from '#/components/HtCustom/FormExpand/register';
type ComponentPropsGetter<Props> = (
value: Partial<Record<string, any>>,
actions: any,
) => Props;
type ComponentPropsMap = HtcComponentPropsMap;
type UntypedComponentProps =
| ComponentPropsGetter<Record<string, any>>
| Record<string, any>;
export type ComponentProps<T extends SchemaComponentType> =
T extends keyof ComponentPropsMap
? ComponentPropsGetter<ComponentPropsMap[T]> | ComponentPropsMap[T]
: UntypedComponentProps;
@@ -0,0 +1,61 @@
import type { BaseFormComponentType } from '@vben/common-ui';
import type { HtcRegisterComponentName } from '#/components/HtCustom/FormExpand/register';
/**
* 表单 schema 中 component 字段允许使用的组件名称。
*
* 新增适配组件时,需要同时在这里补充名称,并在 index.ts 中注册真实组件。
*/
export type AdapterComponentType =
| 'ApiCascader'
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
| 'Cascader'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'FileUpload'
| 'HtcFormSwitch'
| 'IconPicker'
| 'ImageUpload'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichTextarea'
| 'Select'
| 'SelectDivision'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload';
export type BaseComponentType =
| 'DefaultButton'
| 'PrimaryButton'
| 'VbenCheckbox'
| 'VbenInput'
| 'VbenInputPassword'
| 'VbenPinInput'
| 'VbenSelect';
export type SchemaComponentType =
| AdapterComponentType
| BaseComponentType
| HtcRegisterComponentName;
export type ComponentType =
| AdapterComponentType
| BaseFormComponentType
| HtcRegisterComponentName;
+68
View File
@@ -0,0 +1,68 @@
import type {
VbenFormProps as BaseVbenFormProps,
VbenFormSchema as FormSchema,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import type { ComponentProps } from './component/props';
import type { SchemaComponentType } from './component/types';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
RichTextarea: 'modelValue',
Switch: 'checked',
Upload: 'fileList',
ImageUpload: 'value',
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
}
type TypedFormSchema<T extends SchemaComponentType> = Omit<
FormSchema<T>,
'component' | 'componentProps'
> & {
component: T;
componentProps?: ComponentProps<T>;
};
export type VbenFormSchema = {
[K in SchemaComponentType]: TypedFormSchema<K>;
}[SchemaComponentType];
export type VbenFormProps = Omit<BaseVbenFormProps<ComponentType>, 'schema'> & {
schema?: VbenFormSchema[];
};
const useVbenForm = (options: VbenFormProps) => {
return useForm<ComponentType>(options as BaseVbenFormProps<ComponentType>);
};
export { initSetupVbenForm, useVbenForm, z };
@@ -0,0 +1,124 @@
/* eslint-disable vue/no-side-effects-in-computed-properties */
import { computed, defineComponent, h, ref, watch } from 'vue';
import hljs from 'highlight.js/lib/core';
const component = defineComponent({
props: {
code: {
type: String,
required: true,
},
language: {
type: String,
default: '',
},
autodetect: {
type: Boolean,
default: true,
},
ignoreIllegals: {
type: Boolean,
default: true,
},
},
setup(props, { emit }) {
const language = ref(props.language);
const ignoreIllegals = ref(props.ignoreIllegals);
watch(
() => props.language,
(newLanguage) => {
language.value = newLanguage;
},
);
watch(
() => props.ignoreIllegals,
(newIgnoreIllegals) => {
ignoreIllegals.value = newIgnoreIllegals;
},
);
const autodetect = computed(() => props.autodetect || !language.value);
const cannotDetectLanguage = computed(
() => !autodetect.value && !hljs.getLanguage(language.value),
);
const className = computed((): string => {
return cannotDetectLanguage.value ? '' : `hljs ${language.value}`;
});
const highlightedCode = computed((): string => {
// No idea what language to use, return raw code
if (cannotDetectLanguage.value) {
console.warn(
`The language "${language.value}" you specified could not be found.`,
);
return props.code;
}
if (autodetect.value) {
const result = hljs.highlightAuto(props.code);
language.value = result.language ?? '';
return result.value;
} else {
const result = hljs.highlight(props.code, {
language: language.value,
ignoreIllegals: props.ignoreIllegals,
});
return result.value;
}
});
// const saveSelection = (e: any) => {
// const selection = window.getSelection();
// if (selection && selection.rangeCount && selection.rangeCount > 0) {
// const range = selection.getRangeAt(0);
// return range;
// }
// return null;
// };
// const restoreSelection = (range: any, el: any) => {
// if (range) {
// const selection = window.getSelection();
// if (selection) {
// selection.removeAllRanges();
// selection.addRange(range);
// console.log('selection range', range);
// }
// }
// };
const updateContent = (e: any) => {
// const range = saveSelection(e)
const result = hljs.highlight(e.target.innerText, {
language: language.value,
ignoreIllegals: props.ignoreIllegals,
});
e.target.innerHTML = result.value;
emit('update:code', e.target.innerText);
// setTimeout(() => {
// restoreSelection(range, e.target)
// }, 0)
};
return {
className,
highlightedCode,
updateContent,
emit,
};
},
render() {
const updateContent = this.updateContent;
return h('pre', {}, [
h('code', {
class: this.className,
innerHTML: this.highlightedCode,
contenteditable: true,
// onInput(e: any) {},
onBlur(e: any) {
updateContent(e);
},
}),
]);
},
});
export default component;
@@ -0,0 +1,70 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置,使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
return h(Image, { src: row[column.field], ...props });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
+170
View File
@@ -0,0 +1,170 @@
import { requestClient } from '#/api/request';
export interface AgentDept {
deptId: number;
tenantId?: string;
deptType: number;
parentId: number;
ancestors?: string;
deptKey?: string;
deptName: string;
deptCategory?: string;
orderNum?: number;
leader?: number;
phone?: string;
email?: string;
status: string;
creditCode?: string;
areaCode?: string;
address?: string;
deptRole?: number;
userName?: string;
defaultUserName?: string;
nickName?: string;
defaultNickName?: string;
defaultUserNickName?: string;
defaultUserId?: number;
password?: string;
createdAt?: string;
}
export interface AgentRole {
roleId: number;
tenantId?: string;
deptId: number;
deptName?: string;
roleName: string;
roleKey: string;
roleSort?: number;
dataScope?: string;
menuCheckStrictly?: number;
deptCheckStrictly?: number;
isPass?: number;
sourceRoleId?: number;
sourceDeptId?: number;
status: string;
createdAt?: string;
remark?: string;
isDefaultRole?: boolean;
}
export interface MenuTreeItem {
title: string;
key: string;
parentId: number;
menuId: number;
menuName: string;
menuType: string;
perms?: string;
children?: MenuTreeItem[];
}
export interface AgentRoleView extends AgentRole {
menuIds: number[];
deptIds: number[];
allowedMenuIds: number[];
menuTree: MenuTreeItem[];
}
export interface AgentUser {
userId: number;
tenantId: string;
deptId: number;
userName: string;
nickName: string;
userType: string;
email: string;
phonenumber: string;
sex: string;
avatar: string;
status: string;
loginIp?: string;
loginDate?: string;
createdDept?: number;
createdBy?: number;
createdAt?: string;
remark?: string;
deptInfo?: { deptId: number; deptName: string };
roles?: AgentRole[];
isDefaultUser?: boolean;
}
export function getAgentDeptListApi(params?: Record<string, any>) {
return requestClient.get<{ items: AgentDept[]; total: number }>(
'/agent/dept/list',
{ params },
);
}
export function getAgentDeptViewApi(params: { deptId: number }) {
return requestClient.get<AgentDept>('/agent/dept/view', { params });
}
export function saveAgentDeptApi(params: Partial<AgentDept>) {
return requestClient.post<{ deptId: number }>('/agent/dept/save', {
...params,
});
}
export function deleteAgentDeptApi(params: { deptId: number }) {
return requestClient.post('/agent/dept/delete', { ...params });
}
export function resetAgentDeptPasswordApi(params: { deptId: number; password: string }) {
return requestClient.post('/agent/dept/resetPassword', { ...params });
}
export function getAgentUserListApi(params?: Record<string, any>) {
return requestClient.get<{ items: AgentUser[]; total: number }>(
'/agent/user/list',
{ params },
);
}
export function getAgentUserViewApi(params: { userId: number }) {
return requestClient.get<AgentUser>('/agent/user/view', { params });
}
export function addAgentUserApi(params: Record<string, any>) {
return requestClient.post('/agent/user/add', { ...params });
}
export function updateAgentUserApi(params: Record<string, any>) {
return requestClient.post('/agent/user/update', { ...params });
}
export function deleteAgentUserApi(params: { userId?: number; userIds?: number[] }) {
return requestClient.post('/agent/user/delete', { ...params });
}
export function resetAgentUserPasswordApi(params: { userId: number; password: string }) {
return requestClient.post('/agent/user/resetPassword', { ...params });
}
export function getAgentRoleListApi(params?: Record<string, any>) {
return requestClient.get<{ items: AgentRole[]; total: number }>(
'/agent/role/list',
{ params },
);
}
export function getAgentRoleViewApi(params: { roleId: number }) {
return requestClient.get<AgentRoleView>('/agent/role/view', { params });
}
export function saveAgentRoleApi(params: Record<string, any>) {
return requestClient.post<{ roleId: number }>('/agent/role/save', {
...params,
});
}
export function deleteAgentRoleApi(params: { roleId?: number; roleIds?: number[] }) {
return requestClient.post('/agent/role/delete', { ...params });
}
export function getAgentMenuTreeApi() {
return requestClient.get<{ items: MenuTreeItem[]; menuIds: number[] }>(
'/agent/menu/tree',
);
}
@@ -0,0 +1,34 @@
import { requestClient } from '#/api/request';
// 获取开放接口权限组表列表
export function List(params:any) {
return requestClient.get<any>('agent/tApiGroup/list', { params });
}
// 删除/批量删除开放接口权限组表
export function Delete(params:any) {
return requestClient.post<any>('agent/tApiGroup/delete', { ...params });
}
// 添加/编辑开放接口权限组表
export function Edit(params:any) {
return requestClient.post<any>('agent/tApiGroup/edit', { ...params });
}
// 修改开放接口权限组表状态
export function Status(params:any) {
return requestClient.post<any>('agent/tApiGroup/status', { ...params });
}
// 获取开放接口权限组表指定详情
export function View(params:any) {
return requestClient.get<any>('agent/tApiGroup/view', { params });
}
// 导出开放接口权限组表
export function Export(params:any) {
return requestClient.post<Blob>('/agent/tApiGroup/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,34 @@
import { requestClient } from '#/api/request';
// 获取开放接口资源表列表
export function List(params:any) {
return requestClient.get<any>('agent/tApiResource/list', { params });
}
// 删除/批量删除开放接口资源表
export function Delete(params:any) {
return requestClient.post<any>('agent/tApiResource/delete', { ...params });
}
// 添加/编辑开放接口资源表
export function Edit(params:any) {
return requestClient.post<any>('agent/tApiResource/edit', { ...params });
}
// 修改开放接口资源表状态
export function Status(params:any) {
return requestClient.post<any>('agent/tApiResource/status', { ...params });
}
// 获取开放接口资源表指定详情
export function View(params:any) {
return requestClient.get<any>('agent/tApiResource/view', { params });
}
// 导出开放接口资源表
export function Export(params:any) {
return requestClient.post<Blob>('/agent/tApiResource/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,70 @@
import { requestClient } from '#/api/request';
// 获取企业客户财务信息列表
export function List(params:any) {
return requestClient.get<any>('common/tAgentBalance/list', { params });
}
// 获取账户余额概览
export function GetAccountBalance(params?: any) {
return requestClient.get<any>('common/tAgentBalance/getAccountBalance', { params });
}
// 获取费用总览折线图
export function FeeTrend(params: any) {
return requestClient.get<any>('common/tAgentBalance/feeTrend', { params });
}
// 获取账户总览折线图
export function AccountTrend(params: any) {
return requestClient.get<any>('common/tAgentBalance/accountTrend', { params });
}
// 删除/批量删除企业客户财务信息
export function Delete(params:any) {
return requestClient.post<any>('common/tAgentBalance/delete', { ...params });
}
// 添加/编辑企业客户财务信息
export function Edit(params:any) {
return requestClient.post<any>('common/tAgentBalance/edit', { ...params });
}
// 修改企业客户财务信息状态
export function Status(params:any) {
return requestClient.post<any>('common/tAgentBalance/status', { ...params });
}
// 获取企业客户财务信息指定详情
export function View(params:any) {
return requestClient.get<any>('common/tAgentBalance/view', { params });
}
// 导出企业客户财务信息
export function Export(params:any) {
return requestClient.post<Blob>('/common/tAgentBalance/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
export function AlipayPay(params: any) {
return requestClient.post<any>('pay/alipay/pay', params);
}
export function AlipayQuery(params: any) {
return requestClient.get<any>('pay/alipay/query', { params });
}
// 转可提现申请
export function BalanceTransferApply(params: {
amount: number;
accountType: number;
bankName?: string;
bankAccount?: string;
accountName?: string;
identityCard?: string;
remark?: string;
}) {
return requestClient.post<any>('common/tAgentBalance/balanceTransferApply', params);
}
@@ -0,0 +1,33 @@
import { requestClient } from '#/api/request';
// 获取余额明细表列表
export function List(params:any) {
return requestClient.get<any>('common/tBalanceDetail/list', { params });
}
// 删除/批量删除余额明细表
export function Delete(params:any) {
return requestClient.post<any>('common/tBalanceDetail/delete', { ...params });
}
// 添加/编辑余额明细表
export function Edit(params:any) {
return requestClient.post<any>('common/tBalanceDetail/edit', { ...params });
}
// 获取余额明细表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tBalanceDetail/view', { params });
}
// 导出余额明细表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tBalanceDetail/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
export function Recharge(params:any) {
return requestClient.post<any>('common/tBalanceDetail/recharge', { ...params });
}
@@ -0,0 +1,34 @@
import { requestClient } from '#/api/request';
// 获取消费账户月账单表列表
export function List(params:any) {
return requestClient.get<any>('common/tConsumeAccountMonthBill/list', { params });
}
// 获取消费账户月账单汇总
export function GetConsumptionBillSummary(params:any) {
return requestClient.get<any>('common/tConsumeAccountMonthBill/getConsumptionBillSummary', { params });
}
// 删除/批量删除消费账户月账单表
export function Delete(params:any) {
return requestClient.post<any>('common/tConsumeAccountMonthBill/delete', { ...params });
}
// 添加/编辑消费账户月账单表
export function Edit(params:any) {
return requestClient.post<any>('common/tConsumeAccountMonthBill/edit', { ...params });
}
// 获取消费账户月账单表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tConsumeAccountMonthBill/view', { params });
}
// 导出消费账户月账单表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tConsumeAccountMonthBill/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取信用额度明细表列表
export function List(params:any) {
return requestClient.get<any>('common/tCreditDetail/list', { params });
}
// 删除/批量删除信用额度明细表
export function Delete(params:any) {
return requestClient.post<any>('common/tCreditDetail/delete', { ...params });
}
// 添加/编辑信用额度明细表
export function Edit(params:any) {
return requestClient.post<any>('common/tCreditDetail/edit', { ...params });
}
// 获取信用额度明细表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tCreditDetail/view', { params });
}
// 导出信用额度明细表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tCreditDetail/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
// 获取分润明细表列表
export function List(params: any) {
return requestClient.get<any>('common/tFinCommissionDetail/list', { params });
}
// 删除/批量删除分润明细表
export function Delete(params: any) {
return requestClient.post<any>('common/tFinCommissionDetail/delete', { ...params });
}
// 添加/编辑分润明细表
export function Edit(params: any) {
return requestClient.post<any>('common/tFinCommissionDetail/edit', { ...params });
}
// 获取分润明细表指定详情
export function View(params: any) {
return requestClient.get<any>('common/tFinCommissionDetail/view', { params });
}
// 获取分层分润概览
export function GetCommissionOverview(params: any) {
return requestClient.get<any>(
'common/tFinCommissionDetail/getCommissionOverview',
{ params },
);
}
// 获取层级汇总表
export function GetLevelSummary(params: any) {
return requestClient.get<any>(
'common/tFinCommissionDetail/getLevelSummary',
{ params },
);
}
// 导出分润明细表
export function Export(params: any) {
return requestClient.post<Blob>(
'/common/tFinCommissionDetail/export',
{ ...params },
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
},
);
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取可提现变动明细表列表
export function List(params:any) {
return requestClient.get<any>('common/tFinWithdrawableChange/list', { params });
}
// 删除/批量删除可提现变动明细表
export function Delete(params:any) {
return requestClient.post<any>('common/tFinWithdrawableChange/delete', { ...params });
}
// 添加/编辑可提现变动明细表
export function Edit(params:any) {
return requestClient.post<any>('common/tFinWithdrawableChange/edit', { ...params });
}
// 获取可提现变动明细表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tFinWithdrawableChange/view', { params });
}
// 导出可提现变动明细表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tFinWithdrawableChange/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,38 @@
import { requestClient } from '#/api/request';
// 获取财务申请表列表
export function List(params:any) {
return requestClient.get<any>('common/tFinanceApply/list', { params });
}
// 删除/批量删除财务申请表
export function Delete(params:any) {
return requestClient.post<any>('common/tFinanceApply/delete', { ...params });
}
// 添加/编辑财务申请表
export function Edit(params:any) {
return requestClient.post<any>('common/tFinanceApply/edit', { ...params });
}
// 获取财务申请表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tFinanceApply/view', { params });
}
// 导出财务申请表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tFinanceApply/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
// 审核余额转可提现申请
export function BalanceTransferAudit(params: {
applyNo: string;
auditResult: number;
auditOpinion?: string;
}) {
return requestClient.post<any>('common/tAgentBalance/balanceTransferAudit', params);
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取财务审核记录表列表
export function List(params:any) {
return requestClient.get<any>('common/tFinanceAuditRecord/list', { params });
}
// 删除/批量删除财务审核记录表
export function Delete(params:any) {
return requestClient.post<any>('common/tFinanceAuditRecord/delete', { ...params });
}
// 添加/编辑财务审核记录表
export function Edit(params:any) {
return requestClient.post<any>('common/tFinanceAuditRecord/edit', { ...params });
}
// 获取财务审核记录表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tFinanceAuditRecord/view', { params });
}
// 导出财务审核记录表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tFinanceAuditRecord/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取消息日志列表
export function List(params:any) {
return requestClient.get<any>('common/tMessageLog/list', { params });
}
// 删除/批量删除消息日志
export function Delete(params:any) {
return requestClient.post<any>('common/tMessageLog/delete', { ...params });
}
// 添加/编辑消息日志
export function Edit(params:any) {
return requestClient.post<any>('common/tMessageLog/edit', { ...params });
}
// 获取消息日志指定详情
export function View(params:any) {
return requestClient.get<any>('common/tMessageLog/view', { params });
}
// 导出消息日志
export function Export(params:any) {
return requestClient.post<Blob>('/common/tMessageLog/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取通知日志列表
export function List(params:any) {
return requestClient.get<any>('common/tNotifyLog/list', { params });
}
// 删除/批量删除通知日志
export function Delete(params:any) {
return requestClient.post<any>('common/tNotifyLog/delete', { ...params });
}
// 添加/编辑通知日志
export function Edit(params:any) {
return requestClient.post<any>('common/tNotifyLog/edit', { ...params });
}
// 获取通知日志指定详情
export function View(params:any) {
return requestClient.get<any>('common/tNotifyLog/view', { params });
}
// 导出通知日志
export function Export(params:any) {
return requestClient.post<Blob>('/common/tNotifyLog/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,63 @@
import { requestClient } from '#/api/request';
// 获取盈利账户月账单表列表
export function List(params: any) {
return requestClient.get<any>('common/tProfitAccountMonthBill/list', { params });
}
// 获取盈利账户月账单汇总
export function GetProfitBillSummary(params: any) {
return requestClient.get<any>(
'common/tProfitAccountMonthBill/getProfitBillSummary',
{ params },
);
}
// 获取核心财务指标
export function GetCoreFinancialData(params: any) {
return requestClient.get<any>(
'common/tProfitAccountMonthBill/getCoreFinancialData',
{ params },
);
}
// 获取财务趋势
export function GetFinancialTrend(params: any) {
return requestClient.get<any>(
'common/tProfitAccountMonthBill/getFinancialTrend',
{ params },
);
}
// 删除/批量删除盈利账户月账单表
export function Delete(params: any) {
return requestClient.post<any>('common/tProfitAccountMonthBill/delete', {
...params,
});
}
// 添加/编辑盈利账户月账单表
export function Edit(params: any) {
return requestClient.post<any>('common/tProfitAccountMonthBill/edit', {
...params,
});
}
// 获取盈利账户月账单表指定详情
export function View(params: any) {
return requestClient.get<any>('common/tProfitAccountMonthBill/view', {
params,
});
}
// 导出盈利账户月账单表
export function Export(params: any) {
return requestClient.post<Blob>(
'/common/tProfitAccountMonthBill/export',
{ ...params },
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
},
);
}
@@ -0,0 +1,34 @@
import { requestClient } from '#/api/request';
// 获取工单问题沟通表列表
export function List(params:any) {
return requestClient.get<any>('/common/tWoCommunication/list', { params });
}
// 删除/批量删除工单问题沟通表
export function Delete(params:any) {
return requestClient.post<any>('common/tWoCommunication/delete', { ...params });
}
// 添加/编辑工单问题沟通表
export function Edit(params:any) {
return requestClient.post<any>('common/tWoCommunication/edit', { ...params });
}
// 获取工单问题沟通表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tWoCommunication/view', { params });
}
// 发送工单沟通消息
export function Message(params:any) {
return requestClient.post<any>('common/tWoCommunication/message', { ...params });
}
// 导出工单问题沟通表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tWoCommunication/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,54 @@
import { requestClient } from '#/api/request';
// 获取工单主表列表
export function List(params:any) {
return requestClient.get<any>('common/tWoMain/list', { params });
}
// 删除/批量删除工单主表
export function Delete(params:any) {
return requestClient.post<any>('common/tWoMain/delete', { ...params });
}
// 添加/编辑工单主表
export function Edit(params:any) {
return requestClient.post<any>('common/tWoMain/edit', { ...params });
}
// 获取工单主表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tWoMain/view', { params });
}
// 工单转单
export function Transfer(params:any) {
return requestClient.post<any>('common/tWoMain/transfer', { ...params });
}
// 工单完成
export function Resolve(params:any) {
return requestClient.post<any>('common/tWoMain/resolve', { ...params });
}
// 工单取消/关闭
export function Cancel(params:any) {
return requestClient.post<any>('common/tWoMain/cancel', { ...params });
}
// 关闭工单
export function Close(params:any) {
return requestClient.post<any>('common/tWoMain/close', { ...params });
}
// 工单评价
export function Evaluate(params:any) {
return requestClient.post<any>('common/tWoMain/evaluate', { ...params });
}
// 导出工单主表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tWoMain/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
// 获取工单记录表列表
export function List(params:any) {
return requestClient.get<any>('common/tWoRecord/list', { params });
}
// 删除/批量删除工单记录表
export function Delete(params:any) {
return requestClient.post<any>('common/tWoRecord/delete', { ...params });
}
// 添加/编辑工单记录表
export function Edit(params:any) {
return requestClient.post<any>('common/tWoRecord/edit', { ...params });
}
// 获取工单记录表指定详情
export function View(params:any) {
return requestClient.get<any>('common/tWoRecord/view', { params });
}
// 导出工单记录表
export function Export(params:any) {
return requestClient.post<Blob>('/common/tWoRecord/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
+185
View File
@@ -0,0 +1,185 @@
/**
* @description 认证相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import { baseRequestClient, requestClient } from '#/api/request';
/**
* 登录类型
* password 密码
* sms 短信
* social 第三方oauth
* email 邮箱
* xcx 小程序
*/
type GrantType = 'email' | 'password' | 'sms' | 'social' | 'xcx';
export namespace AuthApi {
/** 登录接口参数 */
export interface LoginParams {
password?: string;
username?: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
}
/** 注册接口参数 */
export interface RegisterParams {
tenantId?: string;
username?: string;
password?: string;
captchaID?: string;
captchaValue?: string;
}
export type RegisterResult = Record<string, never>;
export interface RefreshTokenResult {
data: string;
status: number;
}
export interface BaseLoginParams {
clientId?: string;
grantType: GrantType;
tenantId: string;
}
export interface OAuthLoginParams extends BaseLoginParams {
socialCode: string;
socialState: string;
source: string;
}
}
/**
* @param companyName 租户/公司名称
* @param domain 绑定域名(不带http(s)://) 可选
* @param tenantId 租户id
*/
export interface TenantOption {
companyName: string;
domain?: string;
tenantId: string;
}
/**
* @param tenantEnabled 是否启用租户
* @param voList 租户列表
*/
export interface TenantResp {
tenantEnabled: boolean;
voList: TenantOption[];
}
/**
* 获取用户权限码
*/
export interface GetAccessCodesResult {
data: string[];
}
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
}
/**
* 发送手机验证码登录短信
*/
export async function sendSmsLoginCodeApi(phone: string) {
return requestClient.post('/system/auth/sms-code', { phone });
}
/**
* 手机验证码登录
*/
export async function smsLoginApi(data: { code: string; phone: string }) {
return requestClient.post<AuthApi.LoginResult>(
'/system/auth/sms-login',
data,
);
}
/**
* 注册register
*/
export async function registerApi(data: AuthApi.RegisterParams) {
return requestClient.post<AuthApi.RegisterResult>(
'/system/user/register',
data,
);
}
/**
* 刷新accessToken
*/
export async function refreshTokenApi() {
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
'/system/auth/refresh',
undefined,
{
withCredentials: true,
},
);
}
/**
* 退出登录
*/
export async function logoutApi() {
return requestClient.post('/system/auth/logout', undefined, {
withCredentials: true,
});
}
/**
* 获取用户权限码
*/
export async function getAccessCodesApi() {
return requestClient.get<GetAccessCodesResult>('/system/auth/codes');
}
/**
* 绑定第三方账号
* @param source 绑定的来源
* @returns 跳转url
*/
export function authBinding(source: string, tenantId: string) {
return requestClient.get<string>(`/auth/binding/${source}`, {
params: {
domain: window.location.host,
tenantId,
},
});
}
/**
* 取消绑定
* @param id id
*/
export function authUnbinding(id: string) {
return requestClient.delete<unknown>(`/auth/unlock/${id}`);
}
/**
* oauth授权回调
* @param data oauth授权
* @returns void
*/
export function authCallback(data: AuthApi.OAuthLoginParams) {
return requestClient.post<unknown>('/auth/social/callback', data);
}
// 获取租户列表
export function tenantList() {
return requestClient.get<TenantResp>('/system/auth/getTenantList');
}
+21
View File
@@ -0,0 +1,21 @@
/**
* @description 验证码相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import { requestClient } from '#/api/request';
export namespace CaptchaApi {
export interface GetCaptchaResult {
captchaID: string;
captchaImage: string;
}
}
export async function getCaptchaApi() {
return requestClient.get<CaptchaApi.GetCaptchaResult>('/system/captcha');
}
+14
View File
@@ -0,0 +1,14 @@
/**
* @description 核心模块接口导出
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
export * from './auth';
export * from './menu';
export * from './user';
export * from './captcha';
export * from './upload';
+20
View File
@@ -0,0 +1,20 @@
/**
* @description 菜单相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import type { RouteRecordStringComponent } from '@vben/types';
import { requestClient } from '#/api/request';
/**
* 获取用户所有菜单
*/
export async function getAllMenusApi() {
return requestClient.get<RouteRecordStringComponent[]>('/system/menu/all');
}
+57
View File
@@ -0,0 +1,57 @@
/**
* @description 文件上传相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import type { AxiosRequestConfig } from '@vben/request';
import { requestClient } from '#/api/request';
/**
* Axios上传进度事件
*/
export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
/**
* 通过单文件上传接口
* @param file 上传的文件
* @param onUploadProgress 上传进度事件 非必传
* @param isDevice 不知道这是什么,但是不敢动
* @param newFileType 新文件类型 1:用户文件 2:用户图片 3:设备文件 4:iot配置图片 5:iot固件文件 6:项目图片 7:项目文件
* @param subDirName 文件父目录
* @param params 额外上传参数
* @returns 上传结果
*/
export function uploadApi(
file: Blob | File,
onUploadProgress?: AxiosProgressEvent,
isDevice?: boolean | undefined,
newFileType?: number,
subDirName?: string,
params?: Record<string, any>,
) {
return requestClient.upload(
'/common/oss/upload',
{
...params,
file,
fileType: 'file',
...(isDevice ? { isDevice } : {}),
...(newFileType ? { newFileType } : {}),
...(subDirName ? { subDirName } : {}),
},
{ onUploadProgress, timeout: 60_000 },
);
}
/**
* 默认上传结果
*/
export interface UploadResult {
url: string;
fileName: string;
ossId: string;
}
+20
View File
@@ -0,0 +1,20 @@
/**
* @description 用户信息相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
/**
* 获取用户信息
*/
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/system/user/info');
}
@@ -0,0 +1,39 @@
import { requestClient } from '#/api/request';
// 获取测试分类列表
export function List(params:any) {
return requestClient.get<any>('gen/testCategory/list', { params });
}
// 删除/批量删除测试分类
export function Delete(params:any) {
return requestClient.post<any>('gen/testCategory/delete', { ...params });
}
// 添加/编辑测试分类
export function Edit(params:any) {
return requestClient.post<any>('gen/testCategory/edit', { ...params });
}
// 修改测试分类状态
export function Status(params:any) {
return requestClient.post<any>('gen/testCategory/status', { ...params });
}
// 获取测试分类指定详情
export function View(params:any) {
return requestClient.get<any>('gen/testCategory/view', { params });
}
// 获取测试分类最大排序
export function MaxSort(params:any) {
return requestClient.get<any>('gen/testCategory/maxSort', { params });
}
// 导出测试分类
export function Export(params:any) {
return requestClient.post<Blob>('/gen/testCategory/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,38 @@
/**
* @description 测试单表相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import { requestClient } from '#/api/request';
// 获取测试单表列表
export function List(params:any) {
return requestClient.get<any>('gen/testDemo/list', { params });
}
// 删除/批量删除测试单表
export function Delete(params:any) {
return requestClient.post<any>('gen/testDemo/delete', { ...params });
}
// 添加/编辑测试单表
export function Edit(params:any) {
return requestClient.post<any>('gen/testDemo/edit', { ...params });
}
// 获取测试单表指定详情
export function View(params:any) {
return requestClient.get<any>('gen/testDemo/view', { params });
}
// 导出测试单表
export function Export(params:any) {
return requestClient.post<Blob>('/gen/testDemo/export', { ...params } , {
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
responseType: 'blob',
});
}
@@ -0,0 +1,325 @@
/**
* @description 代码生成器表格相关接口
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
import { requestClient } from '#/api/request';
import type { DictOption } from '#/store/dict';
export interface SysGenTableListReq {
page: number;
pageSize: number;
genType: number;
varName: string;
status: string;
}
export interface SysGenTableListModel {
tableId: number; // 生成ID
genType: number; // 生成类型
genTemplate: number; // 生成模板
varName: string; // 实体命名
options: string; // 配置选项
dbName: string; // 数据库名称
tableName: string; // 主表名称
tableComment: string; // 主表注释
daoName: string; // 主表dao模型
masterColumns: string; // 主表字段
addonName: string; // 插件名称
status: string; // 生成状态(0成功
createdDept: number; // 创建组织
createdBy: number; // 创建者
createdAt: string; // 创建时间
}
export interface SysGenTableListRes {
items: SysGenTableListModel[];
total: number;
}
export async function getSysGenTableListApi(params: SysGenTableListReq) {
return requestClient.get<SysGenTableListRes>('/genCodes/genTable/list', { params });
}
export interface SysGenTableAddReq {
genType: number;
genTemplate: number;
varName: string;
options: string;
dbName: string;
tableName: string;
tableComment: string;
daoName: string;
masterColumns: string;
addonName: string;
}
export interface SysGenTableAddRes {
tableId: number;
}
export interface SysGenTableEditReq {
tableId: number;
genType?: number;
genTemplate?: number;
varName?: string;
options?: any;
dbName?: string;
tableName?: string;
tableComment?: string;
daoName?: string;
masterColumns?: any;
addonName?: string;
}
export interface SysGenTableEditRes {
tableId: number;
}
export interface SysGenTableDeleteReq {
tableIds: number[];
}
export interface SysGenTableDeleteRes {
tableIds: number[];
}
export interface SysGenTableJoinModel {
uuid: string;
dbName: string;
masterTableName: string;
linkTable: string;
alias: string;
linkMode: number;
field: string;
masterField: string;
daoName: string;
columns: GenCodesColumn[];
}
export interface SysGenTableTreeModel {
titleColumn: string;
styleType: number;
}
export interface SysGenTableMenuModel {
icon: string;
pid: number;
sort: number;
}
export interface SysGenTableFuncDictModel {
valueColumn: string|null ;
labelColumn: string|null;
}
export interface SysGenTablePresetStepModel {
formGridCols: number;
}
export interface SysGenTableOptionsModel {
headOps?: string[];
columnOps?: string[];
autoOps?: string[];
join?:any[];
menu?:SysGenTableMenuModel;
funcDict?: SysGenTableFuncDictModel;
presetStep?:SysGenTablePresetStepModel;
tree?:SysGenTableTreeModel;
}
export interface SysGenTableViewReq {
tableId: number;
}
export interface SysGenTableViewRes {
tableId: number;
genType: number;
genTemplate: number;
varName: string;
options: SysGenTableOptionsModel|string;
dbName: string;
tableName: string;
tableComment: string;
daoName: string;
masterColumns: GenCodesColumn[]|string;
addonName: string;
status: string;
createdDept: number;
createdBy: number;
createdAt: string;
}
export async function addSysGenTableApi(params: SysGenTableAddReq | {
[x: string]: any;
}) {
return requestClient.post<SysGenTableAddRes>('/genCodes/genTable/add', { ...params });
}
export async function deleteSysGenTableApi(params: SysGenTableDeleteReq) {
return requestClient.post<SysGenTableDeleteRes>('/genCodes/genTable/delete', { ...params });
}
export async function editSysGenTableApi(params: SysGenTableEditReq) {
return requestClient.post<SysGenTableEditRes>('/genCodes/genTable/edit', { ...params });
}
export async function getSysGenTableViewApi(params: SysGenTableViewReq) {
return requestClient.get<SysGenTableViewRes>('/genCodes/genTable/view', { params });
}
export interface SelectItemModel {
value: string|number;
label: string;
name: string;
disabled?: boolean;
}
export interface SelectGenTypeModel extends SelectItemModel,DictOption {
templates: SelectGenTemplateModel[];
}
export interface SelectGenTemplateModel extends SelectItemModel {
isAddon: boolean;
}
export interface GenCodesSelect {
genType: SelectGenTypeModel[];
db: SelectItemModel[];
status: SelectItemModel[];
linkMode: SelectItemModel[];
buildMethod: SelectItemModel[];
formMode: SelectItemModel[];
formRole: SelectItemModel[];
whereMode: SelectItemModel[];
addons: SelectItemModel[];
tableAlign: SelectItemModel[];
treeStyleType: SelectItemModel[];
dictMode: SelectItemModel[];
}
export interface GenCodesSelectsReq {
}
export interface GenCodesSelectsRes extends GenCodesSelect {
}
export async function getGenCodesSelectsApi(params: GenCodesSelectsReq) {
return requestClient.get<GenCodesSelectsRes>('/genCodes/genTable/selects', { params });
}
export interface DbTableSelectModel {
value: string;
label: string;
name: string;
daoName: string;
defVarName: string;
defAlias: string;
defTableComment: string;
}
export interface GenCodesTableSelectReq {
dbGroup: string;
}
export interface GenCodesTableSelectRes {
items: DbTableSelectModel[];
}
export async function getGenCodesTableSelectApi(params: GenCodesTableSelectReq) {
return requestClient.get<GenCodesTableSelectRes>('/genCodes/genTable/tableSelect', { params });
}
export interface DbColumnSelectModel {
value: string;
label: string;
name: string;
}
export interface GenCodesColumnSelectReq {
dbGroup: string;
tableName: string;
}
export interface GenCodesColumnSelectRes {
items: DbColumnSelectModel[];
}
export async function getGenCodesColumnSelectApi(params: GenCodesColumnSelectReq) {
return requestClient.get<GenCodesColumnSelectRes>('/genCodes/genTable/columnSelect', { params });
}
export interface GenCodesColumn {
id: number;
name: string;
dc: string;
dataType: string;
sqlType: string;
length: number;
isAllowNull: boolean;
defaultValue: any;
index: string;
extra: string;
goName: string;
goType: string;
tsName: string;
tsType: string;
isList: boolean;
isExport: boolean;
isSort: boolean;
isQuery: boolean;
queryWhere: string;
isEdit: boolean;
required: boolean;
unique: boolean;
formMode: string;
formRole: string;
formGridSpan: number;
dictType: number;
align: string;
width: number;
}
export interface GenCodesColumnListModel extends GenCodesColumn {
}
export interface GenCodesColumnListParam {
dbGroup: string;
tableName: string;
isLink: number;
alias: string;
}
export interface GenCodesColumnListRes {
items: GenCodesColumnListModel[];
}
export async function getGenCodesColumnListApi(params: GenCodesColumnListParam) {
return requestClient.get<GenCodesColumnListRes>('/genCodes/genTable/columnList', { params });
}
export interface GenCodesPreviewReq extends SysGenTableListModel {
}
export interface GenFile {
content: string;
path: string;
meth: number;
required: boolean;
}
export interface GenCodesPreviewRes {
views: Record<string, GenFile>;
config?: any;
}
export async function postGenCodesPreviewApi(params: GenCodesPreviewReq) {
return requestClient.post<GenCodesPreviewRes>('/genCodes/genTable/preview', { ...params });
}
export interface GenCodesBuildReq extends GenCodesPreviewReq {
}
export interface GenCodesBuildRes {
}
export async function postGenCodesBuildApi(params: GenCodesBuildReq) {
return requestClient.post<GenCodesBuildRes>('/genCodes/genTable/build', { ...params });
}
+11
View File
@@ -0,0 +1,11 @@
/**
* @description API接口统一导出
* @Link https://github.com/xiujiecn/xiu-admin
* @Copyright Copyright (c) 2025 LiXiujie
* @Author Lxj <li@xiujie.cn>
* @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
* @date 2024-03-21
*/
export * from './core';
export * from './system';
@@ -0,0 +1,47 @@
import type {
TApiDeleteParam,
TApiEditParam,
TApiExportParam,
TApiListParam,
TApiListRes,
TApiViewParam,
TApiViewRes,
} from './type';
import { requestClient } from '#/api/request';
// 获取API集成列表
export function List(params: TApiListParam) {
return requestClient.get<TApiListRes>('agent/tApi/list', { params });
}
// 删除/批量删除API集成
export function Delete(params: TApiDeleteParam) {
return requestClient.post('agent/tApi/delete', { ...params });
}
// 添加/编辑API集成
export function Edit(params: TApiEditParam) {
return requestClient.post('agent/tApi/edit', { ...params });
}
// 获取API集成指定详情
export function View(params: TApiViewParam) {
return requestClient.get<TApiViewRes>('agent/tApi/view', {
params,
});
}
// 导出API集成
export function Export(params: TApiExportParam) {
return requestClient.post<Blob>(
'/agent/tApi/export',
{ ...params },
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
responseType: 'blob',
},
);
}
@@ -0,0 +1,105 @@
import type { GlobalListRes } from '#/types/global';
import type { MemberSumma } from '#/utils';
/** API集成列表项 */
export interface TApiModel {
/** ID */
id: number;
/** APPKey */
appKey: string;
/** APPSecret */
appSecret: string;
/** API分组 */
groupId: number;
/** 限流周期(秒,0:不限流) */
rateLimitPeriod: number;
/** 限流次数 */
rateLimitCount: number;
/** 所属机构 */
deptId: number;
/** 客户编号 */
deptKey: string;
/** 客户名称 */
deptName: string;
/** 租户ID */
tenantId: string;
/** 创建部门 */
createdDept: number;
/** 创建者 */
createdBy: number;
/** 创建者摘要信息 */
createdBySumma: MemberSumma;
/** 创建时间 */
createdAt: string;
/** 更新者 */
updatedBy: number;
/** 更新者摘要信息 */
updatedBySumma: MemberSumma;
/** 更新时间 */
updatedAt: string;
}
/** 获取API集成列表 请求参数 */
export interface TApiListParam {
/** 页码 */
page?: number;
/** 每页大小 */
pageSize?: number;
/** 关键字 */
keyword?: string;
/** ID */
id?: number;
/** 创建时间 */
createdAt?: string[];
}
/** 获取API集成列表 响应 */
export type TApiListRes = GlobalListRes<TApiModel>;
/** 导出API集成列表 请求参数 */
export type TApiExportParam = TApiListParam;
/** 修改/新增API集成 请求参数 */
export interface TApiEditParam {
/** ID */
id?: number;
/** APPKey */
appKey?: string;
/** APPSecret */
appSecret?: string;
/** API分组 */
groupId?: number;
/** 限流周期(秒,0:不限流) */
rateLimitPeriod?: number;
/** 限流次数 */
rateLimitCount?: number;
/** 所属机构 */
deptId?: number;
/** 租户ID */
tenantId?: string;
/** 创建部门 */
createdDept?: number;
/** 创建者 */
createdBy?: number;
/** 创建时间 */
createdAt?: string;
/** 更新者 */
updatedBy?: number;
/** 更新时间 */
updatedAt?: string;
}
/** 获取API集成指定信息 请求参数 */
export interface TApiViewParam {
/** APPKey */
appKey: string;
}
/** 获取API集成指定信息 响应 */
export type TApiViewRes = TApiModel;
/** 删除API集成 请求参数 */
export interface TApiDeleteParam {
/** 主键ID */
id: number | number[];
}
@@ -0,0 +1,53 @@
import type {
TSimChannelTemplateDeleteParam,
TSimChannelTemplateEditParam,
TSimChannelTemplateExportParam,
TSimChannelTemplateListParam,
TSimChannelTemplateListRes,
TSimChannelTemplateViewParam,
TSimChannelTemplateViewRes,
} from './type';
import { requestClient } from '#/api/request';
// 获取通道模板列表
export function List(params: TSimChannelTemplateListParam) {
return requestClient.get<TSimChannelTemplateListRes>(
'iot/tSimChannelTemplate/list',
{ params },
);
}
// 删除/批量删除通道模板
export function Delete(params: TSimChannelTemplateDeleteParam) {
return requestClient.post('iot/tSimChannelTemplate/delete', { ...params });
}
// 添加/编辑通道模板
export function Edit(params: TSimChannelTemplateEditParam) {
return requestClient.post('iot/tSimChannelTemplate/edit', { ...params });
}
// 获取通道模板指定详情
export function View(params: TSimChannelTemplateViewParam) {
return requestClient.get<TSimChannelTemplateViewRes>(
'iot/tSimChannelTemplate/view',
{
params,
},
);
}
// 导出通道模板
export function Export(params: TSimChannelTemplateExportParam) {
return requestClient.post<Blob>(
'/iot/tSimChannelTemplate/export',
{ ...params },
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
responseType: 'blob',
},
);
}
@@ -0,0 +1,141 @@
import type { GlobalListRes } from '#/types/global';
import type { MemberSumma } from '#/utils';
/** 通道模板列表项 */
export interface TSimChannelTemplateModel {
/** ID */
id: number;
/** 模板标识 */
tempKey: string;
/** 模板名称 */
tempName: string;
/** 模板模型 */
tempModel: string;
/** 模板配置 */
tempConfig: string;
/** 模板状态(0未启用,1启用) */
tempStatus: number;
/** 租户ID */
tenantId: string;
/** 创建部门 */
createdDept: number;
/** 创建者 */
createdBy: number;
/** 创建者摘要信息 */
createdBySumma: MemberSumma;
/** 创建时间 */
createdAt: string;
/** 更新者 */
updatedBy: number;
/** 更新者摘要信息 */
updatedBySumma: MemberSumma;
/** 更新时间 */
updatedAt: string;
}
/** 获取通道模板指定信息响应项 */
export interface TSimChannelTemplateViewModel {
/** ID */
id: number;
/** 模板标识 */
tempKey: string;
/** 模板名称 */
tempName: string;
/** 模板模型 */
tempModel: string;
/** 模板配置 */
tempConfig: string;
/** 模板状态(0未启用,1启用) */
tempStatus: number;
/** 备注 */
remark: string;
/** 租户ID */
tenantId: string;
/** 创建部门 */
createdDept: number;
/** 创建者 */
createdBy: number;
/** 创建时间 */
createdAt: string;
/** 更新者 */
updatedBy: number;
/** 更新时间 */
updatedAt: string;
/** 创建者摘要信息 */
createdBySumma: MemberSumma;
/** 更新者摘要信息 */
updatedBySumma: MemberSumma;
}
/** 获取通道模板列表 请求参数 */
export interface TSimChannelTemplateListParam {
/** 页码 */
page?: number;
/** 每页大小 */
pageSize?: number;
/** 关键字 */
keyword?: string;
/** ID */
id?: number;
/** 模板标识 */
tempKey?: string;
/** 模板名称 */
tempName?: string;
/** 模板模型 */
tempModel?: string;
/** 模板状态(0未启用,1启用) */
tempStatus?: number;
/** 创建时间 */
createdAt?: string[];
}
/** 获取通道模板列表 响应 */
export type TSimChannelTemplateListRes = GlobalListRes<TSimChannelTemplateModel>;
/** 导出通道模板列表 请求参数 */
export type TSimChannelTemplateExportParam = TSimChannelTemplateListParam;
/** 修改/新增通道模板 请求参数 */
export interface TSimChannelTemplateEditParam {
/** ID */
id?: number;
/** 模板标识 */
tempKey?: string;
/** 模板名称 */
tempName?: string;
/** 模板模型 */
tempModel?: string;
/** 模板配置 */
tempConfig?: string;
/** 模板状态(0未启用,1启用) */
tempStatus?: number;
/** 备注 */
remark?: string;
/** 租户ID */
tenantId?: string;
/** 创建部门 */
createdDept?: number;
/** 创建者 */
createdBy?: number;
/** 创建时间 */
createdAt?: string;
/** 更新者 */
updatedBy?: number;
/** 更新时间 */
updatedAt?: string;
}
/** 获取通道模板指定信息 请求参数 */
export interface TSimChannelTemplateViewParam {
/** ID */
id: number;
}
/** 获取通道模板指定信息 响应 */
export type TSimChannelTemplateViewRes = TSimChannelTemplateViewModel;
/** 删除通道模板 请求参数 */
export interface TSimChannelTemplateDeleteParam {
/** 主键ID */
id: number | number[];
}
@@ -0,0 +1,62 @@
import type {
TSimChannelsDeleteParam,
TSimChannelsEditParam,
TSimChannelsExportParam,
TSimChannelsListParam,
TSimChannelsListRes,
TSimChannelsTestParam,
TSimChannelsTestRes,
TSimChannelsViewParam,
TSimChannelsViewRes,
} from './type';
import { requestClient } from '#/api/request';
// 获取通道管理列表
export function List(params: TSimChannelsListParam) {
return requestClient.get<TSimChannelsListRes>('iot/tSimChannels/list', {
params,
});
}
// 删除/批量删除通道管理
export function Delete(params: TSimChannelsDeleteParam) {
return requestClient.post('iot/tSimChannels/delete', { ...params });
}
// 添加/编辑通道管理
export function Edit(params: TSimChannelsEditParam) {
return requestClient.post('iot/tSimChannels/edit', { ...params });
}
// 获取通道管理指定详情
export function View(params: TSimChannelsViewParam) {
return requestClient.get<TSimChannelsViewRes>('iot/tSimChannels/view', {
params,
});
}
// 导出通道管理
export function Export(params: TSimChannelsExportParam) {
return requestClient.post<Blob>(
'/iot/tSimChannels/export',
{ ...params },
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
responseType: 'blob',
},
);
}
export function Test(params: TSimChannelsTestParam) {
return requestClient.post<TSimChannelsTestRes>('iot/tSimChannels/test', {
...params,
});
}
/** 刷新通道卡数量统计 */
export function RefreshCardCount() {
return requestClient.post('iot/tSimChannels/refresh-card-count');
}
@@ -0,0 +1,306 @@
import type { GlobalListRes } from '#/types/global';
import type { MemberSumma } from '#/utils';
/** 通道管理列表项 */
export interface TSimChannelsModel {
/** 库存卡数量 */
stockCardCount: number;
/** 卡信息数量 */
cardInfoCount: number;
/** 销户卡数量 */
cancellationCardCount: number;
/** ID */
id: number;
/** 运营商:1移动,2联通,3电信 */
operator: number;
/** 模板标识 */
tempKey: string;
/** 是否允许同步:0否 1是 */
syncEnabled: number;
/** 模板名称 */
tempName: string;
/** 模板模型,用于识别共享 Token 能力 */
tempModel?: string;
/** 通道编号 */
chanKey: string;
/** 通道名称 */
chanName: string;
/** 通道配置 */
chanConfig: string;
/** 通道状态(0未启用,1启用) */
chanStatus: number;
/** 是否开放共享Token(0否,1是) */
shareEnabled: number;
/** 是否正常(0是,1否) */
isNormal: null | number;
/** 对接信息 */
dockingStatus: null | number;
/** 通道别名 */
chanAlias: string;
/** 通道行政区域代码(省市) */
regionCode: string;
/** 应用标识 */
appKey: string;
/** 应用密钥 */
appSecret: string;
/** 接口调用凭证 */
accessToken: string;
/** 凭证申请时间 */
accessTokenCreated: string;
/** 凭证过期时间 */
accessTokenExpire: string;
/** 刷新凭证 */
refreshToken: string;
/** 刷新凭证申请时间 */
refreshTokenCreated: string;
/** 刷新凭证过期时间 */
refreshTokenExpire: string;
/** 备注 */
remark: string;
/** 登录账号 */
loginUser: string;
/** 登录密码 */
loginPassword: string;
/** 登录地址 */
loginUrl: string;
/** 登录状态(0未启用,1启用) */
loginState: number;
/** 租户ID */
tenantId: string;
/** 创建部门 */
createdDept: number;
/** 创建者 */
createdBy: number;
/** 创建者摘要信息 */
createdBySumma: MemberSumma;
/** 创建时间 */
createdAt: string;
/** 更新者 */
updatedBy: number;
/** 更新者摘要信息 */
updatedBySumma: MemberSumma;
/** 更新时间 */
updatedAt: string;
}
/** 获取通道管理指定信息响应项 */
export interface TSimChannelsViewModel {
/** ID */
id: number;
/** 运营商:1移动,2联通,3电信 */
operator: number;
/** 模板标识 */
tempKey: string;
/** 是否允许同步:0否 1是 */
syncEnabled: number;
/** 模板模型,用于识别共享 Token 能力 */
tempModel?: string;
/** 通道编号 */
chanKey: string;
/** 通道名称 */
chanName: string;
/** 通道配置 */
chanConfig: string;
/** 通道状态(0启用,1停用) */
chanStatus: number;
/** 是否开放共享Token(0否,1是) */
shareEnabled: number;
/** 是否正常(0是,1否) */
isNormal: null | number;
/** 对接信息 */
dockingStatus: null | number;
/** 通道别名 */
chanAlias: string;
/** 通道行政区域代码(省市) */
regionCode: string;
/** 应用标识 */
appKey: string;
/** 应用密钥 */
appSecret: string;
/** 接口调用凭证 */
accessToken: string;
/** 凭证申请时间 */
accessTokenCreated: string;
/** 凭证过期时间 */
accessTokenExpire: string;
/** 刷新凭证 */
refreshToken: string;
/** 备注 */
remark: string;
/** 登录账号 */
loginUser: string;
/** 登录密码 */
loginPassword: string;
/** 登录地址 */
loginUrl: string;
/** 租户ID */
tenantId: string;
/** 创建部门 */
createdDept: number;
/** 创建者 */
createdBy: number;
/** 创建时间 */
createdAt: string;
/** 更新者 */
updatedBy: number;
/** 更新时间 */
updatedAt: string;
/** 模板名称 */
tempName: string;
/** 创建者摘要信息 */
createdBySumma: MemberSumma;
/** 更新者摘要信息 */
updatedBySumma: MemberSumma;
}
/** 获取通道管理列表 请求参数 */
export interface TSimChannelsListParam {
/** 页码 */
page?: number;
/** 每页大小 */
pageSize?: number;
/** 关键字 */
keyword?: string;
/** ID */
id?: number;
/** 运营商:1移动,2联通,3电信 */
operator?: number;
/** 模板标识 */
tempKey?: string;
/** 通道编号 */
chanKey?: string;
/** 通道名称 */
chanName?: string;
/** 通道别名 */
chanAlias?: string;
/** 通道状态(0未启用,1启用) */
chanStatus?: number;
shareEnabled?: number;
isNormal?: number | null;
dockingStatus?: number | null;
syncEnabled?: number;
/** 创建时间 */
createdAt?: string[];
}
/** 获取通道管理列表 响应 */
export type TSimChannelsListRes = GlobalListRes<TSimChannelsModel>;
/** 导出通道管理列表 请求参数 */
export type TSimChannelsExportParam = TSimChannelsListParam;
/** 修改/新增通道管理 请求参数 */
export interface TSimChannelsEditParam {
/** ID */
id?: number;
/** 运营商:1移动,2联通,3电信 */
operator?: number;
/** 模板标识 */
tempKey?: string;
/** 通道编号 */
chanKey?: string;
/** 通道名称 */
chanName?: string;
/** 通道配置 */
chanConfig?: string;
/** 通道状态(0未启用,1启用) */
chanStatus?: number;
/** 是否开放共享Token(0否,1是) */
shareEnabled?: number;
/** 是否正常(0是,1否) */
isNormal?: null | number;
/** 对接信息 */
dockingStatus?: null | number;
/** 通道别名 */
chanAlias?: string;
/** 通道行政区域代码(省市) */
regionCode?: string;
/** 应用标识 */
appKey?: string;
/** 应用密钥 */
appSecret?: string;
/** 接口调用凭证 */
accessToken?: string;
/** 凭证申请时间 */
accessTokenCreated?: string;
/** 凭证过期时间 */
accessTokenExpire?: string;
/** 刷新凭证 */
refreshToken?: string;
/** 刷新凭证申请时间 */
refreshTokenCreated?: string;
/** 刷新凭证过期时间 */
refreshTokenExpire?: string;
/** 备注 */
remark?: string;
/** 登录账号 */
loginUser?: string;
/** 登录密码 */
loginPassword?: string;
/** 登录地址 */
loginUrl?: string;
/** 登录状态(0未启用,1启用) */
loginState?: number;
/** 租户ID */
tenantId?: string;
/** 创建部门 */
createdDept?: number;
/** 创建者 */
createdBy?: number;
/** 创建时间 */
createdAt?: string;
/** 更新者 */
updatedBy?: number;
/** 更新时间 */
updatedAt?: string;
}
/** 获取通道管理指定信息 请求参数 */
export interface TSimChannelsViewParam {
/** ID */
id: number;
}
/** 获取通道管理指定信息 响应 */
export type TSimChannelsViewRes = TSimChannelsViewModel;
/** 删除通道管理 请求参数 */
export interface TSimChannelsDeleteParam {
/** 主键ID */
id: number | number[];
}
/** 通道测试请求参数 */
export interface TSimChannelsTestParam {
channelId: number;
inputMode: 'complete' | 'direct_single' | 'single';
cardValueType?: 'iccid' | 'imsi' | 'msisdn';
cardValue?: string;
iccid?: string;
imsi?: string;
msisdn?: string;
/** 未传时执行全部查询项 */
items?: string[];
}
export type TSimChannelsTestItemStatus = 'failed' | 'success' | 'timeout';
/** 单项测试结果 */
export interface TSimChannelsTestItem {
key: string;
name: string;
status: TSimChannelsTestItemStatus;
durationMs: number;
errorCode?: number;
message?: string;
data?: unknown;
/** 完整通道测试响应 JSON,归属当前单项展示 */
responseJson?: string;
}
/** 通道测试响应 */
export interface TSimChannelsTestRes {
items: TSimChannelsTestItem[];
/** Unmodified response body from the channel gateway test endpoint. */
gatewayResponseRaw?: string;
}
@@ -0,0 +1,52 @@
import type {
TSimCardAllocateBatchSubmitParam,
TSimCardAllocateBatchSubmitRes,
TSimCardAllocateExcelSubmitParam,
TSimCardAllocateInputPrepareParam,
TSimCardAllocatePrepareRes,
TSimCardAllocateTarget,
} from './type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardAllocate/excel/template', {
responseReturn: 'raw',
});
}
export function ListTargets(keyword = '') {
return requestClient.get<{ items: TSimCardAllocateTarget[] }>(
'/iot/tSimCardAllocate/targets',
{ params: { keyword } },
);
}
export function InputPrepare(params: TSimCardAllocateInputPrepareParam) {
return requestClient.post<TSimCardAllocatePrepareRes>(
'/iot/tSimCardAllocate/input/prepare',
params,
);
}
export function ExcelSubmit(params: TSimCardAllocateExcelSubmitParam) {
const data: Record<string, any> & { file: File } = {
file: params.file,
remark: params.remark ?? '',
};
if (params.targetDeptId) {
data.targetDeptId = String(params.targetDeptId);
}
return requestClient.upload<TSimCardAllocatePrepareRes>(
'/iot/tSimCardAllocate/excel/submit',
data,
{ timeout: 10 * 60_000 },
);
}
export function BatchSubmit(params: TSimCardAllocateBatchSubmitParam) {
return requestClient.post<TSimCardAllocateBatchSubmitRes>(
'/iot/tSimCardAllocate/batch/submit',
params,
);
}
@@ -0,0 +1,49 @@
export interface TSimCardAllocateTarget {
deptId: number;
parentId: number;
deptKey?: string;
deptName: string;
deptCategory?: string;
}
export interface TSimCardAllocateInputPrepareParam {
input: string;
targetDeptId: number;
}
export interface TSimCardAllocateExcelSubmitParam {
file: File;
targetDeptId?: number;
remark?: string;
}
export interface TSimCardAllocateBatchSubmitParam {
batchNo: string;
targetDeptId?: number;
remark?: string;
}
export interface TSimCardAllocateBatchDetail {
rowNo: number;
itemId: string;
targetDeptId?: number;
targetDeptKey?: string;
targetDeptName?: string;
status: string;
errorReason?: string;
}
export interface TSimCardAllocatePrepareRes {
batchNo: string;
batchType: number;
batchAsyncStatus?: number;
target?: TSimCardAllocateTarget;
totalCount: number;
validCount: number;
failCount: number;
failReason?: string;
status: string;
details: TSimCardAllocateBatchDetail[];
}
export type TSimCardAllocateBatchSubmitRes = TSimCardAllocatePrepareRes;
@@ -0,0 +1,94 @@
import type {
TSimAddPackageBatchConfirmParam,
TSimAddPackageBatchConfirmRes,
TSimAddPackageOptionsRes,
TSimAddPackagePrepareResult,
TSimBindPackageAddPackageExcelSubmitParam,
TSimBindPackageAddPackageInputPrepareParam,
TSimBindPackageAddPackageInputPrepareRes,
TSimBindPackageAddPackageInputSubmitParam,
TSimBindPackageAddPackageSubmitRes,
} from './type';
import { requestClient } from '#/api/request';
// 获取卡绑定套餐列表
export function List(params: any) {
return requestClient.get<any>('iot/tSimCardBindPkg/list', { params });
}
// 获取卡套餐详情
export function GetPkgInfo(params: any) {
return requestClient.get<any>('iot/tSimCardBindPkg/getPkgInfo', { params });
}
// 获取变更套餐
export function GetChangePkg(params: any) {
return requestClient.get<any>('iot/tSimCardBindPkg/getChangePkg', { params });
}
export function DownloadAddPackageTemplate() {
return requestClient.download(
'/iot/tSimCardBindPkg/addPackage/excel/template',
{
responseReturn: 'raw',
},
);
}
export function AddPackageExcelSubmit(
params: TSimBindPackageAddPackageExcelSubmitParam,
): Promise<TSimBindPackageAddPackageSubmitRes> {
return requestClient.upload(
'/iot/tSimCardBindPkg/addPackage/excel/submit',
{
file: params.file,
manualConfirm: params.manualConfirm,
},
{
timeout: 60_000,
},
);
}
export function AddPackageOptions(params: {
iccids: string[];
}): Promise<TSimAddPackageOptionsRes> {
return requestClient.post('/iot/tSimCardBindPkg/addPackage/options', params);
}
export function AddPackagePaymentContext(): Promise<TSimAddPackageOptionsRes> {
return requestClient.post('/iot/tSimCardBindPkg/addPackage/payment-context');
}
export function AddPackageInputSubmit(
params: TSimBindPackageAddPackageInputSubmitParam,
): Promise<TSimBindPackageAddPackageSubmitRes> {
return requestClient.post('/iot/tSimCardBindPkg/addPackage/input/submit', {
...params,
});
}
export function AddPackageInputPrepare(
params: TSimBindPackageAddPackageInputPrepareParam,
): Promise<TSimBindPackageAddPackageInputPrepareRes> {
return requestClient.post('/iot/tSimCardBindPkg/addPackage/input/prepare', {
...params,
});
}
export function AddPackagePrepareResult(
batchNo: string,
): Promise<TSimAddPackagePrepareResult> {
return requestClient.get('/iot/tSimCardBindPkg/addPackage/prepare/result', {
params: { batchNo },
});
}
export function AddPackageBatchConfirm(
params: TSimAddPackageBatchConfirmParam,
): Promise<TSimAddPackageBatchConfirmRes> {
return requestClient.post('/iot/tSimCardBindPkg/addPackage/batch/confirm', {
...params,
});
}
@@ -0,0 +1,119 @@
export type AddPackageEffectiveMode = 'current_month';
export type AddPackagePaymentMode = 'balance' | 'offline';
export interface TSimBindPackageAddPackageExcelSubmitParam {
file: File;
paymentMode?: AddPackagePaymentMode;
offlineContractCompleted?: boolean;
manualConfirm?: boolean;
}
export interface TSimBindPackageAddPackageInputSubmitParam {
input: string;
packageCode?: string;
effectiveMode?: AddPackageEffectiveMode | string;
remark?: string;
paymentMode?: AddPackagePaymentMode;
offlineContractCompleted?: boolean;
[property: string]: any;
}
export interface TSimBindPackageAddPackageInputPrepareParam {
input: string;
packageCode?: string;
effectiveMode?: AddPackageEffectiveMode | string;
remark?: string;
[property: string]: any;
}
export interface TSimBindPackageAddPackageSubmitRes {
batchAsyncStatus?: number;
batchNo?: string;
batchType?: number;
targetBatchType?: number;
total?: number;
validCount?: number;
failCount?: number;
[property: string]: any;
}
export type TSimBindPackageAddPackageInputPrepareRes =
TSimAddPackagePrepareResult;
export interface TSimAddPackageBatchConfirmParam {
batchNo: string;
packageCode?: string;
effectiveMode?: AddPackageEffectiveMode | string;
remark?: string;
paymentMode?: AddPackagePaymentMode | string;
offlineContractCompleted?: boolean;
[property: string]: any;
}
export interface TSimAddPackageBatchConfirmRes {
batchAsyncStatus?: number;
batchNo?: string;
batchType?: number;
effectiveMode?: AddPackageEffectiveMode | string;
packageCode?: string;
status?: string;
[property: string]: any;
}
export interface TSimAddPackagePaymentAccount {
payerDeptId: number;
targetCount: number;
requiredAmount: number | string;
availableBalance: number | string;
balanceEnough: boolean;
}
export interface TSimAddPackageOption {
flowMb: number;
packageCode: string;
packageName: string;
unitPrice?: number | string;
totalAmount?: number | string;
priceAvailable?: boolean;
priceReason?: string;
paymentAccounts?: TSimAddPackagePaymentAccount[];
}
export interface TSimAddPackageOptionsRes {
items: TSimAddPackageOption[];
isTopDept?: boolean;
defaultPaymentMode?: AddPackagePaymentMode | string;
availableBalance?: number | string;
}
export interface TSimAddPackagePrepareResult {
availableBalance?: number | string;
batchAsyncStatus?: number;
batchInfo?: Record<string, any>;
batchNo?: string;
batchType?: number;
effectiveMode?: AddPackageEffectiveMode | string;
effectiveModeText?: string;
effectiveTime?: string;
expectedExpireTime?: string;
failCount?: number;
failReason?: string;
flowMb?: number | string;
isTopDept?: boolean;
packageCode?: string;
packageName?: string;
paymentAccounts?: TSimAddPackagePaymentAccount[];
availablePackages?: TSimAddPackageOption[];
quantity?: number;
runResult?: number;
snapshot?: Record<string, any>;
status?: string;
successCount?: number;
targetBatchType?: number;
total?: number;
totalAmount?: number | string;
unitPrice?: number | string;
validCount?: number;
defaultPaymentMode?: AddPackagePaymentMode | string;
[property: string]: any;
}
@@ -0,0 +1,31 @@
import type {
TSimCardCancelExcelSubmitParam,
TSimCardCancelInputSubmitParam,
TSimCardCancelSubmitRes,
} from './type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardCancel/excel/template', {
responseReturn: 'raw',
});
}
export function ExcelSubmit(
params: TSimCardCancelExcelSubmitParam,
): Promise<TSimCardCancelSubmitRes> {
return requestClient.upload(
'/iot/tSimCardCancel/excel/submit',
{ file: params.file },
{
timeout: 60_000,
},
);
}
export function InputSubmit(
params: TSimCardCancelInputSubmitParam,
): Promise<TSimCardCancelSubmitRes> {
return requestClient.post('/iot/tSimCardCancel/input/submit', params);
}
@@ -0,0 +1,39 @@
/** 提交Excel卡销户请求参数 */
export interface TSimCardCancelExcelSubmitParam {
/** Excel文件 */
file: File;
}
/** 提交批量输入卡销户请求参数 */
export interface TSimCardCancelInputSubmitParam {
/** 批量输入ICCID文本 */
input: string;
/** 销户备注 */
remark?: string;
[property: string]: any;
}
/** 卡销户提交响应 */
export interface TSimCardCancelSubmitRes {
/** 批次异步处理状态(Pending 等) */
batchAsyncStatus?: number;
/** 批次号 */
batchNo?: string;
/** 批次类型 */
batchType?: number;
/** 失败行数 */
failCount?: number;
/** 实际销户数量 */
quantity?: number;
/** 结果下载任务ID */
resultDownloadId?: number;
/** 结果文件OSS ID */
resultFileOssId?: number;
/** 导入状态 */
status?: string;
/** 解析总行数 */
total?: number;
/** 校验通过行数 */
validCount?: number;
[property: string]: any;
}
@@ -0,0 +1,119 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const download = vi.fn();
const get = vi.fn();
const post = vi.fn();
const upload = vi.fn();
vi.mock('#/api/request', () => ({
requestClient: {
download,
get,
post,
upload,
},
}));
describe('tSimCardChange api', () => {
beforeEach(() => {
download.mockReset();
get.mockReset();
post.mockReset();
upload.mockReset();
});
it('downloads excel change template from backend contract path', async () => {
const { DownloadTemplate } = await import('./index');
DownloadTemplate();
expect(download).toHaveBeenCalledWith(
'/iot/tSimCardChange/excel/template',
{
responseReturn: 'raw',
},
);
});
it('submits excel change as file-only form data', async () => {
const { ExcelSubmit } = await import('./index');
const file = new File(['iccid'], 'change.xlsx');
ExcelSubmit({ file });
expect(upload).toHaveBeenCalledWith(
'/iot/tSimCardChange/excel/submit',
{ file },
{
timeout: 60_000,
},
);
});
it('prepares input change with backend contract path', async () => {
const { InputPrepare } = await import('./index');
const params = {
durationMonths: 6,
effectiveMode: 'immediate' as const,
input: '8986000000000000001\n8986000000000000002',
newPackageCode: 'PKG002',
offlineContractCompleted: true,
};
InputPrepare(params);
expect(post).toHaveBeenCalledWith('/iot/tSimCardChange/input/prepare', {
...params,
});
});
it('prepares range change with backend contract path', async () => {
const { RangePrepare } = await import('./index');
const params = {
durationMonths: 6,
effectiveMode: 'next_month' as const,
endIccid: '8986000000000000009',
endImsi: '460000000000009',
endMsisdn: '13000000009',
newPackageCode: 'PKG002',
offlineContractCompleted: true,
startIccid: '8986000000000000001',
startImsi: '460000000000001',
startMsisdn: '13000000001',
};
RangePrepare(params);
expect(post).toHaveBeenCalledWith('/iot/tSimCardChange/range/prepare', {
...params,
});
});
it('submits change prepare batch with backend contract path', async () => {
const { BatchSubmit } = await import('./index');
const params = {
batchNo: 'CH202607060001',
durationMonths: 6,
effectiveMode: 'immediate' as const,
newPackageCode: 'PKG002',
offlineContractCompleted: true,
};
BatchSubmit(params);
expect(post).toHaveBeenCalledWith('/iot/tSimCardChange/batch/submit', {
...params,
});
});
it('polls change prepare result with backend contract path', async () => {
const { PrepareResult } = await import('./index');
const batchNo = 'CH202607060001';
PrepareResult(batchNo);
expect(get).toHaveBeenCalledWith('/iot/tSimCardChange/prepare/result', {
params: { batchNo },
});
});
});
@@ -0,0 +1,77 @@
import type {
TSimCardChangeBatchPrepareRes,
TSimCardChangeBatchPrepareResult,
TSimCardChangeBatchSubmitParam,
TSimCardChangeBatchSubmitRes,
TSimCardChangeExcelSubmitParam,
TSimCardChangeExcelSubmitRes,
TSimCardChangeInputPrepareParam,
TSimCardChangeInputOptionsParam,
TSimCardChangeOptionsRes,
TSimCardChangeRangePrepareParam,
TSimCardChangeRangeOptionsParam,
} from './type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardChange/excel/template', {
responseReturn: 'raw',
});
}
export function ExcelSubmit(
params: TSimCardChangeExcelSubmitParam,
): Promise<TSimCardChangeExcelSubmitRes> {
return requestClient.upload(
'/iot/tSimCardChange/excel/submit',
{
file: params.file,
},
{
timeout: 60_000,
},
);
}
export function InputOptions(params: TSimCardChangeInputOptionsParam) {
return requestClient.post<TSimCardChangeOptionsRes>(
'/iot/tSimCardChange/input/change-options',
params,
);
}
export function RangeOptions(params: TSimCardChangeRangeOptionsParam) {
return requestClient.post<TSimCardChangeOptionsRes>(
'/iot/tSimCardChange/range/change-options',
params,
);
}
export function InputPrepare(params: TSimCardChangeInputPrepareParam) {
return requestClient.post<TSimCardChangeBatchPrepareRes>(
'/iot/tSimCardChange/input/prepare',
{ ...params },
);
}
export function RangePrepare(params: TSimCardChangeRangePrepareParam) {
return requestClient.post<TSimCardChangeBatchPrepareRes>(
'/iot/tSimCardChange/range/prepare',
{ ...params },
);
}
export function BatchSubmit(params: TSimCardChangeBatchSubmitParam) {
return requestClient.post<TSimCardChangeBatchSubmitRes>(
'/iot/tSimCardChange/batch/submit',
{ ...params },
);
}
export function PrepareResult(batchNo: string) {
return requestClient.get<TSimCardChangeBatchPrepareResult>(
'/iot/tSimCardChange/prepare/result',
{ params: { batchNo } },
);
}
@@ -0,0 +1,149 @@
export type TSimCardChangeEffectiveMode = 'immediate' | 'next_month';
export interface TSimCardChangeInputPrepareParam {
durationMonths: number;
effectiveMode: TSimCardChangeEffectiveMode;
giftDurationMonths?: number;
input: string;
newPackageCode: string;
offlineContractCompleted: boolean;
[property: string]: any;
}
export interface TSimCardChangeExcelSubmitParam {
file: File;
}
export interface TSimCardChangeExcelSubmitRes {
batchNo?: string;
batchType?: number;
resultDownloadId?: number;
resultFileOssId?: number;
status?: string;
[property: string]: any;
}
export interface TSimCardChangeInputOptionsParam {
input: string;
}
export interface TSimCardChangeRangeOptionsParam {
endIccid: string;
endImsi: string;
endMsisdn: string;
startIccid: string;
startImsi: string;
startMsisdn: string;
}
export interface TSimCardChangePackageOption {
changeUnitPrice?: number | string;
flowMb?: number;
packageCode: string;
packageName: string;
packageType?: number;
renewableDurationOptions?: number[];
}
export interface TSimCardChangeOptionsRes {
customerAgentId?: number;
customerName?: string;
oldPackageCode?: string;
oldPackageName?: string;
oldPackageType?: number;
packages?: TSimCardChangePackageOption[];
quantity?: number;
}
export interface TSimCardChangeBatchPrepareRes {
batchNo: string;
batchType?: number;
agentDefaultPriceId?: number;
changeUnitPrice?: number | string;
customerAgentId?: number;
customerName?: string;
failCount?: number;
newPackageCode?: string;
newPackageFlowMb?: number;
newPackageName?: string;
newPackageType?: number;
oldPackageCode?: string;
oldPackageFlowMb?: number;
oldPackageName?: string;
oldPackageType?: number;
priceSource?: string;
quantity?: number;
renewableDurationOptions?: number[];
resultDownloadId?: number;
resultFileOssId?: number;
sourceBatchNo?: string;
status?: string;
targetBatchType?: number;
targetPriceId?: number;
total?: number;
validCount?: number;
[property: string]: any;
}
export interface TSimCardChangeRangePrepareParam {
durationMonths: number;
effectiveMode: TSimCardChangeEffectiveMode;
endIccid: string;
endImsi: string;
endMsisdn: string;
giftDurationMonths?: number;
newPackageCode: string;
offlineContractCompleted: boolean;
startIccid: string;
startImsi: string;
startMsisdn: string;
[property: string]: any;
}
export interface TSimCardChangeBatchSubmitParam {
adminNote?: string;
batchNo: string;
buyerNote?: string;
durationMonths: number;
effectiveMode: TSimCardChangeEffectiveMode;
giftDurationMonths?: number;
newPackageCode: string;
offlineContractCompleted: boolean;
remark?: string;
[property: string]: any;
}
export interface TSimCardChangeBatchSubmitRes {
batchNo?: string;
batchType?: number;
changeUnitPrice?: number | string;
customerAgentId?: number;
customerName?: string;
durationMonths?: number;
effectiveMode?: string;
giftDurationMonths?: number;
newPackageCode?: string;
newPackageName?: string;
oldPackageCode?: string;
oldPackageName?: string;
orderSn?: string;
packageBeginTime?: string;
packageEndTime?: string;
quantity?: number;
sourceBatchNo?: string;
status?: string;
totalAmount?: number | string;
[property: string]: any;
}
export type TSimCardChangeBatchAsyncStatus = number;
export interface TSimCardChangeBatchPrepareResult {
batchAsyncStatus: TSimCardChangeBatchAsyncStatus;
batchInfo?: TSimCardChangeBatchPrepareRes;
batchNo: string;
failReason?: string;
runResult?: number;
status: string;
[property: string]: any;
}
@@ -0,0 +1,56 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const download = vi.fn();
const post = vi.fn();
const upload = vi.fn();
vi.mock('#/api/request', () => ({
requestClient: {
download,
post,
upload,
},
}));
describe('tSimCardDataCut api', () => {
beforeEach(() => {
download.mockReset();
post.mockReset();
upload.mockReset();
});
it('downloads the data cut template from the backend contract path', async () => {
const { DownloadTemplate } = await import('./index');
DownloadTemplate();
expect(download).toHaveBeenCalledWith('/iot/tSimCardDataCut/excel/template', {
responseReturn: 'raw',
});
});
it('submits an excel data cut batch with the batch threshold', async () => {
const { ExcelSubmit } = await import('./index');
const file = new File(['iccid'], 'dataCut.xlsx');
ExcelSubmit({ file, trafficCutoffThreshold: 1024.125 });
expect(upload).toHaveBeenCalledWith(
'/iot/tSimCardDataCut/excel/submit',
{ file, trafficCutoffThreshold: 1024.125 },
{ timeout: 60_000 },
);
});
it('submits an input data cut batch from the backend contract path', async () => {
const { InputSubmit } = await import('./index');
const params = {
input: '8986000000000000001',
trafficCutoffThreshold: 2048,
};
InputSubmit(params);
expect(post).toHaveBeenCalledWith('/iot/tSimCardDataCut/input/submit', params);
});
});
@@ -0,0 +1,34 @@
import type {
TSimCardDataCutExcelSubmitParam,
TSimCardDataCutInputSubmitParam,
TSimCardDataCutSubmitRes,
} from './type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardDataCut/excel/template', {
responseReturn: 'raw',
});
}
export function ExcelSubmit(
params: TSimCardDataCutExcelSubmitParam,
): Promise<TSimCardDataCutSubmitRes> {
return requestClient.upload(
'/iot/tSimCardDataCut/excel/submit',
{
file: params.file,
trafficCutoffThreshold: params.trafficCutoffThreshold,
},
{
timeout: 60_000,
},
);
}
export function InputSubmit(
params: TSimCardDataCutInputSubmitParam,
): Promise<TSimCardDataCutSubmitRes> {
return requestClient.post('/iot/tSimCardDataCut/input/submit', params);
}
@@ -0,0 +1,16 @@
export interface TSimCardDataCutExcelSubmitParam {
file: File;
trafficCutoffThreshold: number;
}
export interface TSimCardDataCutInputSubmitParam {
input: string;
trafficCutoffThreshold: number;
}
export interface TSimCardDataCutSubmitRes {
batchNo: string;
batchType: number;
batchAsyncStatus: number;
total: number;
}
@@ -0,0 +1,58 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const download = vi.fn();
const post = vi.fn();
const upload = vi.fn();
vi.mock('#/api/request', () => ({
requestClient: {
download,
post,
upload,
},
}));
describe('tSimCardDataStart api', () => {
beforeEach(() => {
download.mockReset();
post.mockReset();
upload.mockReset();
});
it('downloads the data start template from the backend contract path', async () => {
const { DownloadTemplate } = await import('./index');
DownloadTemplate();
expect(download).toHaveBeenCalledWith('/iot/tSimCardDataStart/excel/template', {
responseReturn: 'raw',
});
});
it('submits an excel data start batch as file-only form data', async () => {
const { ExcelSubmit } = await import('./index');
const file = new File(['iccid'], 'dataStart.xlsx');
ExcelSubmit({ file });
expect(upload).toHaveBeenCalledWith(
'/iot/tSimCardDataStart/excel/submit',
{ file },
{ timeout: 60_000 },
);
});
it('submits an input data start batch from the backend contract path', async () => {
const { InputSubmit } = await import('./index');
const params = {
input: '8986000000000000001',
};
InputSubmit(params);
expect(post).toHaveBeenCalledWith(
'/iot/tSimCardDataStart/input/submit',
params,
);
});
});
@@ -0,0 +1,33 @@
import type {
TSimCardLifecycleExcelSubmitParam,
TSimCardLifecycleInputSubmitParam,
TSimCardLifecycleSubmitRes,
} from '../tSimCardLifecycle/type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardDataStart/excel/template', {
responseReturn: 'raw',
});
}
export function ExcelSubmit(
params: TSimCardLifecycleExcelSubmitParam,
): Promise<TSimCardLifecycleSubmitRes> {
return requestClient.upload(
'/iot/tSimCardDataStart/excel/submit',
{
file: params.file,
},
{
timeout: 60_000,
},
);
}
export function InputSubmit(
params: TSimCardLifecycleInputSubmitParam,
): Promise<TSimCardLifecycleSubmitRes> {
return requestClient.post('/iot/tSimCardDataStart/input/submit', params);
}
@@ -0,0 +1,58 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const download = vi.fn();
const post = vi.fn();
const upload = vi.fn();
vi.mock('#/api/request', () => ({
requestClient: {
download,
post,
upload,
},
}));
describe('tSimCardDataStop api', () => {
beforeEach(() => {
download.mockReset();
post.mockReset();
upload.mockReset();
});
it('downloads the data stop template from the backend contract path', async () => {
const { DownloadTemplate } = await import('./index');
DownloadTemplate();
expect(download).toHaveBeenCalledWith('/iot/tSimCardDataStop/excel/template', {
responseReturn: 'raw',
});
});
it('submits an excel data stop batch as file-only form data', async () => {
const { ExcelSubmit } = await import('./index');
const file = new File(['iccid'], 'dataStop.xlsx');
ExcelSubmit({ file });
expect(upload).toHaveBeenCalledWith(
'/iot/tSimCardDataStop/excel/submit',
{ file },
{ timeout: 60_000 },
);
});
it('submits an input data stop batch from the backend contract path', async () => {
const { InputSubmit } = await import('./index');
const params = {
input: '8986000000000000001 14000000001 460001000000001',
};
InputSubmit(params);
expect(post).toHaveBeenCalledWith(
'/iot/tSimCardDataStop/input/submit',
params,
);
});
});
@@ -0,0 +1,33 @@
import type {
TSimCardLifecycleExcelSubmitParam,
TSimCardLifecycleInputSubmitParam,
TSimCardLifecycleSubmitRes,
} from '../tSimCardLifecycle/type';
import { requestClient } from '#/api/request';
export function DownloadTemplate() {
return requestClient.download('/iot/tSimCardDataStop/excel/template', {
responseReturn: 'raw',
});
}
export function ExcelSubmit(
params: TSimCardLifecycleExcelSubmitParam,
): Promise<TSimCardLifecycleSubmitRes> {
return requestClient.upload(
'/iot/tSimCardDataStop/excel/submit',
{
file: params.file,
},
{
timeout: 60_000,
},
);
}
export function InputSubmit(
params: TSimCardLifecycleInputSubmitParam,
): Promise<TSimCardLifecycleSubmitRes> {
return requestClient.post('/iot/tSimCardDataStop/input/submit', params);
}
@@ -0,0 +1,39 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const get = vi.fn();
vi.mock('#/api/request', () => ({
requestClient: {
get,
},
}));
describe('tSimCardInfo session api', () => {
beforeEach(() => {
get.mockReset();
});
it('queries session without showing a global failure message', async () => {
const { QuerySimSession } = await import('./index');
const params = { iccid: '8986000000000000001' };
QuerySimSession(params);
expect(get).toHaveBeenCalledWith('iot/tSimCardInfo/querySimSession', {
params,
errorMessageMode: 'none',
});
});
it('queries status without showing a global failure message', async () => {
const { QuerySimStatus } = await import('./index');
const params = { iccid: '8986000000000000001' };
QuerySimStatus(params);
expect(get).toHaveBeenCalledWith('iot/tSimCardInfo/querySimStatus', {
params,
errorMessageMode: 'none',
});
});
});

Some files were not shown because too many files have changed in this diff Show More